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