1 // Copyright 1999, Be Incorporated. All Rights Reserved. 2 // Copyright 2000-2004, Jun Suzuki. All Rights Reserved. 3 // Copyright 2007, 2010 Stephan Aßmus. All Rights Reserved. 4 // Copyright 2010-2013, Haiku, Inc. All Rights Reserved. 5 // This file may be used under the terms of the Be Sample Code License. 6 7 8 #include "MediaConverterWindow.h" 9 10 #include <stdio.h> 11 #include <string.h> 12 #include <unistd.h> 13 14 #include <Alert.h> 15 #include <Application.h> 16 #include <Box.h> 17 #include <Button.h> 18 #include <Catalog.h> 19 #include <ControlLook.h> 20 #include <FilePanel.h> 21 #include <FindDirectory.h> 22 #include <LayoutBuilder.h> 23 #include <Locale.h> 24 #include <Menu.h> 25 #include <MenuBar.h> 26 #include <MenuField.h> 27 #include <MenuItem.h> 28 #include <NumberFormat.h> 29 #include <Path.h> 30 #include <PopUpMenu.h> 31 #include <Roster.h> 32 #include <ScrollBar.h> 33 #include <ScrollView.h> 34 #include <Slider.h> 35 #include <StringView.h> 36 #include <TextControl.h> 37 38 #include "MediaFileInfoView.h" 39 #include "MediaFileListView.h" 40 #include "MessageConstants.h" 41 42 43 #undef B_TRANSLATION_CONTEXT 44 #define B_TRANSLATION_CONTEXT "MediaConverter" 45 #define VERSION "1.3.0" 46 47 48 static const unsigned int kMinSourceWidth = 12; 49 static const unsigned int kQualitySliderWidth = 28; 50 static const unsigned int kDurationWidth = 10; 51 52 53 // #pragma mark - DirectoryFilter 54 55 56 class DirectoryFilter : public BRefFilter { 57 public: 58 DirectoryFilter() {}; 59 virtual bool Filter(const entry_ref* ref, 60 BNode* node, struct stat_beos* st, const char* filetype) 61 { 62 return node->IsDirectory(); 63 } 64 }; 65 66 67 // #pragma mark - FileFormatMenuItem 68 69 70 class FileFormatMenuItem : public BMenuItem { 71 public: 72 FileFormatMenuItem(media_file_format* format); 73 virtual ~FileFormatMenuItem(); 74 75 media_file_format fFileFormat; 76 }; 77 78 79 FileFormatMenuItem::FileFormatMenuItem(media_file_format* format) 80 : 81 BMenuItem(format->pretty_name, new BMessage(FORMAT_SELECT_MESSAGE)) 82 { 83 memcpy(&fFileFormat, format, sizeof(fFileFormat)); 84 } 85 86 87 FileFormatMenuItem::~FileFormatMenuItem() 88 { 89 } 90 91 92 // #pragma mark - CodecMenuItem 93 94 95 class CodecMenuItem : public BMenuItem { 96 public: 97 CodecMenuItem(media_codec_info* ci, uint32 message_type); 98 virtual ~CodecMenuItem(); 99 100 media_codec_info fCodecInfo; 101 }; 102 103 104 CodecMenuItem::CodecMenuItem(media_codec_info* ci, uint32 message_type) 105 : 106 BMenuItem(ci->pretty_name, new BMessage(message_type)) 107 { 108 memcpy(&fCodecInfo, ci, sizeof(fCodecInfo)); 109 } 110 111 112 CodecMenuItem::~CodecMenuItem() 113 { 114 } 115 116 117 // #pragma mark - OutputBox 118 119 120 class OutputBox : public BBox { 121 public: 122 OutputBox(border_style border, BView* child); 123 virtual void FrameResized(float width, float height) 124 { 125 MediaConverterWindow* window 126 = dynamic_cast<MediaConverterWindow*>(Window()); 127 if (window != NULL) 128 window->TruncateOutputFolderPath(); 129 BBox::FrameResized(width, height); 130 } 131 }; 132 133 134 OutputBox::OutputBox(border_style border, BView* child) 135 : 136 BBox(border, child) 137 { 138 } 139 140 141 // #pragma mark - MediaConverterWindow 142 143 144 MediaConverterWindow::MediaConverterWindow(BRect frame) 145 : 146 BWindow(frame, B_TRANSLATE_SYSTEM_NAME("MediaConverter"), 147 B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, B_NOT_ZOOMABLE 148 | B_NOT_V_RESIZABLE | B_ASYNCHRONOUS_CONTROLS 149 | B_AUTO_UPDATE_SIZE_LIMITS), 150 fVideoQuality(75), 151 fAudioQuality(75), 152 fSaveFilePanel(NULL), 153 fOpenFilePanel(NULL), 154 fOutputDirSpecified(false), 155 fEnabled(true), 156 fConverting(false), 157 fCancelling(false) 158 { 159 BPath outputDir; 160 if (find_directory(B_USER_DIRECTORY, &outputDir) != B_OK) 161 outputDir.SetTo("/boot/home"); 162 fOutputDir.SetTo(outputDir.Path()); 163 164 fMenuBar = new BMenuBar("menubar"); 165 _CreateMenu(); 166 167 float padding = be_control_look->DefaultItemSpacing(); 168 169 fListView = new MediaFileListView(); 170 fListView->SetExplicitMinSize(BSize(padding * kMinSourceWidth, B_SIZE_UNSET)); 171 BScrollView* scroller = new BScrollView(NULL, fListView, 0, false, true); 172 173 // file list view box 174 fSourcesBox = new BBox(B_FANCY_BORDER, scroller); 175 fSourcesBox->SetLayout(new BGroupLayout(B_HORIZONTAL, 0)); 176 // fSourcesBox's layout adjusted in _UpdateLabels 177 178 // info box 179 fInfoView = new MediaFileInfoView(); 180 fInfoView->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, 181 B_ALIGN_VERTICAL_UNSET)); 182 fInfoBox = new BBox(B_FANCY_BORDER, fInfoView); 183 184 // output menu fields 185 fFormatMenu = new BMenuField(NULL, B_TRANSLATE("File format:"), 186 new BPopUpMenu("")); 187 fAudioMenu = new BMenuField(NULL, B_TRANSLATE("Audio encoding:"), 188 new BPopUpMenu("")); 189 fVideoMenu = new BMenuField(NULL, B_TRANSLATE("Video encoding:"), 190 new BPopUpMenu("")); 191 192 // output folder 193 fDestButton = new BButton(B_TRANSLATE("Output folder"), 194 new BMessage(OUTPUT_FOLDER_MESSAGE)); 195 BAlignment labelAlignment(be_control_look->DefaultLabelAlignment()); 196 fOutputFolder = new BStringView(NULL, outputDir.Path()); 197 fOutputFolder->SetExplicitAlignment(labelAlignment); 198 199 // start/end duration 200 fStartDurationTC = new BTextControl(NULL, "0", NULL); 201 BLayoutItem* startDuration = fStartDurationTC->CreateTextViewLayoutItem(); 202 startDuration->SetExplicitSize(BSize(padding * kDurationWidth, B_SIZE_UNSET)); 203 startDuration->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 204 B_ALIGN_VERTICAL_CENTER)); 205 fEndDurationTC = new BTextControl(NULL, "0", NULL); 206 BLayoutItem* endDuration = fEndDurationTC->CreateTextViewLayoutItem(); 207 endDuration->SetExplicitSize(BSize(padding * kDurationWidth, B_SIZE_UNSET)); 208 endDuration->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 209 B_ALIGN_VERTICAL_CENTER)); 210 211 // video quality 212 fVideoQualitySlider = new BSlider("VSlider", "" , 213 new BMessage(VIDEO_QUALITY_CHANGED_MESSAGE), 1, 100, B_HORIZONTAL); 214 fVideoQualitySlider->SetModificationMessage( 215 new BMessage(VIDEO_QUALITY_CHANGED_MESSAGE)); 216 fVideoQualitySlider->SetValue(fVideoQuality); 217 fVideoQualitySlider->SetEnabled(false); 218 fVideoQualitySlider->SetExplicitSize(BSize(padding * kQualitySliderWidth, 219 B_SIZE_UNSET)); 220 221 // audio quality 222 fAudioQualitySlider = new BSlider("ASlider", "" , 223 new BMessage(AUDIO_QUALITY_CHANGED_MESSAGE), 1, 100, B_HORIZONTAL); 224 fAudioQualitySlider->SetModificationMessage( 225 new BMessage(AUDIO_QUALITY_CHANGED_MESSAGE)); 226 fAudioQualitySlider->SetValue(fAudioQuality); 227 fAudioQualitySlider->SetEnabled(false); 228 fAudioQualitySlider->SetExplicitSize(BSize(padding * kQualitySliderWidth, 229 B_SIZE_UNSET)); 230 231 // output format box 232 BView* outputGrid = BLayoutBuilder::Grid<>() 233 .Add(fFormatMenu->CreateLabelLayoutItem(), 0, 0) 234 .Add(fFormatMenu->CreateMenuBarLayoutItem(), 1, 0) 235 .Add(fAudioMenu->CreateLabelLayoutItem(), 0, 1) 236 .Add(fAudioMenu->CreateMenuBarLayoutItem(), 1, 1) 237 .Add(fVideoMenu->CreateLabelLayoutItem(), 0, 2) 238 .Add(fVideoMenu->CreateMenuBarLayoutItem(), 1, 2) 239 .Add(fDestButton, 0, 3) 240 .Add(fOutputFolder, 1, 3) 241 .Add(fStartDurationTC->CreateLabelLayoutItem(), 0, 4) 242 .Add(startDuration, 1, 4) 243 .Add(fEndDurationTC->CreateLabelLayoutItem(), 0, 5) 244 .Add(endDuration, 1, 5) 245 .Add(fVideoQualitySlider, 0, 6, 2, 1) 246 .Add(fAudioQualitySlider, 0, 7, 2, 1) 247 .View(); 248 outputGrid->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, 249 B_ALIGN_USE_FULL_HEIGHT)); 250 fOutputBox = new OutputBox(B_FANCY_BORDER, outputGrid); 251 fOutputBox->SetLayout(new BGroupLayout(B_HORIZONTAL, 0)); 252 // fOutputBox's layout adjusted in _UpdateLabels 253 254 // buttons 255 fPreviewButton = new BButton(B_TRANSLATE("Preview"), 256 new BMessage(PREVIEW_MESSAGE)); 257 fPreviewButton->SetEnabled(false); 258 259 fConvertButton = new BButton(B_TRANSLATE("Convert"), 260 new BMessage(CONVERT_BUTTON_MESSAGE)); 261 262 // Status views 263 fStatus = new BStringView(NULL, NULL); 264 fStatus->SetExplicitAlignment(labelAlignment); 265 fFileStatus = new BStringView(NULL, NULL); 266 fFileStatus->SetExplicitAlignment(labelAlignment); 267 268 SetStatusMessage(""); 269 _UpdateLabels(); 270 271 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 272 .SetInsets(0, 0, 0, 0) 273 .Add(fMenuBar) 274 .AddSplit(B_HORIZONTAL, B_USE_DEFAULT_SPACING) 275 .SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING, 276 B_USE_WINDOW_SPACING, 0) 277 .Add(fSourcesBox) 278 .AddGroup(B_VERTICAL, B_USE_ITEM_SPACING) 279 .Add(fInfoBox) 280 .Add(fOutputBox) 281 .End() 282 .End() 283 .AddGrid(B_USE_ITEM_SPACING) 284 .SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING, 285 B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING) 286 .Add(fStatus, 0, 0) 287 .Add(fFileStatus, 0, 1) 288 .Add(BSpaceLayoutItem::CreateGlue(), 1, 0) 289 .Add(fPreviewButton, 2, 0) 290 .Add(fConvertButton, 3, 0) 291 .End(); 292 } 293 294 295 MediaConverterWindow::~MediaConverterWindow() 296 { 297 delete fSaveFilePanel; 298 delete fOpenFilePanel; 299 } 300 301 302 // #pragma mark - 303 304 305 void 306 MediaConverterWindow::MessageReceived(BMessage* message) 307 { 308 entry_ref inRef; 309 310 BEntry inEntry; 311 BNumberFormat fNumberFormat; 312 BString buffer; 313 314 switch (message->what) { 315 #if B_BEOS_VERSION <= B_BEOS_VERSION_6 316 case B_LANGUAGE_CHANGED: 317 LanguageChanged(); 318 break; 319 #endif 320 321 case INIT_FORMAT_MENUS: 322 BuildFormatMenu(); 323 if (CountSourceFiles() == 0) 324 SetEnabled(false, false); 325 break; 326 327 case B_SIMPLE_DATA: 328 if (message->WasDropped()) { 329 DetachCurrentMessage(); 330 message->what = B_REFS_RECEIVED; 331 BMessenger(be_app).SendMessage(message); 332 delete message; 333 } 334 break; 335 336 case FORMAT_SELECT_MESSAGE: 337 BuildAudioVideoMenus(); 338 break; 339 case AUDIO_CODEC_SELECT_MESSAGE: 340 break; 341 case VIDEO_CODEC_SELECT_MESSAGE: 342 break; 343 344 case CONVERT_BUTTON_MESSAGE: 345 if (!fConverting) { 346 fConvertButton->SetLabel(B_TRANSLATE("Cancel")); 347 fConverting = true; 348 SetStatusMessage(B_TRANSLATE("Convert")); 349 SetEnabled(false, true); 350 BMessenger(be_app).SendMessage(START_CONVERSION_MESSAGE); 351 } else if (!fCancelling) { 352 fCancelling = true; 353 SetStatusMessage(B_TRANSLATE("Cancelling" B_UTF8_ELLIPSIS)); 354 BMessenger(be_app).SendMessage(CANCEL_CONVERSION_MESSAGE); 355 } 356 break; 357 358 case CONVERSION_DONE_MESSAGE: 359 { 360 SetStatusMessage(fCancelling ? B_TRANSLATE("Conversion cancelled") 361 : B_TRANSLATE("Conversion completed")); 362 fConverting = false; 363 fCancelling = false; 364 bool enable = CountSourceFiles() > 0; 365 SetEnabled(enable, enable); 366 fConvertButton->SetLabel(B_TRANSLATE("Convert")); 367 break; 368 } 369 370 case OUTPUT_FOLDER_MESSAGE: 371 // Execute Save Panel 372 if (fSaveFilePanel == NULL) { 373 BButton* selectThisDir; 374 375 BMessage folderSelect(FOLDER_SELECT_MESSAGE); 376 fSaveFilePanel = new BFilePanel(B_OPEN_PANEL, NULL, NULL, 377 B_DIRECTORY_NODE, true, &folderSelect, NULL, false, true); 378 fSaveFilePanel->SetButtonLabel(B_DEFAULT_BUTTON, 379 B_TRANSLATE("Select")); 380 fSaveFilePanel->SetTarget(this); 381 382 fSaveFilePanel->Window()->Lock(); 383 fSaveFilePanel->Window()->SetTitle( 384 B_TRANSLATE("MediaConverter+:SaveDirectory")); 385 BRect buttonRect 386 = fSaveFilePanel->Window()->ChildAt(0)->FindView( 387 "cancel button")->Frame(); 388 buttonRect.right = buttonRect.left - 20; 389 buttonRect.left = buttonRect.right - 130; 390 selectThisDir = new BButton(buttonRect, NULL, 391 B_TRANSLATE("Select this folder"), 392 new BMessage(SELECT_THIS_DIR_MESSAGE), 393 B_FOLLOW_BOTTOM | B_FOLLOW_RIGHT); 394 selectThisDir->SetTarget(this); 395 fSaveFilePanel->Window()->ChildAt(0)->AddChild(selectThisDir); 396 fSaveFilePanel->Window()->Unlock(); 397 398 fSaveFilePanel->SetRefFilter(new DirectoryFilter); 399 } 400 fSaveFilePanel->Show(); 401 break; 402 403 case FOLDER_SELECT_MESSAGE: 404 // "SELECT" Button at Save Panel Pushed 405 fSaveFilePanel->GetNextSelectedRef(&inRef); 406 inEntry.SetTo(&inRef, true); 407 _SetOutputFolder(inEntry); 408 fOutputDirSpecified = true; 409 fSaveFilePanel->Rewind(); 410 break; 411 412 case SELECT_THIS_DIR_MESSAGE: 413 // "THIS DIR" Button at Save Panel Pushed 414 fSaveFilePanel->GetPanelDirectory(&inRef); 415 fSaveFilePanel->Hide(); 416 inEntry.SetTo(&inRef, true); 417 _SetOutputFolder(inEntry); 418 fOutputDirSpecified = true; 419 break; 420 421 case OPEN_FILE_MESSAGE: 422 // Execute Open Panel 423 if (!fOpenFilePanel) { 424 fOpenFilePanel = new BFilePanel(B_OPEN_PANEL, NULL, NULL, 425 B_FILE_NODE, true, NULL, NULL, false, true); 426 fOpenFilePanel->SetTarget(this); 427 } 428 fOpenFilePanel->Show(); 429 break; 430 431 case B_REFS_RECEIVED: 432 // Media Files Seleced by Open Panel 433 DetachCurrentMessage(); 434 message->what = B_REFS_RECEIVED; 435 BMessenger(be_app).SendMessage(message); 436 // fall through 437 438 case B_CANCEL: 439 break; 440 441 case QUIT_MESSAGE: 442 MediaConverterWindow::QuitRequested(); 443 break; 444 445 case PREVIEW_MESSAGE: 446 { 447 // Build the command line to launch the preview application. 448 // TODO: Launch the default app instead of hardcoded MediaPlayer! 449 int32 srcIndex = fListView->CurrentSelection(); 450 BMediaFile* inFile = NULL; 451 status_t status = GetSourceFileAt(srcIndex, &inFile, &inRef); 452 453 const char* argv[3]; 454 BString startPosString; 455 BPath path; 456 457 if (status == B_OK) { 458 argv[0] = "-pos"; 459 // NOTE: -pos argument is currently not supported by Haiku 460 // MediaPlayer. 461 startPosString << fStartDurationTC->Text(); 462 startPosString << "000"; 463 argv[1] = startPosString.String(); 464 465 status = inEntry.SetTo(&inRef); 466 } 467 468 if (status == B_OK) { 469 status = inEntry.GetPath(&path); 470 if (status == B_OK) 471 argv[2] = path.Path(); 472 } 473 474 if (status == B_OK) { 475 status = be_roster->Launch( 476 "application/x-vnd.Haiku-MediaPlayer", 477 3, (char**)argv, NULL); 478 } 479 480 if (status != B_OK && status != B_ALREADY_RUNNING) { 481 BString errorString(B_TRANSLATE("Error launching: %strError%")); 482 errorString.ReplaceFirst("%strError%", strerror(status)); 483 BAlert* alert = new BAlert(B_TRANSLATE("Error"), 484 errorString.String(), B_TRANSLATE("OK")); 485 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 486 alert->Go(); 487 } 488 break; 489 } 490 491 case VIDEO_QUALITY_CHANGED_MESSAGE: 492 { 493 int32 value; 494 BString data; 495 496 message->FindInt32("be:value", &value); 497 double percentValue = value / 100.0; 498 499 if (fNumberFormat.FormatPercent(data, percentValue) != B_OK) 500 data.SetToFormat("%d%%", (int8)value); 501 502 buffer.SetToFormat(B_TRANSLATE("Video quality: %s"), data.String()); 503 fVideoQualitySlider->SetLabel(buffer.String()); 504 fVideoQuality = (int)percentValue; 505 break; 506 } 507 508 case AUDIO_QUALITY_CHANGED_MESSAGE: 509 { 510 int32 value; 511 BString data; 512 513 message->FindInt32("be:value", &value); 514 double percentValue = value / 100.0; 515 516 if (fNumberFormat.FormatPercent(data, percentValue) != B_OK) 517 data.SetToFormat("%d%%", (int8)value); 518 519 buffer.SetToFormat(B_TRANSLATE("Audio quality: %s"), data.String()); 520 fAudioQualitySlider->SetLabel(buffer.String()); 521 fAudioQuality = (int)percentValue; 522 break; 523 } 524 525 default: 526 BWindow::MessageReceived(message); 527 break; 528 } 529 } 530 531 532 bool 533 MediaConverterWindow::QuitRequested() 534 { 535 if (!fConverting) { 536 BMessenger(be_app).SendMessage(B_QUIT_REQUESTED); 537 return true; 538 } else if (!fCancelling) { 539 fCancelling = true; 540 SetStatusMessage(B_TRANSLATE("Cancelling")); 541 BMessenger(be_app).SendMessage(CANCEL_CONVERSION_MESSAGE); 542 } 543 544 return false; 545 } 546 547 548 // #pragma mark - 549 550 551 void 552 MediaConverterWindow::LanguageChanged() 553 { 554 _DestroyMenu(); 555 _CreateMenu(); 556 _UpdateLabels(); 557 BuildAudioVideoMenus(); 558 Lock(); 559 fInfoView->Invalidate(); 560 Unlock(); 561 } 562 563 564 void 565 MediaConverterWindow::BuildAudioVideoMenus() 566 { 567 BMenu* menu = fAudioMenu->Menu(); 568 BMenuItem* item; 569 570 // clear out old audio codec menu items 571 while ((item = menu->RemoveItem((int32)0)) != NULL) 572 delete item; 573 574 bool separator = true; 575 576 // get selected file format 577 FileFormatMenuItem* ffmi 578 = (FileFormatMenuItem*)fFormatMenu->Menu()->FindMarked(); 579 media_file_format* mf_format = &(ffmi->fFileFormat); 580 581 media_format format, outfmt; 582 format.Clear(); 583 media_codec_info codec_info; 584 int32 cookie = 0; 585 CodecMenuItem* cmi; 586 587 // add available audio encoders to menu 588 format.type = B_MEDIA_RAW_AUDIO; 589 format.u.raw_audio = media_raw_audio_format::wildcard; 590 while (get_next_encoder(&cookie, mf_format, &format, &outfmt, &codec_info) 591 == B_OK) { 592 if (separator) { 593 menu->AddItem(new BMenuItem( 594 B_TRANSLATE_CONTEXT("No audio", "Audio codecs list"), 595 new BMessage(AUDIO_CODEC_SELECT_MESSAGE))); 596 menu->AddSeparatorItem(); 597 separator = false; 598 } 599 600 cmi = new CodecMenuItem(&codec_info, AUDIO_CODEC_SELECT_MESSAGE); 601 menu->AddItem(cmi); 602 // reset media format struct 603 /* 604 format.type = B_MEDIA_RAW_AUDIO; 605 format.u.raw_audio = media_raw_audio_format::wildcard; 606 */ 607 } 608 609 // mark first audio encoder 610 item = menu->ItemAt(0); 611 if (item != NULL) { 612 fAudioMenu->SetEnabled(fEnabled); 613 fAudioQualitySlider->SetEnabled(fEnabled); 614 item->SetMarked(true); 615 ((BInvoker*)item)->Invoke(); 616 } else { 617 item = new BMenuItem( 618 B_TRANSLATE_CONTEXT("None available", "Audio codecs"), NULL); 619 menu->AddItem(item); 620 item->SetMarked(true); 621 fAudioMenu->SetEnabled(false); 622 fAudioQualitySlider->SetEnabled(false); 623 } 624 625 // clear out old video codec menu items 626 menu = fVideoMenu->Menu(); 627 while ((item = menu->RemoveItem((int32)0)) != NULL) 628 delete item; 629 630 separator = true; 631 632 // construct a generic video format. Some of these parameters 633 // seem silly, but are needed for R4.5.x, which is more picky 634 // than subsequent BeOS releases will be. 635 format.Clear(); 636 format.type = B_MEDIA_RAW_VIDEO; 637 format.u.raw_video.last_active = (uint32)(240 - 1); 638 format.u.raw_video.orientation = B_VIDEO_TOP_LEFT_RIGHT; 639 format.u.raw_video.display.format = B_RGB32; 640 format.u.raw_video.display.line_width = (int32)320; 641 format.u.raw_video.display.line_count = (int32)240; 642 format.u.raw_video.display.bytes_per_row = 4 * 320; 643 644 // add available video encoders to menu 645 cookie = 0; 646 while (get_next_encoder(&cookie, mf_format, &format, &outfmt, &codec_info) 647 == B_OK) { 648 if (separator) { 649 menu->AddItem(new BMenuItem( 650 B_TRANSLATE_CONTEXT("No video", "Video codecs list"), 651 new BMessage(VIDEO_CODEC_SELECT_MESSAGE))); 652 menu->AddSeparatorItem(); 653 separator = false; 654 } 655 656 cmi = new CodecMenuItem(&codec_info, VIDEO_CODEC_SELECT_MESSAGE); 657 menu->AddItem(cmi); 658 } 659 660 // mark first video encoder 661 item = menu->ItemAt(0); 662 if (item != NULL) { 663 fVideoMenu->SetEnabled(fEnabled); 664 fVideoQualitySlider->SetEnabled(fEnabled); 665 item->SetMarked(true); 666 ((BInvoker*)item)->Invoke(); 667 } else { 668 item = new BMenuItem( 669 B_TRANSLATE_CONTEXT("None available", "Video codecs"), NULL); 670 menu->AddItem(item); 671 item->SetMarked(true); 672 fVideoMenu->SetEnabled(false); 673 fVideoQualitySlider->SetEnabled(false); 674 } 675 } 676 677 void 678 MediaConverterWindow::GetSelectedFormatInfo(media_file_format** format, 679 media_codec_info** audio, media_codec_info** video) 680 { 681 *audio = NULL; 682 *video = NULL; 683 *format = NULL; 684 685 FileFormatMenuItem* formatItem = 686 dynamic_cast<FileFormatMenuItem*>(fFormatMenu->Menu()->FindMarked()); 687 if (formatItem != NULL) 688 *format = &(formatItem->fFileFormat); 689 690 *audio = *video = NULL; 691 CodecMenuItem* codecItem = 692 dynamic_cast<CodecMenuItem*>(fAudioMenu->Menu()->FindMarked()); 693 if (codecItem != NULL) 694 *audio = &(codecItem->fCodecInfo); 695 696 codecItem = dynamic_cast<CodecMenuItem*>(fVideoMenu->Menu()->FindMarked()); 697 if (codecItem != NULL) 698 *video = &(codecItem->fCodecInfo); 699 } 700 701 702 void 703 MediaConverterWindow::BuildFormatMenu() 704 { 705 BMenu* menu = fFormatMenu->Menu(); 706 BMenuItem* item; 707 708 // clear out old format menu items 709 while ((item = menu->RemoveItem((int32)0)) != NULL) 710 delete item; 711 712 // add menu items for each file format 713 media_file_format mfi; 714 int32 cookie = 0; 715 FileFormatMenuItem* ff_item; 716 while (get_next_file_format(&cookie, &mfi) == B_OK) { 717 if ((mfi.capabilities & media_file_format::B_WRITABLE) == 0) 718 continue; 719 ff_item = new FileFormatMenuItem(&mfi); 720 menu->AddItem(ff_item); 721 } 722 723 // mark first item 724 item = menu->ItemAt(0); 725 if (item != NULL) { 726 item->SetMarked(true); 727 ((BInvoker*)item)->Invoke(); 728 } 729 } 730 731 732 void 733 MediaConverterWindow::SetFileMessage(const char* message) 734 { 735 fFileStatus->SetText(message); 736 } 737 738 739 void 740 MediaConverterWindow::SetStatusMessage(const char* message) 741 { 742 fStatus->SetText(message); 743 } 744 745 746 // #pragma mark - 747 748 749 bool 750 MediaConverterWindow::AddSourceFile(BMediaFile* file, const entry_ref& ref) 751 { 752 if (!fListView->AddMediaItem(file, ref)) 753 return false; 754 755 if (!fOutputDirSpecified) { 756 BEntry entry(&ref); 757 entry.GetParent(&entry); 758 _SetOutputFolder(entry); 759 } 760 761 return true; 762 } 763 764 765 void 766 MediaConverterWindow::RemoveSourceFile(int32 index) 767 { 768 delete fListView->RemoveItem(index); 769 fStartDurationTC->SetText("0"); 770 fEndDurationTC->SetText("0"); 771 } 772 773 774 int32 775 MediaConverterWindow::CountSourceFiles() 776 { 777 return fListView->CountItems(); 778 } 779 780 781 status_t 782 MediaConverterWindow::GetSourceFileAt(int32 index, BMediaFile** _file, 783 entry_ref* ref) 784 { 785 MediaFileListItem* item = dynamic_cast<MediaFileListItem*>( 786 fListView->ItemAt(index)); 787 if (item != NULL) { 788 *_file = item->fMediaFile; 789 *ref = item->fRef; 790 return B_OK; 791 } else 792 return B_ERROR; 793 } 794 795 796 void 797 MediaConverterWindow::SourceFileSelectionChanged() 798 { 799 int32 selected = fListView->CurrentSelection(); 800 BMediaFile* file = NULL; 801 entry_ref ref; 802 bool enabled = GetSourceFileAt(selected, &file, &ref) == B_OK; 803 804 fPreviewButton->SetEnabled(enabled); 805 fVideoQualitySlider->SetEnabled(enabled); 806 fAudioQualitySlider->SetEnabled(enabled); 807 fStartDurationTC->SetEnabled(enabled); 808 fEndDurationTC->SetEnabled(enabled); 809 810 BString duration; 811 if (enabled) { 812 fInfoView->Update(file, &ref); 813 // HACK: get the fInfoView to update the duration "synchronously" 814 UpdateIfNeeded(); 815 duration << fInfoView->Duration() / 1000; 816 } else 817 duration = "0"; 818 819 // update duration text controls 820 fStartDurationTC->SetText("0"); 821 fEndDurationTC->SetText(duration.String()); 822 } 823 824 825 // #pragma mark - 826 827 828 void 829 MediaConverterWindow::SetEnabled(bool enabled, bool convertEnabled) 830 { 831 fConvertButton->SetEnabled(convertEnabled); 832 if (enabled == fEnabled) 833 return; 834 835 fFormatMenu->SetEnabled(enabled); 836 fAudioMenu->SetEnabled(enabled); 837 fVideoMenu->SetEnabled(enabled); 838 fListView->SetEnabled(enabled); 839 fStartDurationTC->SetEnabled(enabled); 840 fEndDurationTC->SetEnabled(enabled); 841 842 fEnabled = enabled; 843 } 844 845 846 bool 847 MediaConverterWindow::IsEnabled() 848 { 849 return fEnabled; 850 } 851 852 853 const char* 854 MediaConverterWindow::StartDuration() const 855 { 856 return fStartDurationTC->Text(); 857 } 858 859 860 const char* 861 MediaConverterWindow::EndDuration() const 862 { 863 return fEndDurationTC->Text(); 864 } 865 866 867 BDirectory 868 MediaConverterWindow::OutputDirectory() const 869 { 870 return fOutputDir; 871 } 872 873 874 void 875 MediaConverterWindow::SetAudioQualityLabel(const char* label) 876 { 877 fAudioQualitySlider->SetLabel(label); 878 } 879 880 881 void 882 MediaConverterWindow::SetVideoQualityLabel(const char* label) 883 { 884 fVideoQualitySlider->SetLabel(label); 885 } 886 887 888 void 889 MediaConverterWindow::TruncateOutputFolderPath() 890 { 891 BEntry entry; 892 fOutputDir.GetEntry(&entry); 893 BPath path; 894 entry.GetPath(&path); 895 BString pathString(path.Path()); 896 float maxWidth = fVideoMenu->MenuBar()->Frame().Width(); 897 898 fOutputFolder->TruncateString(&pathString, B_TRUNCATE_MIDDLE, maxWidth); 899 fOutputFolder->SetText(pathString.String()); 900 if (fOutputFolder->StringWidth(path.Path()) > maxWidth) 901 fOutputFolder->SetToolTip(path.Path()); 902 else 903 fOutputFolder->SetToolTip((const char*)NULL); 904 } 905 906 907 // #pragma mark - 908 909 910 void 911 MediaConverterWindow::_UpdateLabels() 912 { 913 BNumberFormat fNumberFormat; 914 915 if (fSourcesBox != NULL) { 916 fSourcesBox->SetLabel(B_TRANSLATE("Source files")); 917 _UpdateBBoxLayoutInsets(fSourcesBox); 918 } 919 920 if (fInfoBox != NULL) 921 fInfoBox->SetLabel(B_TRANSLATE("File details")); 922 923 if (fOutputBox != NULL) { 924 fOutputBox->SetLabel(B_TRANSLATE("Output format")); 925 _UpdateBBoxLayoutInsets(fOutputBox); 926 } 927 928 if (fConvertButton != NULL) 929 fConvertButton->SetLabel(B_TRANSLATE("Convert")); 930 931 if (fPreviewButton != NULL) 932 fPreviewButton->SetLabel(B_TRANSLATE("Preview")); 933 934 if (fDestButton != NULL) 935 fDestButton->SetLabel(B_TRANSLATE("Output folder")); 936 937 if (fVideoQualitySlider != NULL) { 938 BString buffer; 939 BString data; 940 double percentValue = fVideoQuality / 100.0; 941 942 if (fNumberFormat.FormatPercent(data, percentValue) != B_OK) 943 data.SetToFormat("%d%%", (int8)fVideoQuality); 944 945 buffer.SetToFormat(B_TRANSLATE("Video quality: %s"), data.String()); 946 fVideoQuality = (int)percentValue; 947 fVideoQualitySlider->SetLabel(buffer.String()); 948 fVideoQualitySlider->SetLimitLabels(B_TRANSLATE("Low"), B_TRANSLATE("High")); 949 } 950 951 if (fAudioQualitySlider != NULL) { 952 BString buffer; 953 BString data; 954 double percentValue = fAudioQuality / 100.0; 955 956 if (fNumberFormat.FormatPercent(data, percentValue) != B_OK) { 957 data.SetToFormat("%d%%", (int8)fAudioQuality); 958 } 959 960 buffer.SetToFormat(B_TRANSLATE("Audio quality: %s"), data.String()); 961 fAudioQuality = (int)percentValue; 962 fAudioQualitySlider->SetLabel(buffer.String()); 963 fAudioQualitySlider->SetLimitLabels(B_TRANSLATE("Low"), B_TRANSLATE("High")); 964 } 965 966 if (fStartDurationTC != NULL) 967 fStartDurationTC->SetLabel(B_TRANSLATE("Start [ms]: ")); 968 969 if (fEndDurationTC != NULL) 970 fEndDurationTC->SetLabel(B_TRANSLATE("End [ms]: ")); 971 972 if (fFormatMenu != NULL) 973 fFormatMenu->SetLabel(B_TRANSLATE("File format:")); 974 975 if (fAudioMenu != NULL) 976 fAudioMenu->SetLabel(B_TRANSLATE("Audio encoding:")); 977 978 if (fVideoMenu != NULL) 979 fVideoMenu->SetLabel(B_TRANSLATE("Video encoding:")); 980 981 SetFileMessage(B_TRANSLATE("Drop media files onto this window")); 982 } 983 984 985 void 986 MediaConverterWindow::_UpdateBBoxLayoutInsets(BBox* box) 987 { 988 BTwoDimensionalLayout* layout 989 = dynamic_cast<BTwoDimensionalLayout*>(box->GetLayout()); 990 if (layout != NULL) { 991 float padding = be_control_look->DefaultItemSpacing(); 992 layout->SetInsets(padding, box->TopBorderOffset() + padding, padding, 993 padding); 994 } 995 } 996 997 998 void 999 MediaConverterWindow::_DestroyMenu() 1000 { 1001 BMenu* menu; 1002 1003 while ((menu = fMenuBar->SubmenuAt(0)) != NULL) { 1004 fMenuBar->RemoveItem(menu); 1005 delete menu; 1006 } 1007 } 1008 1009 1010 void 1011 MediaConverterWindow::_CreateMenu() 1012 { 1013 BMenu* menu; 1014 BMenuItem* item; 1015 1016 menu = new BMenu(B_TRANSLATE_CONTEXT("File", "Menu")); 1017 item = new BMenuItem(B_TRANSLATE_CONTEXT("Open" B_UTF8_ELLIPSIS, "Menu"), 1018 new BMessage(OPEN_FILE_MESSAGE), 'O'); 1019 menu->AddItem(item); 1020 menu->AddSeparatorItem(); 1021 item = new BMenuItem(B_TRANSLATE_CONTEXT("Quit", "Menu"), 1022 new BMessage(QUIT_MESSAGE), 'Q'); 1023 menu->AddItem(item); 1024 1025 fMenuBar->AddItem(menu); 1026 } 1027 1028 1029 void 1030 MediaConverterWindow::_SetOutputFolder(BEntry entry) 1031 { 1032 BPath path; 1033 entry.GetPath(&path); 1034 if (access(path.Path(), W_OK) != -1) { 1035 fOutputDir.SetTo(&entry); 1036 } else { 1037 BString errorString(B_TRANSLATE("Error writing to location: %strPath%." 1038 " Defaulting to location: /boot/home")); 1039 errorString.ReplaceFirst("%strPath%", path.Path()); 1040 BAlert* alert = new BAlert(B_TRANSLATE("Error"), 1041 errorString.String(), B_TRANSLATE("OK")); 1042 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1043 alert->Go(); 1044 fOutputDir.SetTo("/boot/home"); 1045 } 1046 TruncateOutputFolderPath(); 1047 } 1048