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