root/src/apps/mediaconverter/MediaConverterApp.cpp
// 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 <inttypes.h>
#include <new>
#include <stdio.h>
#include <string.h>

#include <Alert.h>
#include <Catalog.h>
#include <fs_attr.h>
#include <Locale.h>
#include <MediaFile.h>
#include <MediaTrack.h>
#include <NumberFormat.h>
#include <Mime.h>
#include <Path.h>
#include <String.h>
#include <StringFormat.h>
#include <View.h>

#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;
}