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