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