/* $Id: playfair.c,v 1.4 2004/04/07 18:28:40 play_fair Exp $
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License as
** published by the Free Software Foundation; either version 2 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful, but
** WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
** 02111-1307, USA.
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <mp4ff.h>
#include <mp4.h>

#include "playfair.h"

#define min(a, b)  (((a) < (b)) ? (a) : (b))
#define stricmp strcasecmp

uint32_t read_callback(void *user_data, void *buffer, uint32_t length) {
    return fread(buffer, 1, length, (FILE*)user_data);
}

uint32_t seek_callback(void *user_data, uint64_t position) {
    return fseek((FILE*)user_data, position, SEEK_SET);
}

int get_aac_track(playfair_callback_t *pfcb, mp4ff_t *infile) {
    int i;
    int numTracks = mp4ff_total_tracks(infile);

    for (i = 0; i < numTracks; i++) {
        int rc = mp4ff_get_track_type(infile, i);
        if (rc == 1) /* audio track */
            return i;
    }

    return ERR_NOAACTRACK;
}

int remux(playfair_callback_t *pfcb, char *infile, char *outfile) {
    char percents[256];
    int percent, old_percent = -1;

    /* infile variables */
    mp4ff_t *mp4infile;
    long sampleId, numSamples;
    FILE *mp4File;
    unsigned char *buffer;
    int buffer_size;
    int rc, track, timescale;

    /* outfile variables */
    MP4FileHandle MP4hFile;
    MP4TrackId MP4track;

    /* initialise the callback structure */
    mp4ff_callback_t *mp4cb = malloc(sizeof(mp4ff_callback_t));

    mp4File = fopen(infile, "rb");
    mp4cb->read = read_callback;
    mp4cb->seek = seek_callback;
    mp4cb->user_data = mp4File;

    mp4infile = mp4ff_open_read(mp4cb);
    if (!mp4infile) {
        return pfcb->die(pfcb->user_data, ERR_FILEOPEN, infile);
    }

    track = get_aac_track(pfcb, mp4infile);
    if (track == ERR_NOAACTRACK) {
        return pfcb->die(pfcb->user_data, ERR_NOAACTRACK);
    }

    buffer = NULL;
    buffer_size = 0;

    mp4ff_get_decoder_config(mp4infile, track, &buffer, &buffer_size);

    timescale = mp4ff_time_scale(mp4infile, track);
    numSamples = mp4ff_num_samples(mp4infile, track);

    MP4hFile = MP4Create(outfile, 0, 0, 0);
    if (MP4hFile == MP4_INVALID_FILE_HANDLE) {
        return pfcb->die(pfcb->user_data, ERR_FILECREATE, outfile);
    }

    MP4SetTimeScale (MP4hFile, 90000);
    MP4track = MP4AddAudioTrack(MP4hFile, timescale, 1024,
                                MP4_MPEG4_AUDIO_TYPE);
    MP4SetAudioProfileLevel(MP4hFile, 0x0F);
    MP4SetTrackESConfiguration(MP4hFile, MP4track, (u_int8_t *)buffer,
                               buffer_size);

    if (buffer)
        free(buffer);

    for (sampleId = 0; sampleId < numSamples; sampleId++) {
        /* get acces unit from MP4 file */
        buffer = NULL;
        buffer_size = 0;

        rc = mp4ff_read_sample(mp4infile, track, sampleId, &buffer,
                               &buffer_size);
        if (rc == 0) {
            int err = pfcb->die(pfcb->user_data, ERR_REMUX);
            mp4ff_close(mp4infile);
            free(mp4cb);
            fclose(mp4File);
            return err;
        }

        MP4WriteSample(MP4hFile, MP4track, buffer, buffer_size,
                       MP4_INVALID_DURATION, 0, 1 );

        if (buffer)
            free(buffer);

        percent = min((int)(((double)sampleId*100)/(double)numSamples), 100);
        if (percent > old_percent) {
            old_percent = percent;
            sprintf(percents, "%s %d%% converted.\r", infile, percent);
            pfcb->status(pfcb->user_data, percents);
        }
    }

    MP4Close(MP4hFile);
    mp4ff_close(mp4infile);

    free(mp4cb);
    fclose(mp4File);

    MP4Optimize(outfile, NULL, 0);

    return SUCCESS;
}

int copy_metadata(playfair_callback_t *pfcb, char *infile,
                  char *outfile, bool copyart) {
    MP4FileHandle mp4in, mp4out;
    char *str;
    u_int8_t flag;
    u_int16_t num, total;
    u_int8_t *data;
    u_int32_t size;

    mp4in = MP4Read(infile, 0);
    mp4out = MP4Modify(outfile, 0, 0);

    if (MP4GetMetadataName(mp4in, &str)) {
        if (!MP4SetMetadataName(mp4out, str)) {
            int err = pfcb->die(pfcb->user_data, ERR_METADATA);
            free(str);
            MP4Close(mp4in);
            MP4Close(mp4out);
            return err;
        }
        free(str); str = NULL;
    }

    if (MP4GetMetadataArtist(mp4in, &str)) {
        if (!MP4SetMetadataArtist(mp4out, str)) {
            int err = pfcb->die(pfcb->user_data, ERR_METADATA);
            free(str);
            MP4Close(mp4in);
            MP4Close(mp4out);
            return err;
        }
        free(str); str = NULL;
    }

    if (MP4GetMetadataAlbum(mp4in, &str)) {
        if (!MP4SetMetadataAlbum(mp4out, str)) {
            int err = pfcb->die(pfcb->user_data, ERR_METADATA);
            free(str);
            MP4Close(mp4in);
            MP4Close(mp4out);
            return err;
        }
        free(str); str = NULL;
    }

    if (MP4GetMetadataWriter(mp4in, &str)) {
        if (!MP4SetMetadataWriter(mp4out, str)) {
            int err = pfcb->die(pfcb->user_data, ERR_METADATA);
            free(str);
            MP4Close(mp4in);
            MP4Close(mp4out);
            return err;
        }
        free(str); str = NULL;
    }

    if (MP4GetMetadataGenre(mp4in, &str)) {
        if (!MP4SetMetadataGenre(mp4out, str)) {
            int err = pfcb->die(pfcb->user_data, ERR_METADATA);
            free(str);
            MP4Close(mp4in);
            MP4Close(mp4out);
            return err;
        }
        free(str); str = NULL;
    }

    if (MP4GetMetadataYear(mp4in, &str)) {
        if (!MP4SetMetadataYear(mp4out, str)) {
            int err = pfcb->die(pfcb->user_data, ERR_METADATA);
            free(str);
            MP4Close(mp4in);
            MP4Close(mp4out);
            return err;
        }
        free(str); str = NULL;
    }

    if (MP4GetMetadataComment(mp4in, &str)) {
        if (!MP4SetMetadataComment(mp4out, str)) {
            int err = pfcb->die(pfcb->user_data, ERR_METADATA);
            free(str);
            MP4Close(mp4in);
            MP4Close(mp4out);
            return err;
        }
        free(str); str = NULL;
    }

    if (MP4GetMetadataTool(mp4in, &str)) {
        if (!MP4SetMetadataTool(mp4out, str)) {
            int err = pfcb->die(pfcb->user_data, ERR_METADATA);
            free(str);
            MP4Close(mp4in);
            MP4Close(mp4out);
            return err;
        }
        free(str); str = NULL;
    }

    if (MP4GetMetadataCompilation(mp4in, &flag)) {
        if (!MP4SetMetadataCompilation(mp4out, flag)) {
            int err = pfcb->die(pfcb->user_data, ERR_METADATA);
            MP4Close(mp4in);
            MP4Close(mp4out);
            return err;
        }
        flag = 0;
    }

    if (MP4GetMetadataTempo(mp4in, &num)) {
        if (!MP4SetMetadataTempo(mp4out, num)) {
            int err = pfcb->die(pfcb->user_data, ERR_METADATA);
            MP4Close(mp4in);
            MP4Close(mp4out);
            return err;
        }
        num = 0;
    }

    if (MP4GetMetadataTrack(mp4in, &num, &total)) {
        if (!MP4SetMetadataTrack(mp4out, num, total)) {
            int err = pfcb->die(pfcb->user_data, ERR_METADATA);
            MP4Close(mp4in);
            MP4Close(mp4out);
            return err;
        }
        free(str); str = NULL;
    }

    if (MP4GetMetadataDisk(mp4in, &num, &total)) {
        if (!MP4SetMetadataDisk(mp4out, num, total)) {
            int err = pfcb->die(pfcb->user_data, ERR_METADATA);
            MP4Close(mp4in);
            MP4Close(mp4out);
            return err;
        }
        num = total = 0;
    }

    if (MP4GetMetadataCoverArt(mp4in, &data, &size)) {
        if (!MP4SetMetadataCoverArt(mp4out, data, size)) {
            int err = pfcb->die(pfcb->user_data, ERR_METADATA);
            free(data);
            MP4Close(mp4in);
            MP4Close(mp4out);
            return err;
        }
        free(data); data = NULL;
        size = 0;
    }

    MP4Close(mp4in);
    MP4Close(mp4out);

    return SUCCESS;
}

int convert(playfair_callback_t *pfcb, char *infile, char *outfile,
            bool copyart, bool copytags) {
    int err;

    if (! set_keyenv() ) {
      return ERR_NOKEY;
    }

    if ((err = remux(pfcb, infile, outfile))) {
        return err;
    }

    if (copytags) {
        if ((err = copy_metadata(pfcb, infile, outfile, copyart))) {
            return err;
        }
    }

    return SUCCESS;
}

/* EOF */
