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