1 // Copyright 1999, Be Incorporated. All Rights Reserved. 2 // Copyright 2000-2004, Jun Suzuki. All Rights Reserved. 3 // Copyright 2007, Stephan Aßmus. All Rights Reserved. 4 // This file may be used under the terms of the Be Sample Code License. 5 #include "MediaConverterApp.h" 6 7 #include <inttypes.h> 8 #include <new> 9 #include <stdio.h> 10 #include <string.h> 11 12 #include <Alert.h> 13 #include <Catalog.h> 14 #include <fs_attr.h> 15 #include <Locale.h> 16 #include <MediaFile.h> 17 #include <MediaTrack.h> 18 #include <Mime.h> 19 #include <Path.h> 20 #include <String.h> 21 #include <StringFormat.h> 22 #include <View.h> 23 24 #include "MediaConverterWindow.h" 25 #include "MediaEncoderWindow.h" 26 #include "MessageConstants.h" 27 28 29 #undef B_TRANSLATION_CONTEXT 30 #define B_TRANSLATION_CONTEXT "MediaConverter" 31 32 33 const char APP_SIGNATURE[] = "application/x-vnd.Haiku-MediaConverter"; 34 35 36 MediaConverterApp::MediaConverterApp() 37 : 38 BApplication(APP_SIGNATURE), 39 fWin(NULL), 40 fConvertThreadID(-1), 41 fConverting(false), 42 fCancel(false) 43 { 44 // TODO: implement settings for window pos 45 fWin = new MediaConverterWindow(BRect(50, 50, 520, 555)); 46 } 47 48 49 MediaConverterApp::~MediaConverterApp() 50 { 51 if (fConvertThreadID >= 0) { 52 fCancel = true; 53 status_t exitValue; 54 wait_for_thread(fConvertThreadID, &exitValue); 55 } 56 } 57 58 59 // #pragma mark - 60 61 62 void 63 MediaConverterApp::MessageReceived(BMessage *msg) 64 { 65 switch (msg->what) { 66 case FILE_LIST_CHANGE_MESSAGE: 67 if (fWin->Lock()) { 68 bool enable = fWin->CountSourceFiles() > 0; 69 fWin->SetEnabled(enable, enable); 70 fWin->Unlock(); 71 } 72 break; 73 74 case START_CONVERSION_MESSAGE: 75 if (!fConverting) 76 StartConverting(); 77 break; 78 79 case CANCEL_CONVERSION_MESSAGE: 80 fCancel = true; 81 break; 82 83 case CONVERSION_DONE_MESSAGE: 84 fCancel = false; 85 fConverting = false; 86 DetachCurrentMessage(); 87 BMessenger(fWin).SendMessage(msg); 88 break; 89 90 default: 91 BApplication::MessageReceived(msg); 92 break; 93 } 94 } 95 96 97 void 98 MediaConverterApp::ReadyToRun() 99 { 100 fWin->Show(); 101 fWin->PostMessage(INIT_FORMAT_MENUS); 102 } 103 104 105 void 106 MediaConverterApp::RefsReceived(BMessage* msg) 107 { 108 entry_ref ref; 109 int32 i = 0; 110 BString errorFiles; 111 int32 errors = 0; 112 113 // from Open dialog or drag & drop 114 115 while (msg->FindRef("refs", i++, &ref) == B_OK) { 116 117 uint32 flags = 0; // B_MEDIA_FILE_NO_READ_AHEAD 118 BMediaFile* file = new(std::nothrow) BMediaFile(&ref, flags); 119 120 if (file == NULL || file->InitCheck() != B_OK) { 121 errorFiles << ref.name << "\n"; 122 errors++; 123 delete file; 124 continue; 125 } 126 if (fWin->Lock()) { 127 if (!fWin->AddSourceFile(file, ref)) 128 delete file; 129 fWin->Unlock(); 130 } 131 } 132 133 if (errors) { 134 BString alertText; 135 static BStringFormat format(B_TRANSLATE("{0, plural, " 136 "one{The file was not recognized as a supported media file:} " 137 "other{# files were not recognized as supported media files:}}")); 138 format.Format(alertText, errors); 139 140 alertText << "\n" << errorFiles; 141 BAlert* alert = new BAlert((errors > 1) ? 142 B_TRANSLATE("Error loading files") : 143 B_TRANSLATE("Error loading a file"), 144 alertText.String(), B_TRANSLATE("Continue"), NULL, NULL, 145 B_WIDTH_AS_USUAL, B_STOP_ALERT); 146 alert->Go(); 147 } 148 } 149 150 151 // #pragma mark - 152 153 154 bool 155 MediaConverterApp::IsConverting() const 156 { 157 return fConverting; 158 } 159 160 161 void 162 MediaConverterApp::StartConverting() 163 { 164 bool locked = fWin->Lock(); 165 166 if (locked && (fWin->CountSourceFiles() > 0)) { 167 fConvertThreadID = spawn_thread(MediaConverterApp::_RunConvertEntry, 168 "converter thread", B_LOW_PRIORITY, (void *)this); 169 if (fConvertThreadID >= 0) { 170 fConverting = true; 171 fCancel = false; 172 resume_thread(fConvertThreadID); 173 } 174 } 175 176 if (locked) { 177 fWin->Unlock(); 178 } 179 } 180 181 182 void 183 MediaConverterApp::SetStatusMessage(const char* message) 184 { 185 if (fWin != NULL && fWin->Lock()) { 186 fWin->SetStatusMessage(message); 187 fWin->Unlock(); 188 } 189 } 190 191 192 // #pragma mark - 193 194 BEntry 195 MediaConverterApp::_CreateOutputFile(BDirectory directory, 196 entry_ref* ref, media_file_format* outputFormat) 197 { 198 BString name(ref->name); 199 // create output file name 200 int32 extIndex = name.FindLast('.'); 201 if (extIndex != B_ERROR) 202 name.Truncate(extIndex + 1); 203 else 204 name.Append("."); 205 206 name.Append(outputFormat->file_extension); 207 208 BEntry directoryEntry; 209 directory.GetEntry(&directoryEntry); 210 if (!directoryEntry.Exists()) { 211 BAlert* alert = new BAlert(B_TRANSLATE("Error"), 212 B_TRANSLATE("Selected directory not found. " 213 "Defaulting to /boot/home"), 214 B_TRANSLATE("OK")); 215 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 216 alert->Go(); 217 directory.SetTo("/boot/home"); 218 } 219 220 BEntry inEntry(ref); 221 BEntry outEntry; 222 223 if (inEntry.InitCheck() == B_OK) { 224 // ensure that output name is unique 225 int32 len = name.Length(); 226 int32 i = 1; 227 while (directory.Contains(name.String())) { 228 name.Truncate(len); 229 name << " " << i; 230 i++; 231 } 232 outEntry.SetTo(&directory, name.String()); 233 } 234 235 return outEntry; 236 } 237 238 239 int32 240 MediaConverterApp::_RunConvertEntry(void* castToMediaConverterApp) 241 { 242 MediaConverterApp* app = (MediaConverterApp*)castToMediaConverterApp; 243 app->_RunConvert(); 244 return 0; 245 } 246 247 248 void 249 MediaConverterApp::_RunConvert() 250 { 251 bigtime_t start = 0; 252 bigtime_t end = 0; 253 int32 audioQuality = 75; 254 int32 videoQuality = 75; 255 256 if (fWin->Lock()) { 257 char *a; 258 start = strtoimax(fWin->StartDuration(), &a, 0) * 1000; 259 end = strtoimax(fWin->EndDuration(), &a, 0) * 1000; 260 audioQuality = fWin->AudioQuality(); 261 videoQuality = fWin->VideoQuality(); 262 fWin->Unlock(); 263 } 264 265 int32 srcIndex = 0; 266 267 BMediaFile *inFile(NULL), *outFile(NULL); 268 BEntry outEntry; 269 entry_ref inRef; 270 entry_ref outRef; 271 BPath path; 272 BString name; 273 274 while (!fCancel) { 275 if (fWin->Lock()) { 276 status_t r = fWin->GetSourceFileAt(srcIndex, &inFile, &inRef); 277 if (r == B_OK) { 278 media_codec_info* audioCodec; 279 media_codec_info* videoCodec; 280 media_file_format* fileFormat; 281 fWin->GetSelectedFormatInfo(&fileFormat, &audioCodec, 282 &videoCodec); 283 BDirectory directory = fWin->OutputDirectory(); 284 fWin->Unlock(); 285 outEntry = _CreateOutputFile(directory, &inRef, fileFormat); 286 287 // display file name 288 289 outEntry.GetPath(&path); 290 name.SetTo(path.Leaf()); 291 292 if (outEntry.InitCheck() == B_OK) { 293 entry_ref outRef; 294 outEntry.GetRef(&outRef); 295 outFile = new BMediaFile(&outRef, fileFormat); 296 297 BString tmp( 298 B_TRANSLATE("Output file '%filename' created")); 299 tmp.ReplaceAll("%filename", name); 300 name = tmp; 301 } else { 302 BString tmp(B_TRANSLATE("Error creating '%filename'")); 303 tmp.ReplaceAll("%filename", name); 304 name = tmp; 305 } 306 307 if (fWin->Lock()) { 308 fWin->SetFileMessage(name.String()); 309 fWin->Unlock(); 310 } 311 312 if (outFile != NULL) { 313 r = _ConvertFile(inFile, outFile, audioCodec, videoCodec, 314 audioQuality, videoQuality, start, end); 315 316 // set mime 317 update_mime_info(path.Path(), false, false, false); 318 319 fWin->Lock(); 320 if (r == B_OK) { 321 fWin->RemoveSourceFile(srcIndex); 322 } else { 323 srcIndex++; 324 BString error( 325 B_TRANSLATE("Error converting '%filename'")); 326 error.ReplaceAll("%filename", inRef.name); 327 fWin->SetStatusMessage(error.String()); 328 } 329 fWin->Unlock(); 330 } 331 } else { 332 srcIndex++; 333 BString error( 334 B_TRANSLATE("Error converting '%filename'")); 335 error.ReplaceAll("%filename", inRef.name); 336 fWin->SetStatusMessage(error.String()); 337 fWin->Unlock(); 338 break; 339 } 340 } else { 341 break; 342 } 343 } 344 345 BMessenger(this).SendMessage(CONVERSION_DONE_MESSAGE); 346 } 347 348 349 // #pragma mark - 350 351 352 status_t 353 MediaConverterApp::_ConvertFile(BMediaFile* inFile, BMediaFile* outFile, 354 media_codec_info* audioCodec, media_codec_info* videoCodec, 355 int32 audioQuality, int32 videoQuality, 356 bigtime_t startDuration, bigtime_t endDuration) 357 { 358 BMediaTrack* inVidTrack = NULL; 359 BMediaTrack* inAudTrack = NULL; 360 BMediaTrack* outVidTrack = NULL; 361 BMediaTrack* outAudTrack = NULL; 362 363 media_format inFormat; 364 media_format outAudFormat; 365 media_format outVidFormat; 366 367 media_raw_audio_format* raf = NULL; 368 media_raw_video_format* rvf = NULL; 369 370 int32 width = -1; 371 int32 height = -1; 372 373 uint8* videoBuffer = NULL; 374 uint8* audioBuffer = NULL; 375 376 // gather the necessary format information and construct output tracks 377 int64 videoFrameCount = 0; 378 int64 audioFrameCount = 0; 379 380 status_t ret = B_OK; 381 bool multiTrack = false; 382 383 int32 tracks = inFile->CountTracks(); 384 for (int32 i = 0; i < tracks && (!outAudTrack || !outVidTrack); i++) { 385 BMediaTrack* inTrack = inFile->TrackAt(i); 386 inFormat.Clear(); 387 inTrack->EncodedFormat(&inFormat); 388 if (inFormat.IsAudio() && (audioCodec != NULL)) { 389 if (outAudTrack != NULL) { 390 multiTrack = true; 391 continue; 392 } 393 inAudTrack = inTrack; 394 outAudFormat.Clear(); 395 outAudFormat.type = B_MEDIA_RAW_AUDIO; 396 raf = &(outAudFormat.u.raw_audio); 397 inTrack->DecodedFormat(&outAudFormat); 398 399 audioBuffer = new uint8[raf->buffer_size]; 400 // audioFrameSize = (raf->format & media_raw_audio_format::B_AUDIO_SIZE_MASK) 401 // audioFrameSize = (raf->format & 0xf) * raf->channel_count; 402 outAudTrack = outFile->CreateTrack(&outAudFormat, audioCodec); 403 404 // Negociate the format with the inTrack again in case the codec 405 // made some changes to it... 406 inTrack->DecodedFormat(&outAudFormat); 407 408 if (outAudTrack != NULL) { 409 if (outAudTrack->SetQuality(audioQuality / 100.0f) != B_OK 410 && fWin->Lock()) { 411 fWin->SetAudioQualityLabel( 412 B_TRANSLATE("Audio quality not supported")); 413 fWin->Unlock(); 414 } 415 } else { 416 SetStatusMessage(B_TRANSLATE("Error creating track.")); 417 } 418 419 } else if (inFormat.IsVideo() && (videoCodec != NULL)) { 420 if (outVidTrack != NULL) { 421 multiTrack = true; 422 continue; 423 } 424 inVidTrack = inTrack; 425 width = (int32)inFormat.Width(); 426 height = (int32)inFormat.Height(); 427 428 // construct desired decoded video format 429 outVidFormat.Clear(); 430 outVidFormat.type = B_MEDIA_RAW_VIDEO; 431 rvf = &(outVidFormat.u.raw_video); 432 rvf->last_active = (uint32)(height - 1); 433 rvf->orientation = B_VIDEO_TOP_LEFT_RIGHT; 434 rvf->display.format = B_RGB32; 435 rvf->display.bytes_per_row = 4 * width; 436 rvf->display.line_width = width; 437 rvf->display.line_count = height; 438 439 inVidTrack->DecodedFormat(&outVidFormat); 440 441 if (rvf->display.format == B_RGBA32) { 442 printf("fixing color space (B_RGBA32 -> B_RGB32)"); 443 rvf->display.format = B_RGB32; 444 } 445 // Transfer the display aspect ratio. 446 if (inFormat.type == B_MEDIA_ENCODED_VIDEO) { 447 rvf->pixel_width_aspect 448 = inFormat.u.encoded_video.output.pixel_width_aspect; 449 rvf->pixel_height_aspect 450 = inFormat.u.encoded_video.output.pixel_height_aspect; 451 } else { 452 rvf->pixel_width_aspect 453 = inFormat.u.raw_video.pixel_width_aspect; 454 rvf->pixel_height_aspect 455 = inFormat.u.raw_video.pixel_height_aspect; 456 } 457 458 videoBuffer = new (std::nothrow) uint8[height 459 * rvf->display.bytes_per_row]; 460 outVidTrack = outFile->CreateTrack(&outVidFormat, videoCodec); 461 462 if (outVidTrack != NULL) { 463 // DLM Added to use 3ivx Parameter View 464 const char* videoQualitySupport = NULL; 465 BView* encoderView = outVidTrack->GetParameterView(); 466 if (encoderView) { 467 MediaEncoderWindow* encoderWin 468 = new MediaEncoderWindow(BRect(50, 50, 520, 555), 469 encoderView); 470 encoderWin->Go(); 471 // blocks until the window is quit 472 473 // The quality setting is ignored by the 3ivx encoder if the 474 // view was displayed, but this method is the trigger to 475 // read all the parameter settings 476 outVidTrack->SetQuality(videoQuality / 100.0f); 477 478 // We can now delete the encoderView created for us by the 479 // encoder 480 delete encoderView; 481 encoderView = NULL; 482 483 videoQualitySupport 484 = B_TRANSLATE("Video using parameters form settings"); 485 } else if (outVidTrack->SetQuality(videoQuality / 100.0f) 486 >= B_OK) { 487 videoQualitySupport 488 = B_TRANSLATE("Video quality not supported"); 489 } 490 491 if (videoQualitySupport && fWin->Lock()) { 492 fWin->SetVideoQualityLabel(videoQualitySupport); 493 fWin->Unlock(); 494 } 495 } else { 496 SetStatusMessage(B_TRANSLATE("Error creating video.")); 497 } 498 } else { 499 // didn't do anything with the track 500 SetStatusMessage( 501 B_TRANSLATE("Input file not recognized as Audio or Video")); 502 inFile->ReleaseTrack(inTrack); 503 } 504 } 505 506 if (!outVidTrack && !outAudTrack) { 507 printf("MediaConverterApp::_ConvertFile() - no tracks found!\n"); 508 ret = B_ERROR; 509 } 510 511 if (multiTrack) { 512 BAlert* alert = new BAlert(B_TRANSLATE("Multi-track file detected"), 513 B_TRANSLATE("The file has multiple audio or video tracks, only the first one of each will " 514 "be converted."), 515 B_TRANSLATE("Understood"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 516 alert->Go(); 517 } 518 519 if (fCancel) { 520 // don't have any video or audio tracks here, or cancelled 521 printf("MediaConverterApp::_ConvertFile()" 522 " - user canceled before transcoding\n"); 523 ret = B_CANCELED; 524 } 525 526 if (ret < B_OK) { 527 delete[] audioBuffer; 528 delete[] videoBuffer; 529 delete outFile; 530 return ret; 531 } 532 533 outFile->CommitHeader(); 534 // this is where you would call outFile->AddCopyright(...) 535 536 int64 framesRead; 537 media_header mh; 538 int32 lastPercent, currPercent; 539 float completePercent; 540 BString status; 541 542 int64 start; 543 int64 end; 544 int32 stat = 0; 545 546 // read video from source and write to destination, if necessary 547 if (outVidTrack != NULL) { 548 lastPercent = -1; 549 videoFrameCount = inVidTrack->CountFrames(); 550 if (endDuration == 0 || endDuration < startDuration) { 551 start = 0; 552 end = videoFrameCount; 553 } else { 554 inVidTrack->SeekToTime(&endDuration, stat); 555 end = inVidTrack->CurrentFrame(); 556 inVidTrack->SeekToTime(&startDuration, stat); 557 start = inVidTrack->CurrentFrame(); 558 if (end > videoFrameCount) 559 end = videoFrameCount; 560 if (start > end) 561 start = 0; 562 } 563 564 framesRead = 0; 565 for (int64 i = start; (i < end) && !fCancel; i += framesRead) { 566 if ((ret = inVidTrack->ReadFrames(videoBuffer, &framesRead, 567 &mh)) != B_OK) { 568 fprintf(stderr, "Error reading video frame %" B_PRId64 ": %s\n", 569 i, strerror(ret)); 570 snprintf(status.LockBuffer(128), 128, 571 B_TRANSLATE("Error read video frame %" B_PRId64), i); 572 status.UnlockBuffer(); 573 SetStatusMessage(status.String()); 574 575 break; 576 } 577 578 if ((ret = outVidTrack->WriteFrames(videoBuffer, framesRead, 579 mh.u.encoded_video.field_flags)) != B_OK) { 580 fprintf(stderr, "Error writing video frame %" B_PRId64 ": %s\n", 581 i, strerror(ret)); 582 snprintf(status.LockBuffer(128), 128, 583 B_TRANSLATE("Error writing video frame %" B_PRId64), i); 584 status.UnlockBuffer(); 585 SetStatusMessage(status.String()); 586 587 break; 588 } 589 completePercent = (float)(i - start) / (float)(end - start) * 100; 590 currPercent = (int32)completePercent; 591 if (currPercent > lastPercent) { 592 lastPercent = currPercent; 593 snprintf(status.LockBuffer(128), 128, 594 B_TRANSLATE("Writing video track: %" B_PRId32 "%% complete"), 595 currPercent); 596 status.UnlockBuffer(); 597 SetStatusMessage(status.String()); 598 599 } 600 } 601 outVidTrack->Flush(); 602 inFile->ReleaseTrack(inVidTrack); 603 } 604 605 // read audio from source and write to destination, if necessary 606 if (outAudTrack != NULL) { 607 lastPercent = -1; 608 609 audioFrameCount = inAudTrack->CountFrames(); 610 611 if (endDuration == 0 || endDuration < startDuration) { 612 start = 0; 613 end = audioFrameCount; 614 } else { 615 inAudTrack->SeekToTime(&endDuration, stat); 616 end = inAudTrack->CurrentFrame(); 617 inAudTrack->SeekToTime(&startDuration, stat); 618 start = inAudTrack->CurrentFrame(); 619 if (end > audioFrameCount) 620 end = audioFrameCount; 621 if (start > end) 622 start = 0; 623 } 624 625 for (int64 i = start; (i < end) && !fCancel; i += framesRead) { 626 if ((ret = inAudTrack->ReadFrames(audioBuffer, &framesRead, 627 &mh)) != B_OK) { 628 fprintf(stderr, "Error reading audio frames: %s\n", strerror(ret)); 629 snprintf(status.LockBuffer(128), 128, 630 B_TRANSLATE("Error read audio frame %" B_PRId64), i); 631 status.UnlockBuffer(); 632 SetStatusMessage(status.String()); 633 634 break; 635 } 636 637 if ((ret = outAudTrack->WriteFrames(audioBuffer, 638 framesRead)) != B_OK) { 639 fprintf(stderr, "Error writing audio frames: %s\n", strerror(ret)); 640 snprintf(status.LockBuffer(128), 128, 641 B_TRANSLATE("Error writing audio frame %" B_PRId64), i); 642 status.UnlockBuffer(); 643 SetStatusMessage(status.String()); 644 645 break; 646 } 647 completePercent = (float)(i - start) / (float)(end - start) * 100; 648 currPercent = (int32)completePercent; 649 if (currPercent > lastPercent) { 650 lastPercent = currPercent; 651 snprintf(status.LockBuffer(128), 128, 652 B_TRANSLATE("Writing audio track: %" B_PRId32 "%% complete"), 653 currPercent); 654 status.UnlockBuffer(); 655 SetStatusMessage(status.String()); 656 } 657 } 658 outAudTrack->Flush(); 659 inFile->ReleaseTrack(inAudTrack); 660 661 } 662 663 outFile->CloseFile(); 664 delete outFile; 665 666 delete[] videoBuffer; 667 delete[] audioBuffer; 668 669 return ret; 670 } 671 672 673 // #pragma mark - 674 675 676 int 677 main(int, char **) 678 { 679 MediaConverterApp app; 680 app.Run(); 681 682 return 0; 683 } 684