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