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