// Copyright 1999, Be Incorporated. All Rights Reserved. // Copyright 2000-2004, Jun Suzuki. All Rights Reserved. // Copyright 2007, Stephan Aßmus. All Rights Reserved. // This file may be used under the terms of the Be Sample Code License. #include "MediaConverterApp.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "MediaConverterWindow.h" #include "MediaEncoderWindow.h" #include "MessageConstants.h" #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "MediaConverter" const char APP_SIGNATURE[] = "application/x-vnd.Haiku-MediaConverter"; MediaConverterApp::MediaConverterApp() : BApplication(APP_SIGNATURE), fWin(NULL), fConvertThreadID(-1), fConverting(false), fCancel(false) { // TODO: implement settings for window pos fWin = new MediaConverterWindow(BRect(50, 50, 520, 555)); } MediaConverterApp::~MediaConverterApp() { if (fConvertThreadID >= 0) { fCancel = true; status_t exitValue; wait_for_thread(fConvertThreadID, &exitValue); } } // #pragma mark - void MediaConverterApp::MessageReceived(BMessage *msg) { switch (msg->what) { case FILE_LIST_CHANGE_MESSAGE: if (fWin->Lock()) { bool enable = fWin->CountSourceFiles() > 0; fWin->SetEnabled(enable, enable); fWin->Unlock(); } break; case START_CONVERSION_MESSAGE: if (!fConverting) StartConverting(); break; case CANCEL_CONVERSION_MESSAGE: fCancel = true; break; case CONVERSION_DONE_MESSAGE: fCancel = false; fConverting = false; DetachCurrentMessage(); BMessenger(fWin).SendMessage(msg); break; default: BApplication::MessageReceived(msg); break; } } void MediaConverterApp::ReadyToRun() { fWin->Show(); fWin->PostMessage(INIT_FORMAT_MENUS); } void MediaConverterApp::RefsReceived(BMessage* msg) { entry_ref ref; int32 i = 0; BString errorFiles; int32 errors = 0; // from Open dialog or drag & drop while (msg->FindRef("refs", i++, &ref) == B_OK) { uint32 flags = 0; // B_MEDIA_FILE_NO_READ_AHEAD BMediaFile* file = new(std::nothrow) BMediaFile(&ref, flags); if (file == NULL || file->InitCheck() != B_OK) { errorFiles << ref.name << "\n"; errors++; delete file; continue; } if (fWin->Lock()) { if (!fWin->AddSourceFile(file, ref)) delete file; fWin->Unlock(); } } if (errors) { BString alertText; static BStringFormat format(B_TRANSLATE("{0, plural, " "one{The file was not recognized as a supported media file:} " "other{# files were not recognized as supported media files:}}")); format.Format(alertText, errors); alertText << "\n" << errorFiles; BAlert* alert = new BAlert((errors > 1) ? B_TRANSLATE("Error loading files") : B_TRANSLATE("Error loading a file"), alertText.String(), B_TRANSLATE("Continue"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); alert->Go(); } } // #pragma mark - bool MediaConverterApp::IsConverting() const { return fConverting; } void MediaConverterApp::StartConverting() { bool locked = fWin->Lock(); if (locked && (fWin->CountSourceFiles() > 0)) { fConvertThreadID = spawn_thread(MediaConverterApp::_RunConvertEntry, "converter thread", B_LOW_PRIORITY, (void *)this); if (fConvertThreadID >= 0) { fConverting = true; fCancel = false; resume_thread(fConvertThreadID); } } if (locked) { fWin->Unlock(); } } void MediaConverterApp::SetStatusMessage(const char* message) { if (fWin != NULL && fWin->Lock()) { fWin->SetStatusMessage(message); fWin->Unlock(); } } // #pragma mark - BEntry MediaConverterApp::_CreateOutputFile(BDirectory directory, entry_ref* ref, media_file_format* outputFormat) { BString name(ref->name); // create output file name int32 extIndex = name.FindLast('.'); if (extIndex != B_ERROR) name.Truncate(extIndex + 1); else name.Append("."); name.Append(outputFormat->file_extension); BEntry directoryEntry; directory.GetEntry(&directoryEntry); if (!directoryEntry.Exists()) { BAlert* alert = new BAlert(B_TRANSLATE("Error"), B_TRANSLATE("Selected directory not found. " "Defaulting to /boot/home"), B_TRANSLATE("OK")); alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); alert->Go(); directory.SetTo("/boot/home"); } BEntry inEntry(ref); BEntry outEntry; if (inEntry.InitCheck() == B_OK) { // ensure that output name is unique int32 len = name.Length(); int32 i = 1; while (directory.Contains(name.String())) { name.Truncate(len); name << " " << i; i++; } outEntry.SetTo(&directory, name.String()); } return outEntry; } int32 MediaConverterApp::_RunConvertEntry(void* castToMediaConverterApp) { MediaConverterApp* app = (MediaConverterApp*)castToMediaConverterApp; app->_RunConvert(); return 0; } void MediaConverterApp::_RunConvert() { bigtime_t start = 0; bigtime_t end = 0; int32 audioQuality = 75; int32 videoQuality = 75; if (fWin->Lock()) { char *a; start = strtoimax(fWin->StartDuration(), &a, 0) * 1000; end = strtoimax(fWin->EndDuration(), &a, 0) * 1000; audioQuality = fWin->AudioQuality(); videoQuality = fWin->VideoQuality(); fWin->Unlock(); } int32 srcIndex = 0; BMediaFile *inFile(NULL), *outFile(NULL); BEntry outEntry; entry_ref inRef; entry_ref outRef; BPath path; BString name; while (!fCancel) { if (fWin->Lock()) { status_t r = fWin->GetSourceFileAt(srcIndex, &inFile, &inRef); if (r == B_OK) { media_codec_info* audioCodec; media_codec_info* videoCodec; media_file_format* fileFormat; fWin->GetSelectedFormatInfo(&fileFormat, &audioCodec, &videoCodec); BDirectory directory = fWin->OutputDirectory(); fWin->Unlock(); outEntry = _CreateOutputFile(directory, &inRef, fileFormat); // display file name outEntry.GetPath(&path); name.SetTo(path.Leaf()); if (outEntry.InitCheck() == B_OK) { entry_ref outRef; outEntry.GetRef(&outRef); outFile = new BMediaFile(&outRef, fileFormat); BString tmp( B_TRANSLATE("Output file '%filename' created")); tmp.ReplaceAll("%filename", name); name = tmp; } else { BString tmp(B_TRANSLATE("Error creating '%filename'")); tmp.ReplaceAll("%filename", name); name = tmp; } if (fWin->Lock()) { fWin->SetFileMessage(name.String()); fWin->Unlock(); } if (outFile != NULL) { r = _ConvertFile(inFile, outFile, audioCodec, videoCodec, audioQuality, videoQuality, start, end); // set mime update_mime_info(path.Path(), false, false, false); fWin->Lock(); if (r == B_OK) { fWin->RemoveSourceFile(srcIndex); } else { srcIndex++; BString error( B_TRANSLATE("Error converting '%filename'")); error.ReplaceAll("%filename", inRef.name); fWin->SetStatusMessage(error.String()); } fWin->Unlock(); } } else { srcIndex++; BString error( B_TRANSLATE("Error converting '%filename'")); error.ReplaceAll("%filename", inRef.name); fWin->SetStatusMessage(error.String()); fWin->Unlock(); break; } } else { break; } } BMessenger(this).SendMessage(CONVERSION_DONE_MESSAGE); } // #pragma mark - status_t MediaConverterApp::_ConvertFile(BMediaFile* inFile, BMediaFile* outFile, media_codec_info* audioCodec, media_codec_info* videoCodec, int32 audioQuality, int32 videoQuality, bigtime_t startDuration, bigtime_t endDuration) { BMediaTrack* inVidTrack = NULL; BMediaTrack* inAudTrack = NULL; BMediaTrack* outVidTrack = NULL; BMediaTrack* outAudTrack = NULL; media_format inFormat; media_format outAudFormat; media_format outVidFormat; media_raw_audio_format* raf = NULL; media_raw_video_format* rvf = NULL; int32 width = -1; int32 height = -1; uint8* videoBuffer = NULL; uint8* audioBuffer = NULL; // gather the necessary format information and construct output tracks int64 videoFrameCount = 0; int64 audioFrameCount = 0; status_t ret = B_OK; bool multiTrack = false; BNumberFormat fNumberFormat; int32 tracks = inFile->CountTracks(); for (int32 i = 0; i < tracks && (!outAudTrack || !outVidTrack); i++) { BMediaTrack* inTrack = inFile->TrackAt(i); inFormat.Clear(); inTrack->EncodedFormat(&inFormat); if (inFormat.IsAudio() && (audioCodec != NULL)) { if (outAudTrack != NULL) { multiTrack = true; continue; } inAudTrack = inTrack; outAudFormat.Clear(); outAudFormat.type = B_MEDIA_RAW_AUDIO; raf = &(outAudFormat.u.raw_audio); inTrack->DecodedFormat(&outAudFormat); audioBuffer = new uint8[raf->buffer_size]; // audioFrameSize = (raf->format & media_raw_audio_format::B_AUDIO_SIZE_MASK) // audioFrameSize = (raf->format & 0xf) * raf->channel_count; outAudTrack = outFile->CreateTrack(&outAudFormat, audioCodec); // Negociate the format with the inTrack again in case the codec // made some changes to it... inTrack->DecodedFormat(&outAudFormat); if (outAudTrack != NULL) { if (outAudTrack->SetQuality(audioQuality / 100.0f) != B_OK && fWin->Lock()) { fWin->SetAudioQualityLabel( B_TRANSLATE("Audio quality not supported")); fWin->Unlock(); } } else { SetStatusMessage(B_TRANSLATE("Error creating track.")); } } else if (inFormat.IsVideo() && (videoCodec != NULL)) { if (outVidTrack != NULL) { multiTrack = true; continue; } inVidTrack = inTrack; width = (int32)inFormat.Width(); height = (int32)inFormat.Height(); // construct desired decoded video format outVidFormat.Clear(); outVidFormat.type = B_MEDIA_RAW_VIDEO; rvf = &(outVidFormat.u.raw_video); rvf->last_active = (uint32)(height - 1); rvf->orientation = B_VIDEO_TOP_LEFT_RIGHT; rvf->display.format = B_RGB32; rvf->display.bytes_per_row = 4 * width; rvf->display.line_width = width; rvf->display.line_count = height; inVidTrack->DecodedFormat(&outVidFormat); if (rvf->display.format == B_RGBA32) { printf("fixing color space (B_RGBA32 -> B_RGB32)"); rvf->display.format = B_RGB32; } // Transfer the display aspect ratio. if (inFormat.type == B_MEDIA_ENCODED_VIDEO) { rvf->pixel_width_aspect = inFormat.u.encoded_video.output.pixel_width_aspect; rvf->pixel_height_aspect = inFormat.u.encoded_video.output.pixel_height_aspect; } else { rvf->pixel_width_aspect = inFormat.u.raw_video.pixel_width_aspect; rvf->pixel_height_aspect = inFormat.u.raw_video.pixel_height_aspect; } videoBuffer = new (std::nothrow) uint8[height * rvf->display.bytes_per_row]; outVidTrack = outFile->CreateTrack(&outVidFormat, videoCodec); if (outVidTrack != NULL) { // DLM Added to use 3ivx Parameter View const char* videoQualitySupport = NULL; BView* encoderView = outVidTrack->GetParameterView(); if (encoderView) { MediaEncoderWindow* encoderWin = new MediaEncoderWindow(BRect(50, 50, 520, 555), encoderView); encoderWin->Go(); // blocks until the window is quit // The quality setting is ignored by the 3ivx encoder if the // view was displayed, but this method is the trigger to // read all the parameter settings outVidTrack->SetQuality(videoQuality / 100.0f); // We can now delete the encoderView created for us by the // encoder delete encoderView; encoderView = NULL; videoQualitySupport = B_TRANSLATE("Video using parameters form settings"); } else if (outVidTrack->SetQuality(videoQuality / 100.0f) >= B_OK) { videoQualitySupport = B_TRANSLATE("Video quality not supported"); } if (videoQualitySupport && fWin->Lock()) { fWin->SetVideoQualityLabel(videoQualitySupport); fWin->Unlock(); } } else { SetStatusMessage(B_TRANSLATE("Error creating video.")); } } else { // didn't do anything with the track SetStatusMessage( B_TRANSLATE("Input file not recognized as Audio or Video")); inFile->ReleaseTrack(inTrack); } } if (!outVidTrack && !outAudTrack) { printf("MediaConverterApp::_ConvertFile() - no tracks found!\n"); ret = B_ERROR; } if (multiTrack) { BAlert* alert = new BAlert(B_TRANSLATE("Multi-track file detected"), B_TRANSLATE("The file has multiple audio or video tracks, only the first one of each will " "be converted."), B_TRANSLATE("Understood"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); alert->Go(); } if (fCancel) { // don't have any video or audio tracks here, or cancelled printf("MediaConverterApp::_ConvertFile()" " - user canceled before transcoding\n"); ret = B_CANCELED; } if (ret < B_OK) { delete[] audioBuffer; delete[] videoBuffer; delete outFile; return ret; } outFile->CommitHeader(); // this is where you would call outFile->AddCopyright(...) int64 framesRead; media_header mh; int32 lastPercent, currPercent; float completePercent; BString status; int64 start; int64 end; int32 stat = 0; // read video from source and write to destination, if necessary if (outVidTrack != NULL) { lastPercent = -1; videoFrameCount = inVidTrack->CountFrames(); if (endDuration == 0 || endDuration < startDuration) { start = 0; end = videoFrameCount; } else { inVidTrack->SeekToTime(&endDuration, stat); end = inVidTrack->CurrentFrame(); inVidTrack->SeekToTime(&startDuration, stat); start = inVidTrack->CurrentFrame(); if (end > videoFrameCount) end = videoFrameCount; if (start > end) start = 0; } framesRead = 0; for (int64 i = start; (i < end) && !fCancel; i += framesRead) { if ((ret = inVidTrack->ReadFrames(videoBuffer, &framesRead, &mh)) != B_OK) { fprintf(stderr, "Error reading video frame %" B_PRId64 ": %s\n", i, strerror(ret)); status.SetToFormat(B_TRANSLATE("Error read video frame %" B_PRId64), i); SetStatusMessage(status.String()); break; } if ((ret = outVidTrack->WriteFrames(videoBuffer, framesRead, mh.u.encoded_video.field_flags)) != B_OK) { fprintf(stderr, "Error writing video frame %" B_PRId64 ": %s\n", i, strerror(ret)); status.SetToFormat(B_TRANSLATE("Error writing video frame %" B_PRId64), i); SetStatusMessage(status.String()); break; } completePercent = (float)(i - start) / (float)(end - start) * 100; currPercent = (int32)completePercent; if (currPercent > lastPercent) { lastPercent = currPercent; BString data; double percentValue = (double)currPercent; if (fNumberFormat.FormatPercent(data, percentValue / 100) != B_OK) { data.SetToFormat("%" B_PRId32 "%%", currPercent); } status.SetToFormat(B_TRANSLATE("Writing video track: %s complete"), data.String()); SetStatusMessage(status.String()); } } outVidTrack->Flush(); inFile->ReleaseTrack(inVidTrack); } // read audio from source and write to destination, if necessary if (outAudTrack != NULL) { lastPercent = -1; audioFrameCount = inAudTrack->CountFrames(); if (endDuration == 0 || endDuration < startDuration) { start = 0; end = audioFrameCount; } else { inAudTrack->SeekToTime(&endDuration, stat); end = inAudTrack->CurrentFrame(); inAudTrack->SeekToTime(&startDuration, stat); start = inAudTrack->CurrentFrame(); if (end > audioFrameCount) end = audioFrameCount; if (start > end) start = 0; } for (int64 i = start; (i < end) && !fCancel; i += framesRead) { if ((ret = inAudTrack->ReadFrames(audioBuffer, &framesRead, &mh)) != B_OK) { fprintf(stderr, "Error reading audio frames: %s\n", strerror(ret)); status.SetToFormat(B_TRANSLATE("Error read audio frame %" B_PRId64), i); SetStatusMessage(status.String()); break; } if ((ret = outAudTrack->WriteFrames(audioBuffer, framesRead)) != B_OK) { fprintf(stderr, "Error writing audio frames: %s\n", strerror(ret)); status.SetToFormat(B_TRANSLATE("Error writing audio frame %" B_PRId64), i); SetStatusMessage(status.String()); break; } completePercent = (float)(i - start) / (float)(end - start) * 100; currPercent = (int32)completePercent; if (currPercent > lastPercent) { lastPercent = currPercent; BString data; double percentValue = (double)currPercent; if (fNumberFormat.FormatPercent(data, percentValue / 100) != B_OK) { data.SetToFormat("%" B_PRId32 "%%", currPercent); } status.SetToFormat(B_TRANSLATE("Writing audio track: %s complete"), data.String()); SetStatusMessage(status.String()); } } outAudTrack->Flush(); inFile->ReleaseTrack(inAudTrack); } outFile->CloseFile(); delete outFile; delete[] videoBuffer; delete[] audioBuffer; return ret; } // #pragma mark - int main(int, char **) { MediaConverterApp app; app.Run(); return 0; }