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 inEntry(ref); 208 BEntry outEntry; 209 210 if (inEntry.InitCheck() == B_OK) { 211 // ensure that output name is unique 212 int32 len = name.Length(); 213 int32 i = 1; 214 while (directory.Contains(name.String())) { 215 name.Truncate(len); 216 name << " " << i; 217 i++; 218 } 219 outEntry.SetTo(&directory, name.String()); 220 } 221 222 return outEntry; 223 } 224 225 226 int32 227 MediaConverterApp::_RunConvertEntry(void* castToMediaConverterApp) 228 { 229 MediaConverterApp* app = (MediaConverterApp*)castToMediaConverterApp; 230 app->_RunConvert(); 231 return 0; 232 } 233 234 235 void 236 MediaConverterApp::_RunConvert() 237 { 238 bigtime_t start = 0; 239 bigtime_t end = 0; 240 int32 audioQuality = 75; 241 int32 videoQuality = 75; 242 243 if (fWin->Lock()) { 244 char *a; 245 start = strtoimax(fWin->StartDuration(), &a, 0) * 1000; 246 end = strtoimax(fWin->EndDuration(), &a, 0) * 1000; 247 audioQuality = fWin->AudioQuality(); 248 videoQuality = fWin->VideoQuality(); 249 fWin->Unlock(); 250 } 251 252 int32 srcIndex = 0; 253 254 BMediaFile *inFile(NULL), *outFile(NULL); 255 BEntry outEntry; 256 entry_ref inRef; 257 entry_ref outRef; 258 BPath path; 259 BString name; 260 261 while (!fCancel) { 262 if (fWin->Lock()) { 263 status_t r = fWin->GetSourceFileAt(srcIndex, &inFile, &inRef); 264 if (r == B_OK) { 265 media_codec_info* audioCodec; 266 media_codec_info* videoCodec; 267 media_file_format* fileFormat; 268 fWin->GetSelectedFormatInfo(&fileFormat, &audioCodec, 269 &videoCodec); 270 BDirectory directory = fWin->OutputDirectory(); 271 fWin->Unlock(); 272 outEntry = _CreateOutputFile(directory, &inRef, fileFormat); 273 274 // display file name 275 276 outEntry.GetPath(&path); 277 name.SetTo(path.Leaf()); 278 279 if (outEntry.InitCheck() == B_OK) { 280 entry_ref outRef; 281 outEntry.GetRef(&outRef); 282 outFile = new BMediaFile(&outRef, fileFormat); 283 284 BString tmp( 285 B_TRANSLATE("Output file '%filename' created")); 286 tmp.ReplaceAll("%filename", name); 287 name = tmp; 288 } else { 289 BString tmp(B_TRANSLATE("Error creating '%filename'")); 290 tmp.ReplaceAll("%filename", name); 291 name = tmp; 292 } 293 294 if (fWin->Lock()) { 295 fWin->SetFileMessage(name.String()); 296 fWin->Unlock(); 297 } 298 299 if (outFile != NULL) { 300 r = _ConvertFile(inFile, outFile, audioCodec, videoCodec, 301 audioQuality, videoQuality, start, end); 302 303 // set mime 304 update_mime_info(path.Path(), false, false, false); 305 306 fWin->Lock(); 307 if (r == B_OK) { 308 fWin->RemoveSourceFile(srcIndex); 309 } else { 310 srcIndex++; 311 BString error( 312 B_TRANSLATE("Error converting '%filename'")); 313 error.ReplaceAll("%filename", inRef.name); 314 fWin->SetStatusMessage(error.String()); 315 } 316 fWin->Unlock(); 317 } 318 319 320 } else { 321 fWin->Unlock(); 322 break; 323 } 324 } else { 325 break; 326 } 327 } 328 329 BMessenger(this).SendMessage(CONVERSION_DONE_MESSAGE); 330 } 331 332 333 // #pragma mark - 334 335 336 status_t 337 MediaConverterApp::_ConvertFile(BMediaFile* inFile, BMediaFile* outFile, 338 media_codec_info* audioCodec, media_codec_info* videoCodec, 339 int32 audioQuality, int32 videoQuality, 340 bigtime_t startDuration, bigtime_t endDuration) 341 { 342 BMediaTrack* inVidTrack = NULL; 343 BMediaTrack* inAudTrack = NULL; 344 BMediaTrack* outVidTrack = NULL; 345 BMediaTrack* outAudTrack = NULL; 346 347 media_format inFormat; 348 media_format outAudFormat; 349 media_format outVidFormat; 350 351 media_raw_audio_format* raf = NULL; 352 media_raw_video_format* rvf = NULL; 353 354 int32 width = -1; 355 int32 height = -1; 356 357 uint8* videoBuffer = NULL; 358 uint8* audioBuffer = NULL; 359 360 // gather the necessary format information and construct output tracks 361 int64 videoFrameCount = 0; 362 int64 audioFrameCount = 0; 363 364 status_t ret = B_OK; 365 366 int32 tracks = inFile->CountTracks(); 367 for (int32 i = 0; i < tracks && (!outAudTrack || !outVidTrack); i++) { 368 BMediaTrack* inTrack = inFile->TrackAt(i); 369 memset(&inFormat, 0, sizeof(media_format)); 370 inTrack->EncodedFormat(&inFormat); 371 if (inFormat.IsAudio() && (audioCodec != NULL)) { 372 inAudTrack = inTrack; 373 memset(&outAudFormat, 0, sizeof(media_format)); 374 outAudFormat.type = B_MEDIA_RAW_AUDIO; 375 raf = &(outAudFormat.u.raw_audio); 376 inTrack->DecodedFormat(&outAudFormat); 377 378 audioBuffer = new uint8[raf->buffer_size]; 379 // audioFrameSize = (raf->format & media_raw_audio_format::B_AUDIO_SIZE_MASK) 380 // audioFrameSize = (raf->format & 0xf) * raf->channel_count; 381 outAudTrack = outFile->CreateTrack(&outAudFormat, audioCodec); 382 383 // Negociate the format with the inTrack again in case the codec 384 // made some changes to it... 385 inTrack->DecodedFormat(&outAudFormat); 386 387 if (outAudTrack != NULL) { 388 if (outAudTrack->SetQuality(audioQuality / 100.0f) != B_OK 389 && fWin->Lock()) { 390 fWin->SetAudioQualityLabel( 391 B_TRANSLATE("Audio quality not supported")); 392 fWin->Unlock(); 393 } 394 } 395 396 } else if (inFormat.IsVideo() && (videoCodec != NULL)) { 397 inVidTrack = inTrack; 398 width = (int32)inFormat.Width(); 399 height = (int32)inFormat.Height(); 400 401 // construct desired decoded video format 402 memset(&outVidFormat, 0, sizeof(outVidFormat)); 403 outVidFormat.type = B_MEDIA_RAW_VIDEO; 404 rvf = &(outVidFormat.u.raw_video); 405 rvf->last_active = (uint32)(height - 1); 406 rvf->orientation = B_VIDEO_TOP_LEFT_RIGHT; 407 rvf->display.format = B_RGB32; 408 rvf->display.bytes_per_row = 4 * width; 409 rvf->display.line_width = width; 410 rvf->display.line_count = height; 411 412 inVidTrack->DecodedFormat(&outVidFormat); 413 414 if (rvf->display.format == B_RGBA32) { 415 printf("fixing color space (B_RGBA32 -> B_RGB32)"); 416 rvf->display.format = B_RGB32; 417 } 418 // Transfer the display aspect ratio. 419 if (inFormat.type == B_MEDIA_ENCODED_VIDEO) { 420 rvf->pixel_width_aspect 421 = inFormat.u.encoded_video.output.pixel_width_aspect; 422 rvf->pixel_height_aspect 423 = inFormat.u.encoded_video.output.pixel_height_aspect; 424 } else { 425 rvf->pixel_width_aspect 426 = inFormat.u.raw_video.pixel_width_aspect; 427 rvf->pixel_height_aspect 428 = inFormat.u.raw_video.pixel_height_aspect; 429 } 430 431 videoBuffer = new (std::nothrow) uint8[height 432 * rvf->display.bytes_per_row]; 433 outVidTrack = outFile->CreateTrack(&outVidFormat, videoCodec); 434 435 if (outVidTrack != NULL) { 436 // DLM Added to use 3ivx Parameter View 437 const char* videoQualitySupport = NULL; 438 BView* encoderView = outVidTrack->GetParameterView(); 439 if (encoderView) { 440 MediaEncoderWindow* encoderWin 441 = new MediaEncoderWindow(BRect(50, 50, 520, 555), 442 encoderView); 443 encoderWin->Go(); 444 // blocks until the window is quit 445 446 // The quality setting is ignored by the 3ivx encoder if the 447 // view was displayed, but this method is the trigger to 448 // read all the parameter settings 449 outVidTrack->SetQuality(videoQuality / 100.0f); 450 451 // We can now delete the encoderView created for us by the 452 // encoder 453 delete encoderView; 454 encoderView = NULL; 455 456 videoQualitySupport 457 = B_TRANSLATE("Video using parameters form settings"); 458 } else if (outVidTrack->SetQuality(videoQuality / 100.0f) 459 >= B_OK) { 460 videoQualitySupport 461 = B_TRANSLATE("Video quality not supported"); 462 } 463 464 if (videoQualitySupport && fWin->Lock()) { 465 fWin->SetVideoQualityLabel(videoQualitySupport); 466 fWin->Unlock(); 467 } 468 } 469 } else { 470 // didn't do anything with the track 471 inFile->ReleaseTrack(inTrack); 472 } 473 } 474 475 if (!outVidTrack && !outAudTrack) { 476 printf("MediaConverterApp::_ConvertFile() - no tracks found!\n"); 477 ret = B_ERROR; 478 } 479 480 if (fCancel) { 481 // don't have any video or audio tracks here, or cancelled 482 printf("MediaConverterApp::_ConvertFile()" 483 " - user canceled before transcoding\n"); 484 ret = B_CANCELED; 485 } 486 487 if (ret < B_OK) { 488 delete[] audioBuffer; 489 delete[] videoBuffer; 490 delete outFile; 491 return ret; 492 } 493 494 outFile->CommitHeader(); 495 // this is where you would call outFile->AddCopyright(...) 496 497 int64 framesRead; 498 media_header mh; 499 int32 lastPercent, currPercent; 500 float completePercent; 501 BString status; 502 503 int64 start; 504 int64 end; 505 int32 stat = 0; 506 507 // read video from source and write to destination, if necessary 508 if (outVidTrack != NULL) { 509 lastPercent = -1; 510 videoFrameCount = inVidTrack->CountFrames(); 511 if (endDuration == 0 || endDuration < startDuration) { 512 start = 0; 513 end = videoFrameCount; 514 } else { 515 inVidTrack->SeekToTime(&endDuration, stat); 516 end = inVidTrack->CurrentFrame(); 517 inVidTrack->SeekToTime(&startDuration, stat); 518 start = inVidTrack->CurrentFrame(); 519 if (end > videoFrameCount) 520 end = videoFrameCount; 521 if (start > end) 522 start = 0; 523 } 524 525 framesRead = 0; 526 for (int64 i = start; (i < end) && !fCancel; i += framesRead) { 527 if ((ret = inVidTrack->ReadFrames(videoBuffer, &framesRead, 528 &mh)) != B_OK) { 529 fprintf(stderr, "Error reading video frame %" B_PRId64 ": %s\n", 530 i, strerror(ret)); 531 snprintf(status.LockBuffer(128), 128, 532 B_TRANSLATE("Error read video frame %" B_PRId64), i); 533 status.UnlockBuffer(); 534 SetStatusMessage(status.String()); 535 536 break; 537 } 538 539 if ((ret = outVidTrack->WriteFrames(videoBuffer, framesRead, 540 mh.u.encoded_video.field_flags)) != B_OK) { 541 fprintf(stderr, "Error writing video frame %" B_PRId64 ": %s\n", 542 i, strerror(ret)); 543 snprintf(status.LockBuffer(128), 128, 544 B_TRANSLATE("Error writing video frame %" B_PRId64), i); 545 status.UnlockBuffer(); 546 SetStatusMessage(status.String()); 547 548 break; 549 } 550 completePercent = (float)(i - start) / (float)(end - start) * 100; 551 currPercent = (int32)completePercent; 552 if (currPercent > lastPercent) { 553 lastPercent = currPercent; 554 snprintf(status.LockBuffer(128), 128, 555 B_TRANSLATE("Writing video track: %" B_PRId32 "%% complete"), 556 currPercent); 557 status.UnlockBuffer(); 558 SetStatusMessage(status.String()); 559 560 } 561 } 562 outVidTrack->Flush(); 563 inFile->ReleaseTrack(inVidTrack); 564 } 565 566 // read audio from source and write to destination, if necessary 567 if (outAudTrack != NULL) { 568 lastPercent = -1; 569 570 audioFrameCount = inAudTrack->CountFrames(); 571 572 if (endDuration == 0 || endDuration < startDuration) { 573 start = 0; 574 end = audioFrameCount; 575 } else { 576 inAudTrack->SeekToTime(&endDuration, stat); 577 end = inAudTrack->CurrentFrame(); 578 inAudTrack->SeekToTime(&startDuration, stat); 579 start = inAudTrack->CurrentFrame(); 580 if (end > audioFrameCount) 581 end = audioFrameCount; 582 if (start > end) 583 start = 0; 584 } 585 586 for (int64 i = start; (i < end) && !fCancel; i += framesRead) { 587 if ((ret = inAudTrack->ReadFrames(audioBuffer, &framesRead, 588 &mh)) != B_OK) { 589 fprintf(stderr, "Error reading audio frames: %s\n", strerror(ret)); 590 snprintf(status.LockBuffer(128), 128, 591 B_TRANSLATE("Error read audio frame %" B_PRId64), i); 592 status.UnlockBuffer(); 593 SetStatusMessage(status.String()); 594 595 break; 596 } 597 598 if ((ret = outAudTrack->WriteFrames(audioBuffer, 599 framesRead)) != B_OK) { 600 fprintf(stderr, "Error writing audio frames: %s\n", strerror(ret)); 601 snprintf(status.LockBuffer(128), 128, 602 B_TRANSLATE("Error writing audio frame %" B_PRId64), i); 603 status.UnlockBuffer(); 604 SetStatusMessage(status.String()); 605 606 break; 607 } 608 completePercent = (float)(i - start) / (float)(end - start) * 100; 609 currPercent = (int32)completePercent; 610 if (currPercent > lastPercent) { 611 lastPercent = currPercent; 612 snprintf(status.LockBuffer(128), 128, 613 B_TRANSLATE("Writing audio track: %" B_PRId32 "%% complete"), 614 currPercent); 615 status.UnlockBuffer(); 616 SetStatusMessage(status.String()); 617 } 618 } 619 outAudTrack->Flush(); 620 inFile->ReleaseTrack(inAudTrack); 621 622 } 623 624 outFile->CloseFile(); 625 delete outFile; 626 627 delete[] videoBuffer; 628 delete[] audioBuffer; 629 630 return ret; 631 } 632 633 634 // #pragma mark - 635 636 637 int 638 main(int, char **) 639 { 640 MediaConverterApp app; 641 app.Run(); 642 643 return 0; 644 } 645