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 382 int32 tracks = inFile->CountTracks(); 383 for (int32 i = 0; i < tracks && (!outAudTrack || !outVidTrack); i++) { 384 BMediaTrack* inTrack = inFile->TrackAt(i); 385 inFormat.Clear(); 386 inTrack->EncodedFormat(&inFormat); 387 if (inFormat.IsAudio() && (audioCodec != NULL)) { 388 inAudTrack = inTrack; 389 outAudFormat.Clear(); 390 outAudFormat.type = B_MEDIA_RAW_AUDIO; 391 raf = &(outAudFormat.u.raw_audio); 392 inTrack->DecodedFormat(&outAudFormat); 393 394 audioBuffer = new uint8[raf->buffer_size]; 395 // audioFrameSize = (raf->format & media_raw_audio_format::B_AUDIO_SIZE_MASK) 396 // audioFrameSize = (raf->format & 0xf) * raf->channel_count; 397 outAudTrack = outFile->CreateTrack(&outAudFormat, audioCodec); 398 399 // Negociate the format with the inTrack again in case the codec 400 // made some changes to it... 401 inTrack->DecodedFormat(&outAudFormat); 402 403 if (outAudTrack != NULL) { 404 if (outAudTrack->SetQuality(audioQuality / 100.0f) != B_OK 405 && fWin->Lock()) { 406 fWin->SetAudioQualityLabel( 407 B_TRANSLATE("Audio quality not supported")); 408 fWin->Unlock(); 409 } 410 } else { 411 SetStatusMessage(B_TRANSLATE("Error creating track.")); 412 } 413 414 } else if (inFormat.IsVideo() && (videoCodec != NULL)) { 415 inVidTrack = inTrack; 416 width = (int32)inFormat.Width(); 417 height = (int32)inFormat.Height(); 418 419 // construct desired decoded video format 420 outVidFormat.Clear(); 421 outVidFormat.type = B_MEDIA_RAW_VIDEO; 422 rvf = &(outVidFormat.u.raw_video); 423 rvf->last_active = (uint32)(height - 1); 424 rvf->orientation = B_VIDEO_TOP_LEFT_RIGHT; 425 rvf->display.format = B_RGB32; 426 rvf->display.bytes_per_row = 4 * width; 427 rvf->display.line_width = width; 428 rvf->display.line_count = height; 429 430 inVidTrack->DecodedFormat(&outVidFormat); 431 432 if (rvf->display.format == B_RGBA32) { 433 printf("fixing color space (B_RGBA32 -> B_RGB32)"); 434 rvf->display.format = B_RGB32; 435 } 436 // Transfer the display aspect ratio. 437 if (inFormat.type == B_MEDIA_ENCODED_VIDEO) { 438 rvf->pixel_width_aspect 439 = inFormat.u.encoded_video.output.pixel_width_aspect; 440 rvf->pixel_height_aspect 441 = inFormat.u.encoded_video.output.pixel_height_aspect; 442 } else { 443 rvf->pixel_width_aspect 444 = inFormat.u.raw_video.pixel_width_aspect; 445 rvf->pixel_height_aspect 446 = inFormat.u.raw_video.pixel_height_aspect; 447 } 448 449 videoBuffer = new (std::nothrow) uint8[height 450 * rvf->display.bytes_per_row]; 451 outVidTrack = outFile->CreateTrack(&outVidFormat, videoCodec); 452 453 if (outVidTrack != NULL) { 454 // DLM Added to use 3ivx Parameter View 455 const char* videoQualitySupport = NULL; 456 BView* encoderView = outVidTrack->GetParameterView(); 457 if (encoderView) { 458 MediaEncoderWindow* encoderWin 459 = new MediaEncoderWindow(BRect(50, 50, 520, 555), 460 encoderView); 461 encoderWin->Go(); 462 // blocks until the window is quit 463 464 // The quality setting is ignored by the 3ivx encoder if the 465 // view was displayed, but this method is the trigger to 466 // read all the parameter settings 467 outVidTrack->SetQuality(videoQuality / 100.0f); 468 469 // We can now delete the encoderView created for us by the 470 // encoder 471 delete encoderView; 472 encoderView = NULL; 473 474 videoQualitySupport 475 = B_TRANSLATE("Video using parameters form settings"); 476 } else if (outVidTrack->SetQuality(videoQuality / 100.0f) 477 >= B_OK) { 478 videoQualitySupport 479 = B_TRANSLATE("Video quality not supported"); 480 } 481 482 if (videoQualitySupport && fWin->Lock()) { 483 fWin->SetVideoQualityLabel(videoQualitySupport); 484 fWin->Unlock(); 485 } 486 } else { 487 SetStatusMessage(B_TRANSLATE("Error creating video.")); 488 } 489 } else { 490 // didn't do anything with the track 491 SetStatusMessage( 492 B_TRANSLATE("Input file not recognized as Audio or Video")); 493 inFile->ReleaseTrack(inTrack); 494 } 495 } 496 497 if (!outVidTrack && !outAudTrack) { 498 printf("MediaConverterApp::_ConvertFile() - no tracks found!\n"); 499 ret = B_ERROR; 500 } 501 502 if (fCancel) { 503 // don't have any video or audio tracks here, or cancelled 504 printf("MediaConverterApp::_ConvertFile()" 505 " - user canceled before transcoding\n"); 506 ret = B_CANCELED; 507 } 508 509 if (ret < B_OK) { 510 delete[] audioBuffer; 511 delete[] videoBuffer; 512 delete outFile; 513 return ret; 514 } 515 516 outFile->CommitHeader(); 517 // this is where you would call outFile->AddCopyright(...) 518 519 int64 framesRead; 520 media_header mh; 521 int32 lastPercent, currPercent; 522 float completePercent; 523 BString status; 524 525 int64 start; 526 int64 end; 527 int32 stat = 0; 528 529 // read video from source and write to destination, if necessary 530 if (outVidTrack != NULL) { 531 lastPercent = -1; 532 videoFrameCount = inVidTrack->CountFrames(); 533 if (endDuration == 0 || endDuration < startDuration) { 534 start = 0; 535 end = videoFrameCount; 536 } else { 537 inVidTrack->SeekToTime(&endDuration, stat); 538 end = inVidTrack->CurrentFrame(); 539 inVidTrack->SeekToTime(&startDuration, stat); 540 start = inVidTrack->CurrentFrame(); 541 if (end > videoFrameCount) 542 end = videoFrameCount; 543 if (start > end) 544 start = 0; 545 } 546 547 framesRead = 0; 548 for (int64 i = start; (i < end) && !fCancel; i += framesRead) { 549 if ((ret = inVidTrack->ReadFrames(videoBuffer, &framesRead, 550 &mh)) != B_OK) { 551 fprintf(stderr, "Error reading video frame %" B_PRId64 ": %s\n", 552 i, strerror(ret)); 553 snprintf(status.LockBuffer(128), 128, 554 B_TRANSLATE("Error read video frame %" B_PRId64), i); 555 status.UnlockBuffer(); 556 SetStatusMessage(status.String()); 557 558 break; 559 } 560 561 if ((ret = outVidTrack->WriteFrames(videoBuffer, framesRead, 562 mh.u.encoded_video.field_flags)) != B_OK) { 563 fprintf(stderr, "Error writing video frame %" B_PRId64 ": %s\n", 564 i, strerror(ret)); 565 snprintf(status.LockBuffer(128), 128, 566 B_TRANSLATE("Error writing video frame %" B_PRId64), i); 567 status.UnlockBuffer(); 568 SetStatusMessage(status.String()); 569 570 break; 571 } 572 completePercent = (float)(i - start) / (float)(end - start) * 100; 573 currPercent = (int32)completePercent; 574 if (currPercent > lastPercent) { 575 lastPercent = currPercent; 576 snprintf(status.LockBuffer(128), 128, 577 B_TRANSLATE("Writing video track: %" B_PRId32 "%% complete"), 578 currPercent); 579 status.UnlockBuffer(); 580 SetStatusMessage(status.String()); 581 582 } 583 } 584 outVidTrack->Flush(); 585 inFile->ReleaseTrack(inVidTrack); 586 } 587 588 // read audio from source and write to destination, if necessary 589 if (outAudTrack != NULL) { 590 lastPercent = -1; 591 592 audioFrameCount = inAudTrack->CountFrames(); 593 594 if (endDuration == 0 || endDuration < startDuration) { 595 start = 0; 596 end = audioFrameCount; 597 } else { 598 inAudTrack->SeekToTime(&endDuration, stat); 599 end = inAudTrack->CurrentFrame(); 600 inAudTrack->SeekToTime(&startDuration, stat); 601 start = inAudTrack->CurrentFrame(); 602 if (end > audioFrameCount) 603 end = audioFrameCount; 604 if (start > end) 605 start = 0; 606 } 607 608 for (int64 i = start; (i < end) && !fCancel; i += framesRead) { 609 if ((ret = inAudTrack->ReadFrames(audioBuffer, &framesRead, 610 &mh)) != B_OK) { 611 fprintf(stderr, "Error reading audio frames: %s\n", strerror(ret)); 612 snprintf(status.LockBuffer(128), 128, 613 B_TRANSLATE("Error read audio frame %" B_PRId64), i); 614 status.UnlockBuffer(); 615 SetStatusMessage(status.String()); 616 617 break; 618 } 619 620 if ((ret = outAudTrack->WriteFrames(audioBuffer, 621 framesRead)) != B_OK) { 622 fprintf(stderr, "Error writing audio frames: %s\n", strerror(ret)); 623 snprintf(status.LockBuffer(128), 128, 624 B_TRANSLATE("Error writing audio frame %" B_PRId64), i); 625 status.UnlockBuffer(); 626 SetStatusMessage(status.String()); 627 628 break; 629 } 630 completePercent = (float)(i - start) / (float)(end - start) * 100; 631 currPercent = (int32)completePercent; 632 if (currPercent > lastPercent) { 633 lastPercent = currPercent; 634 snprintf(status.LockBuffer(128), 128, 635 B_TRANSLATE("Writing audio track: %" B_PRId32 "%% complete"), 636 currPercent); 637 status.UnlockBuffer(); 638 SetStatusMessage(status.String()); 639 } 640 } 641 outAudTrack->Flush(); 642 inFile->ReleaseTrack(inAudTrack); 643 644 } 645 646 outFile->CloseFile(); 647 delete outFile; 648 649 delete[] videoBuffer; 650 delete[] audioBuffer; 651 652 return ret; 653 } 654 655 656 // #pragma mark - 657 658 659 int 660 main(int, char **) 661 { 662 MediaConverterApp app; 663 app.Run(); 664 665 return 0; 666 } 667