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