//                              -*- Mode: C++ -*- 
// avicat.cc -- combine .AVI files into 1 long movie
// 
// $Id: avicat.cpp,v 1.2 2001/07/23 11:15:13 kabi Exp $
// 
// Copyright (C) 2001  Tom Pavel <pavel@alum.mit.edu>
// 
// Creator         : Tom Pavel  <tom>     Fri Feb  2 00:45:21 2001
// Modifier        : Tom Pavel  <tom>     Sun May 13 00:35:47 2001
// Update Count    : 31
// Status          : first working version
// 


#include <avifile.h>
#include <except.h>
#include <aviplay.h>
#include <version.h>

#include <unistd.h>		// for getopt()


// hold on to audio samples until we have a chunk at least this big:
const int MinAudChunk = 128;


IAviWriteStream* 
createVidOutStream (const IAviReadStream* inStr, IAviWriteFile* outFile)
{
    BITMAPINFOHEADER bh;
    inStr->GetVideoFormatInfo((uint8_t*)&bh, sizeof(bh));
    IAviWriteStream* outStr =
	outFile->AddStream(AviStream::Video, (uint8_t*)&bh, sizeof(bh),
			   bh.biCompression,
			   1000000.*inStr->GetFrameTime());
    return outStr;
}

IAviWriteStream* 
createAudOutStream (const IAviReadStream* inStr, IAviWriteFile* outFile)
{
    
    WAVEFORMATEX wf;
    inStr->GetAudioFormatInfo((uint8_t*)&wf, 0);
    IAviWriteStream* outStr =
	outFile->AddStream(AviStream::Audio, (uint8_t*)&wf, 18,
			   wf.wFormatTag,
			   wf.nAvgBytesPerSec,
			   wf.nBlockAlign);
    return outStr;
}



static uint8_t buf[512000];


void
concatFile (const char* fname,
	    IAviWriteFile* outFile,
	    IAviWriteStream*& outVideo,
	    IAviWriteStream*& outAudio)
{
    // Open input file by name, and create Video/Audio streams.
    IAviReadFile* aviFile = CreateIAviReadFile (fname);
    IAviReadStream* inVidStr = aviFile->GetStream(0, AviStream::Video);
    IAviReadStream* inAudStr = aviFile->GetStream(0, AviStream::Audio);
    if (inVidStr == 0 || inAudStr == 0)
	return;

    // Create the output streams (if they don't exist yet), using the formats
    // of the current (first) file.
    if (outVideo == 0)
	outVideo = createVidOutStream (inVidStr, outFile);
    else
	// check that formats match...
	;

    if (outAudio == 0)
	outAudio = createAudOutStream (inAudStr, outFile);
    else
	// check that formats match...
	;


    WAVEFORMATEX wf;
    inAudStr->GetAudioFormatInfo (&wf, 0);
    double bpf = inVidStr->GetFrameTime() * wf.nAvgBytesPerSec;

    long long written_audio = 0;
    long long written_frames = 0;

    while (!inVidStr->Eof() || !inAudStr->Eof()) {

	Unsigned samp_read, bytes_read;
	int flags = 0;

	long long excess =
	    static_cast<long long>(bpf*(written_frames+1) - written_audio);
	size_t to_read = (excess < MinAudChunk) ? 0 : excess;
	if (to_read > sizeof(buf) || inVidStr->Eof())
	    to_read = sizeof(buf);
	if (inAudStr->Eof())
	    excess = 0;
#if 0
	cout << "Excess " << excess
	     << ", written audio " << written_audio
	     << ", written frames " << written_frames
	     << ", bpf " << bpf
	     << ", to_read " << to_read
	     << endl;
#endif

	if (! inAudStr->Eof() && to_read > 0) {
	    inAudStr->ReadDirect(buf, sizeof(buf), to_read,
				 samp_read, bytes_read);
	    //cout << "READ " << bytes_read << " B from AudStr" << endl;
	    
	    inAudStr->GetFrameFlags(&flags);
	    outAudio->AddChunk(buf, bytes_read, flags);

	    written_audio += bytes_read;
	    excess -= bytes_read;
	}

	if (! inVidStr->Eof()) {
	    inVidStr->ReadDirect(buf, sizeof(buf), 1,
				 samp_read, bytes_read);
	    //cout << "READ " << bytes_read << " B from VidStr" << endl;

	    inVidStr->GetFrameFlags(&flags);
	    outVideo->AddChunk(buf, bytes_read, flags);
	    
	    written_frames++;
	}
    }
}


void
Usage (const char* progname)
{
    cerr << "usage: " << progname
	 << " [-o outfile] [-h]"
	 << endl;
    exit(1);
}


int
main (int argc, char* argv[])
try
{
    int debug = 0;
    const char* outFn = "out.avi";

    int ch;
    while ((ch = getopt(argc, argv, "dho:")) != EOF) {
        switch ((char)ch) {
        case 'd':
            ++debug;
	    break;

	case 'o':
	    outFn = optarg;
	    break;

	case 'h':
        case '?':
        default:
	    Usage (argv[0]);
	}
    }

    argc -= optind;
    argv += optind;
    if (argc < 1) Usage (argv[0]);

    // Standard AVIlib sanity check:
    if ( GetAvifileVersion() != AVIFILE_VERSION) {
        cout << "This binary was compiled for Avifile ver. "
	     << AVIFILE_VERSION
	     << ", but the library is ver. "
	     << GetAvifileVersion()
	     << ". Aborting."
	     << endl;
        return 0;
    }   


    // Do the real work
    IAviWriteFile* outFile = CreateSegmentedFile(outFn);

    // Init to NULL.  Will get set upon doing the first inFile.
    IAviWriteStream* vidStr = 0;
    IAviWriteStream* audStr = 0;

    while (argc > 0) {
	concatFile (*argv, outFile, vidStr, audStr);
        --argc;
        ++argv;	
    }

    // Close the outFile and write out the header, etc.
    delete outFile;
}

catch(FatalError& error) {
    error.Print();
}   
