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 using std::nothrow; 27 28 const char APP_SIGNATURE[] = "application/x-vnd.Haiku-MediaConverter"; 29 30 31 MediaConverterApp::MediaConverterApp() 32 : BApplication(APP_SIGNATURE) 33 , fWin(NULL) 34 , fConvertThreadID(-1) 35 , fConverting(false) 36 , fCancel(false) 37 { 38 // TODO: implement settings for window pos 39 fWin = new MediaConverterWindow(BRect(50, 50, 520, 555)); 40 } 41 42 43 MediaConverterApp::~MediaConverterApp() 44 { 45 if (fConvertThreadID >= 0) { 46 fCancel = true; 47 status_t exitValue; 48 wait_for_thread(fConvertThreadID, &exitValue); 49 } 50 } 51 52 53 // #pragma mark - 54 55 56 void 57 MediaConverterApp::MessageReceived(BMessage *msg) 58 { 59 switch (msg->what) { 60 case FILE_LIST_CHANGE_MESSAGE: 61 if (fWin->Lock()) { 62 bool enable = fWin->CountSourceFiles() > 0; 63 fWin->SetEnabled(enable, enable); 64 fWin->Unlock(); 65 } 66 break; 67 68 case START_CONVERSION_MESSAGE: 69 if (!fConverting) 70 StartConverting(); 71 break; 72 73 case CANCEL_CONVERSION_MESSAGE: 74 fCancel = true; 75 break; 76 77 case CONVERSION_DONE_MESSAGE: 78 fCancel = false; 79 fConverting = false; 80 DetachCurrentMessage(); 81 BMessenger(fWin).SendMessage(msg); 82 break; 83 84 default: 85 BApplication::MessageReceived(msg); 86 } 87 } 88 89 90 void 91 MediaConverterApp::ReadyToRun() 92 { 93 fWin->Show(); 94 fWin->PostMessage(INIT_FORMAT_MENUS); 95 } 96 97 void 98 MediaConverterApp::RefsReceived(BMessage *msg) 99 { 100 entry_ref ref; 101 int32 i = 0; 102 BMediaFile *f; 103 BString errorFiles; 104 int32 errors = 0; 105 106 // from Open dialog or drag & drop 107 108 while (msg->FindRef("refs", i++, &ref) == B_OK) { 109 f = new BMediaFile(&ref/*, B_MEDIA_FILE_NO_READ_AHEAD*/); 110 if (f->InitCheck() != B_OK) { 111 errorFiles << ref.name << "\n"; 112 errors++; 113 delete f; 114 continue; 115 } 116 if (fWin->Lock()) { 117 fWin->AddSourceFile(f, ref); 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->pixel_width_aspect = 1; 388 rvf->pixel_height_aspect = 1; 389 rvf->display.format = B_RGB32; 390 rvf->display.bytes_per_row = 4 * width; 391 rvf->display.line_width = width; 392 rvf->display.line_count = height; 393 394 inVidTrack->DecodedFormat(&outVidFormat); 395 396 if (rvf->display.format == B_RGBA32) { 397 printf("fixing color space (B_RGBA32 -> B_RGB32)"); 398 rvf->display.format = B_RGB32; 399 } 400 401 videoBuffer = new (nothrow) uint8[height * rvf->display.bytes_per_row]; 402 outVidTrack = outFile->CreateTrack(&outVidFormat, videoCodec); 403 404 if (outVidTrack != NULL) { 405 // DLM Added to use 3ivx Parameter View 406 const char* videoQualitySupport = NULL; 407 BView* encoderView = outVidTrack->GetParameterView(); 408 if (encoderView) { 409 MediaEncoderWindow* encoderWin 410 = new MediaEncoderWindow(BRect(50, 50, 520, 555), encoderView); 411 encoderWin->Go(); 412 // blocks until the window is quit 413 414 // The quality setting is ignored by the 3ivx encoder if the 415 // view was displayed, but this method is the trigger to read 416 // all the parameter settings 417 outVidTrack->SetQuality(videoQuality / 100.0f); 418 419 // We can now delete the encoderView created for us by the encoder 420 delete encoderView; 421 encoderView = NULL; 422 423 videoQualitySupport = VIDEO_PARAMFORM_STRING; 424 } else { 425 if (outVidTrack->SetQuality(videoQuality / 100.0f) >= B_OK) 426 videoQualitySupport = VIDEO_SUPPORT_STRING; 427 } 428 if (videoQualitySupport && fWin->Lock()) { 429 fWin->SetVideoQualityLabel(videoQualitySupport); 430 fWin->Unlock(); 431 } 432 } 433 } else { 434 // didn't do anything with the track 435 inFile->ReleaseTrack(inTrack); 436 } 437 } 438 439 if (!outVidTrack && !outAudTrack) { 440 printf("MediaConverterApp::_ConvertFile() - no tracks found!\n"); 441 ret = B_ERROR; 442 } 443 444 if (fCancel) { 445 // don't have any video or audio tracks here, or cancelled 446 printf("MediaConverterApp::_ConvertFile() - user canceld before transcoding\n"); 447 ret = B_CANCELED; 448 } 449 450 if (ret < B_OK) { 451 delete[] audioBuffer; 452 delete[] videoBuffer; 453 delete outFile; 454 return ret; 455 } 456 457 outFile->CommitHeader(); 458 // this is where you would call outFile->AddCopyright(...) 459 460 int64 framesRead; 461 media_header mh; 462 int32 lastPercent, currPercent; 463 float completePercent; 464 BString status; 465 466 int64 start; 467 int64 end; 468 int32 stat = 0; 469 470 // read video from source and write to destination, if necessary 471 if (outVidTrack != NULL) { 472 lastPercent = -1; 473 videoFrameCount = inVidTrack->CountFrames(); 474 if (endDuration == 0 || endDuration < startDuration) { 475 start = 0; 476 end = videoFrameCount; 477 } else { 478 inVidTrack->SeekToTime(&endDuration, stat); 479 end = inVidTrack->CurrentFrame(); 480 inVidTrack->SeekToTime(&startDuration, stat); 481 start = inVidTrack->CurrentFrame(); 482 if (end > videoFrameCount) 483 end = videoFrameCount; 484 if (start > end) 485 start = 0; 486 } 487 488 framesRead = 0; 489 for (int64 i = start; (i <= end) && !fCancel; i += framesRead) { 490 if ((ret = inVidTrack->ReadFrames(videoBuffer, &framesRead, 491 &mh)) != B_OK) { 492 fprintf(stderr, "Error reading video frame %Ld: %s\n", i, 493 strerror(ret)); 494 status.SetTo(ERROR_READ_VIDEO_STRING); 495 status << i; 496 SetStatusMessage(status.String()); 497 498 break; 499 } 500 //printf("writing frame %lld\n", i); 501 if ((ret = outVidTrack->WriteFrames(videoBuffer, framesRead, 502 mh.u.encoded_video.field_flags)) != B_OK) { 503 fprintf(stderr, "Error writing video frame %Ld: %s\n", i, 504 strerror(ret)); 505 status.SetTo(ERROR_WRITE_VIDEO_STRING); 506 status << i; 507 SetStatusMessage(status.String()); 508 break; 509 } 510 completePercent = (float)(i - start) / (float)(end - start) * 100; 511 currPercent = (int16)floor(completePercent); 512 if (currPercent > lastPercent) { 513 lastPercent = currPercent; 514 status.SetTo(WRITE_VIDEO_STRING); 515 status.Append(" "); 516 status << currPercent << "% " << COMPLETE_STRING; 517 SetStatusMessage(status.String()); 518 519 } 520 } 521 outVidTrack->Flush(); 522 inFile->ReleaseTrack(inVidTrack); 523 } 524 525 // read audio from source and write to destination, if necessary 526 if (outAudTrack != NULL) { 527 lastPercent = -1; 528 529 audioFrameCount = inAudTrack->CountFrames(); 530 531 if (endDuration == 0 || endDuration < startDuration) { 532 start = 0; 533 end = audioFrameCount; 534 } else { 535 inAudTrack->SeekToTime(&endDuration, stat); 536 end = inAudTrack->CurrentFrame(); 537 inAudTrack->SeekToTime(&startDuration, stat); 538 start = inAudTrack->CurrentFrame(); 539 if (end > audioFrameCount) 540 end = audioFrameCount; 541 if (start > end) 542 start = 0; 543 } 544 545 for (int64 i = start; (i <= end) && !fCancel; i += framesRead) { 546 if ((ret = inAudTrack->ReadFrames(audioBuffer, &framesRead, 547 &mh)) != B_OK) { 548 fprintf(stderr, "Error reading audio frames: %s\n", strerror(ret)); 549 status.SetTo(ERROR_READ_AUDIO_STRING); 550 status << i; 551 SetStatusMessage(status.String()); 552 break; 553 } 554 //printf("writing audio frames %lld\n", i); 555 if ((ret = outAudTrack->WriteFrames(audioBuffer, 556 framesRead)) != B_OK) { 557 fprintf(stderr, "Error writing audio frames: %s\n", 558 strerror(ret)); 559 status.SetTo(ERROR_WRITE_AUDIO_STRING); 560 status << i; 561 SetStatusMessage(status.String()); 562 break; 563 } 564 completePercent = (float)(i - start) / (float)(end - start) * 100; 565 currPercent = (int16)floor(completePercent); 566 if (currPercent > lastPercent) { 567 lastPercent = currPercent; 568 status.SetTo(WRITE_AUDIO_STRING); 569 status.Append(" "); 570 status << currPercent << "% " << COMPLETE_STRING; 571 SetStatusMessage(status.String()); 572 } 573 } 574 outAudTrack->Flush(); 575 inFile->ReleaseTrack(inAudTrack); 576 577 } 578 579 outFile->CloseFile(); 580 delete outFile; 581 582 delete[] videoBuffer; 583 delete[] audioBuffer; 584 585 return ret; 586 } 587 588 589 // #pragma mark - 590 591 592 int 593 main(int, char **) 594 { 595 MediaConverterApp app; 596 app.Run(); 597 598 return 0; 599 } 600