/*
 Copyright (C) 2002 Andreas Thiede ( a.thiede@berlin.de )

 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
*/

#include <IO/Output/QTRecorder.hpp>
#include <IO/Manager/IOManager.hpp>
#include <IO/Manager/IOBuffer.hpp>
#include <IO/Manager/IOStream.hpp>
#import <ctype.h>
#import <unistd.h>
#import <stdlib.h>
#import <fcntl.h>
#import <time.h>
#import <sys/time.h>

#import <util/file.h>
#import <util/str.h>
#import <util/Loader.h>

#import <lib/Preferences.h>

#define kMyCreatorType          FOUR_CHAR_CODE('TVOD')

extern "C" OSErr NativePathNameToFSSpec( const char * pathname, FSSpecPtr theFile, long flags );

extern "C" OSErr FSSpecToNativePathName( FSSpecPtr theFile, char * pathname, UInt16 pathnameMaxBufferSize, long flags );
#if 0
extern "C" {

int RGB2YUV_ppc (int x_dim, int y_dim, const void *inBuf, void * outBuf );

#if ! defined min
#define min( a, b ) ( a < b ? a : b )
#endif

extern int recording;

int RGB2YUV_ppc (int x_dim, int y_dim, const void *inBuf, void * outBuf );

}
#endif

static unsigned char videoOutBuf[720*576*4];
static unsigned char audioOutBuf[8192*2*4];

static UInt64 millitime()
{
    struct timeval now;
        
    gettimeofday( &now, 0 );
    
    return (UInt64) now.tv_sec * 1000 + (UInt64) now.tv_usec / 1000;
}

static string t2str( UInt64 msec )
{
    char buf[128];
    
    time_t t = (unsigned) (msec / 1000);
    
    struct tm * p = localtime( &t );
    
    sprintf( buf, "%02d:%02d:%02d:%03d", p->tm_hour, p->tm_min, p->tm_sec, (unsigned) (msec % 1000) );
    
    return buf;
}

bool CheckError(OSErr error, char *msg)
{
    if( error != noErr ) {
        printf("Error(%d,%d, %s)\n", error, GetMoviesError(), msg );
        return true;
    }
    return false;
}

/************************************************************
*                                                           *
*    CONSTANTS                                              *
*                                                           *
*************************************************************/

#define	kOurSoundResourceID 128

#define	kSoundSampleDuration 1
#define	kSyncSample 0
#define	kTrackStart	0
#define	kMediaStart	0
/* 
for the following constants, please consult the Macintosh
Audio Compression and Expansion Toolkit
*/
#define kMACEBeginningNumberOfBytes 6
#define kMACE31MonoPacketSize 2
#define kMACE31StereoPacketSize 4
#define kMACE61MonoPacketSize 1
#define kMACE61StereoPacketSize 2


// -- VideoRecorder

class VideoRecorder : public IOOutput {

    typedef IOOutput super;

    QTRecorder * mOwner;

    IOThread   mWorkThread;
    
public:

    VideoRecorder( QTRecorder * owner, const string & aName, IODeviceID anID )
     : IOOutput( aName, anID ), mOwner( owner ), mWorkThread( aName )  {
        init();
    }
    
    ~VideoRecorder() {
        finit();
    }

    void init() {}

    void finit() {}
    
    bool start() {
    
        mStarted = true;

        mWorkThread.start(  videoRecorderThreadEntry, this );
        
        return mStarted == true;
    }
    
    bool stop() {
    
        mStarted = false;

        mWorkThread.stop();

        return mStarted == false;
    }

    size_t frameSize() {
        return mOwner->videoFrameSize();
    }
    
    bool updateFrom( IOBuffer * ioBuf ) {

        if( mOwner->mGO ) {
            return super::updateFrom( ioBuf );
        }
        return false;
    }

    void run() {

        OSErr err = BeginMediaEdits ( mOwner->mVideoMedia);
    
        UInt64 startTime = millitime();
        
        printf( "Video start %s\n", t2str( startTime ).c_str() ); fflush( stdout );
        
        if( CheckError( err, "BeginVideoMediaEdits error" ) ) {
            
            mStarted = false;
        
            return;
        }
        
        IOThread::Request request;
    
        while( ! mOwner->mGO )
            usleep( 10 * 1000 );

        do {
    
            request = mWorkThread.serviceCancellation();
        
            switch( request ) {

            case IOThread::ioStop:
                break;
            
            case IOThread::ioPause:
                
            default:
        
                {
                    IOBuffer * ioBuf = getBuffer( 40 );
                
                    if( ioBuf && mOwner->mGO ) {

                        mOwner->AddVideoSamplesToMedia( ioBuf );
                    
                        ioBuf->release();
                    }                    
                
                }
                break;
            }
        
            if( ! mOwner->mGO ) {

                OSErr err = EndMediaEdits ( mOwner->mVideoMedia);
    
                CheckError( err, "EndVideoMediaEdits error" );
                
                break;
            }

        } while( request !=  IOThread::ioStop );

        UInt64 stopTime = millitime();
        
        printf( "Video stop %s duration %d\n", t2str( stopTime ).c_str(), (unsigned) ( stopTime - startTime ) ); fflush( stdout );

    }

    static void * videoRecorderThreadEntry( void * _this ) {

        ((VideoRecorder*) _this )->run();
    
        return 0;
    }

};

// ___ AudioRecorder

class AudioRecorder : public IOOutput {

    typedef IOOutput super;

    QTRecorder * mOwner;

    IOThread   mWorkThread;

public:

    AudioRecorder( QTRecorder * owner, const string & aName, IODeviceID anID )
     : IOOutput( aName, anID ), mOwner( owner ), mWorkThread( aName ) {
        init();
    }
    
    ~AudioRecorder() {
        finit();
    }

    void init() {}

    void finit() {}
    
    bool start() {
    
        mStarted = true;

        mWorkThread.start(  audioRecorderThreadEntry, this );
        
        return mStarted = true;
    }
    
    bool stop() {

        mStarted = false;

        mWorkThread.stop();

        return mStarted = false;
    }

    size_t frameSize() {
        return mOwner->audioFrameSize();
    }
    
    bool updateFrom( IOBuffer * ioBuf ) {

        if( mOwner->mGO ) {
            return super::updateFrom( ioBuf );
        }
        return false;
    }

    void run() {

        OSErr err = BeginMediaEdits ( mOwner->mAudioMedia);

        UInt64 startTime = millitime();
        
        printf( "Audio start %s\n", t2str( startTime ).c_str() ); fflush( stdout );
    
        if( CheckError( err, "BeginAudioMediaEdits error" ) ) {

            mStarted = false;
        
            return;
        }
        
        IOThread::Request request;
    
        while( ! mOwner->mGO )
            usleep( 10 * 1000 );

        do {
    
            request = mWorkThread.serviceCancellation();
        
            switch( request ) {

            case IOThread::ioStop:
                break;
            
            case IOThread::ioPause:
                
            default:
        
                {
                    IOBuffer * ioBuf = getBuffer( 40 );
                
                    if( ioBuf && mOwner->mGO ) {

                        mOwner->AddAudioSamplesToMedia( ioBuf );
                    
                        ioBuf->release();
                    }                    
                
                }
                break;
            }
        
            if( ! mOwner->mGO ) {

                OSErr err = EndMediaEdits ( mOwner->mAudioMedia);
    
                CheckError( err, "EndAudioMediaEdits error" );
                
                break;
            }

        } while( request !=  IOThread::ioStop );

        UInt64 stopTime = millitime();
        
        printf( "Audio stop %s duration %d\n", t2str( stopTime ).c_str(), (unsigned) ( stopTime - startTime ) ); fflush( stdout );

    }

    static void * audioRecorderThreadEntry( void * _this ) {

        ((AudioRecorder*) _this )->run();
    
        return 0;
    }

};



/************************************************************
*                                                           *
*    QTSound_CreateMySoundTrack()                           *
*                                                           *
*    Creates a QuickTime movie sound track & media data     *
*                                                           *
*************************************************************/


bool QTRecorder::CreateSoundTrack()
{    
    mAudioTrack = NewMovieTrack( mQTMovie, 0, 0, kFullVolume );
    
    if( CheckError (GetMoviesError(), "NewMovieTrack error" ) )
        return false;

    mAudioMedia = NewTrackMedia( mAudioTrack, SoundMediaType, 44100, 0, 0 );

    if( CheckError( GetMoviesError(), "NewTrackMedia error" ) )
        return false;

    return true;
} 

/************************************************************
*                                                           *
*    CONSTANTS                                              *
*                                                           *
*************************************************************/

#define		kVideoTimeScale 	600
#define		kNumVideoFrames 	70
#define		kPixelDepth 		8	/* use 8-bit depth */
#define		kNoOffset 			0
#define		kMgrChoose			0
#define		kSyncSample 		0
#define		kAddOneVideoSample	1
#define		kSampleDuration 	60	/* frame duration = 1/10 sec */
#define		kTrackStart			0
#define		kMediaStart			0


/************************************************************
*                                                           *
*    QTVideo_CreateMyVideoTrack()                           *
*                                                           *
*    Creates a video track for a given QuickTime movie      *
*                                                           *
*************************************************************/

bool QTRecorder::CreateVideoTrack()
{
    mVideoTrack = NewMovieTrack( mQTMovie, FixRatio( mTrackFrame.right,1), FixRatio( mTrackFrame.bottom,1), kNoVolume);

    if( CheckError( GetMoviesError(), "NewMovieTrack error" ) )
        return false;

    mVideoMedia = NewTrackMedia( mVideoTrack, VideoMediaType, 44100 /* Video Time Scale */, nil, 0);
	
    if( CheckError( GetMoviesError(), "NewTrackMedia error" ) )
        return  false;

    return true;
} 

static void scale_down( char * _dst, const char * _src, int width, int height, int factor )
{
    int x,y;
    
    for( y = 0; y < height/factor; y++ ) {

        UInt32 * dst = (UInt32 *) &_dst[y*width/factor*4];
        UInt32 * src = (UInt32 *) &_src[y*factor*width*4];
    
        for( x = 0; x < width/factor; x++ ) {
            dst[x] = src[x*factor];
        }
    }
    
}

/************************************************************
*                                                           *
*    QTRecorder::AddVideoSamplesToMedia()                   *
*                                                           *
*    Creates video samples for the media in a track         *
*                                                           *
*************************************************************/

void QTRecorder::AddVideoSamplesToMedia( IOBuffer * ioBuf )
{
    lock();

    if( (time(0) - mStartTime ) > 0 ) {
    
        float rate = (float) mVideoFrames / (float) ((time(0) - mStartTime ));

        if( rate > mFrameRate ) {
            //printf("Dropping frame due to %f > %f now %d start %d frames %d\n", rate, mFrameRate, time(0), mStartTime, mVideoFrames );
            unlock();
            return;
        }
    }

    TimeValue vTime = GetMediaDuration( mVideoMedia );
    TimeValue aTime = GetMediaDuration( mAudioMedia );
    
    //printf( "video time %ld audio time %ld\r", vTime, aTime ); fflush( stdout );

    int duration = 1764;

    if( mUseAudio ) {
    
        if( vTime > aTime ) {
            mDroppedFrames++;
            unlock();
            return;
        }
        else if( vTime < aTime ) {

            duration = (aTime-vTime);

            //printf("adjusted duration to %d\n", duration); fflush( stdout );
        }
    }
    
    long      maxCompressedSize;
    Handle    compressedData    = 0;
    Ptr       compressedDataPtr = 0;
    OSErr     err               = noErr;
    GWorldPtr gWorld            = 0;

    
    int pixFmt = 32;
    
    char * p = (char * )ioBuf->getData();
    
    p += 360 * 288 * 4;

    // SuperCed adds
    // just for me because I want to record in 360 * 288
    mDivider = 1;
    // end SuperCed change
    if( mDivider != 1 ) {
        scale_down( (char*) videoOutBuf, p, 360, 288, mDivider );
    
        err = NewGWorldFromPtr( &gWorld, pixFmt, &mTrackFrame, 0, 0, 0,  (char*) videoOutBuf, 360 * 4 / mDivider );

    }
    else {
        err = NewGWorldFromPtr( &gWorld, pixFmt, &mTrackFrame, 0, 0, 0,  (char*) p, 360 * 4  );
    }
    
    err = GetMaxCompressionSize( GetGWorldPixMap(gWorld) , &mTrackFrame, 
                                 kMgrChoose, /* let ICM choose depth */
                                 codecNormalQuality, 
                                 mCodec, 
                                 (CompressorComponent) anyCodec,
                                 &maxCompressedSize);

    if( CheckError (err, "GetMaxCompressionSize error" ) )
        goto fail;

    compressedData = NewHandle(maxCompressedSize);

    if( CheckError( MemError(), "NewHandle error" ) )
        goto fail;

    MoveHHi( compressedData );
    HLock( compressedData );
	
    compressedDataPtr = *compressedData;

    if( CheckError( MemError(), "NewHandle error" ) )
        goto fail;

    err = CompressImage( GetGWorldPixMap(gWorld), 
                         &mTrackFrame, codecNormalQuality, mCodec, mImageDesc, compressedDataPtr );

    if( CheckError( err, "CompressImage error" ) )
        goto fail;

    err = AddMediaSample( mVideoMedia, compressedData, kNoOffset,	/* no offset in data */
                                        (**mImageDesc).dataSize, 
                                        duration,	/* frame duration = 1/25 sec */
                                        (SampleDescriptionHandle) mImageDesc, 
                                        kAddOneVideoSample,	/* one sample */
                                        kSyncSample,	/* self-contained samples */
                                        nil);
#if 0 
    printf("cType(%d) vd1(%ld) vd2(%d) width(%d) height(%d) vRes(%d) hRes(%d) size(%d) depth(%d) frameCount(%d) clutID(%d) \n", 
    (*mImageDesc)->cType, 
    (*mImageDesc)->resvd1,
    (*mImageDesc)->resvd2,
    (*mImageDesc)->width,
    (*mImageDesc)->height,
    (*mImageDesc)->vRes,
    (*mImageDesc)->hRes,
    (*mImageDesc)->dataSize,
    (*mImageDesc)->depth,
    (*mImageDesc)->frameCount,
    (*mImageDesc)->clutID);

#endif
#if 0
  CodecType           cType;                  /* what kind of codec compressed this data */
  long                resvd1;                 /* reserved for Apple use */
  short               resvd2;                 /* reserved for Apple use */
  short               dataRefIndex;           /* set to zero  */
  short               version;                /* which version is this data */
  short               revisionLevel;          /* what version of that codec did this */
  long                vendor;                 /* whose  codec compressed this data */
  CodecQ              temporalQuality;        /* what was the temporal quality factor  */
  CodecQ              spatialQuality;         /* what was the spatial quality factor */
  short               width;                  /* how many pixels wide is this data */
  short               height;                 /* how many pixels high is this data */
  Fixed               hRes;                   /* horizontal resolution */
  Fixed               vRes;                   /* vertical resolution */
  long                dataSize;               /* if known, the size of data for this image descriptor */
  short               frameCount;             /* number of frames this description applies to */
  Str31               name;                   /* name of codec ( in case not installed )  */
  short               depth;                  /* what depth is this data (1-32) or ( 33-40 grayscale ) */
  short               clutID;            
#endif
    
    mVideoFrames++;
                                                                            
    if( CheckError( err, "AddVideoMediaSample error" ) )
        goto fail;
        
fail:
    
    if (compressedData)
    {
        DisposeHandle (compressedData);
    }    
    
    if( gWorld ) {
        DisposeGWorld( gWorld );
    }
    
    unlock();
} 

static inline short adjust( float src )
{
    if ( src > 0)
        src *= 32767.0;
    else
        src *= 32768.0;

    return (short) src;
}

/************************************************************
*                                                           *
*    QTRecorder::AddAudioSamplesToMedia()                   *
*                                                           *
*    Creates video samples for the media in a track         *
*                                                           *
*************************************************************/

void QTRecorder::AddAudioSamplesToMedia( IOBuffer * ioBuf )
{
    OSErr err;
    
    //puts("QTRecorder::AddAudioSamplesToMedia");

    lock();
 
    int numSamples = (ioBuf->getSize()/sizeof(float))/2;
        
    int numBytes = numSamples*sizeof(short)*2;
    
    Handle sndHandle = NewHandleClear(0);
    
    * sndHandle = (char*) audioOutBuf;
    
    float * src = (float*) ioBuf->getData();
    //short * dst = (short*) NewPtrClear( numBytes );
    short * dst = (short*) audioOutBuf;
    
    for( int i = 0; i < numSamples*2; i++ ) {
    
        dst[i] = adjust( src[i] );
        
        //printf("dst[%d]=%x\n", i, dst[i] );
    }
    
    // place the converted data into a handle
    //err = PtrAndHand( dst, sndHandle, numBytes );
    *sndHandle = (char*) audioOutBuf;
    
    if( CheckError( err, "AddAudioMediaSample handle error"  ) )
        goto fail;
    
    err = AddMediaSample( mAudioMedia,
                          sndHandle,
                          kNoOffset,/* offset in data */
                          numBytes,
                          1,/* duration of each sound sample */
                          (SampleDescriptionHandle) mSoundDesc,
                          numSamples,
                          kSyncSample,/* self-contained samples */
                          nil);
    
    if( CheckError( err, "AddAudioMediaSample error"  ) )
        goto fail;
        
fail:

#if 0
    if( dst ) {
        DisposePtr( (char*) dst );
    }
#endif
    
    if( sndHandle != 0 ) {
        *sndHandle = 0;
        DisposeHandle( sndHandle );
    }
    
    unlock();

}

bool QTRecorder::BeginMovie ()
{
    mUseAudio = GetPrivateProfileInt( "Recording", "UseAudio" , 1 );
    mUseVideo = GetPrivateProfileInt( "Recording", "UseVideo" , 1 );

    if( ! mUseAudio && ! mUseVideo ) {
        return false;
    }
    
    char buf[1024];

    mCodec = kAnimationCodecType;
        
    OSErr err;

    CodecNameSpecListPtr clist;

    err = GetCodecNameList ( &clist, 1 );

    if( CheckError( err, "GetCodecNameList"  ) ) {
        puts("using default codec kAnimationCodecType");
    }
    else {
  
        int s = GetPrivateProfileInt( "Recording", "Compression", 0 );
        int i;
        int found = 0;
        
        for( i = 0; i < clist->count; i++ ) {
        
            CodecNameSpec p = clist->list[i];

            if( (unsigned) s == p.cType ) {

                mCodec = s;
            
                printf("using codec(%d) type(%ld) name(%s) handle(%p)\n",(int) p.codec, p.cType, PToCStr( p.typeName, buf ), p.name );
        
                found = 1;
                break;
            }
        }
        
         if( ! found ) {
            puts("using default codec kAnimationCodecType");
        }
        
        DisposeCodecNameList( clist );
    }
    
    GetPrivateProfileString( "Recording", "FrameRate", "25.0", buf, sizeof(buf) );

    mFrameRate = atof( buf );
    
#if 0

    ComponentDescription cd;
    Component            c   = 0;

    cd.componentType         = 0;
    cd.componentSubType      = 0;
    cd.componentManufacturer = 0;
    cd.componentFlags        = 0;
    cd.componentFlagsMask    = 0;

    while(( c = FindNextComponent( c, &cd )) != 0 ) {
        
        ComponentInstance ci;
                
        if( OpenAComponent( c, &ci ) == noErr ) {
        
            printf("c = %p\n", c );
        
            Handle componentName = NewHandle(0);
            Handle componentInfo = NewHandle(0);
        
            char buf1[256];
            char buf2[256];
        
            ComponentDescription cd1;

            memset( &cd1, 0, sizeof(cd1 ));
            
            if( GetComponentInfo( (Component) ci, &cd1, componentName, componentInfo, nil ) == noErr ) {
                printf( "Component(%s) Info(%s)\n", PToCStr( (const StringPtr ) *componentName, buf1 ), 
                        PToCStr( (const StringPtr ) *componentInfo, buf2 ));
            }
        
            DisposeHandle(componentName);
            DisposeHandle(componentInfo);
            
            CloseComponent(ci);
        }
    }

#endif
    
    mDivider = GetPrivateProfileInt( "Recording", "ScaleDivider", 4 );
    // SuperCed changes
    // just for me
    mDivider = 1;
    // end SuperCed changes

    printf("mDivider(%d)\n", mDivider );

    mImageDesc = (ImageDescriptionHandle) NewHandle(4);
    mSoundDesc = (SoundDescriptionV1Handle)NewHandleClear(sizeof(SoundDescriptionV1));;

    // fill in the fields of the sample description

    (*mSoundDesc)->desc.descSize                  = sizeof(SoundDescriptionV1);
    (*mSoundDesc)->desc.dataFormat                = k16BitBigEndianFormat;
    (*mSoundDesc)->desc.resvd1                    = 0;
    (*mSoundDesc)->desc.resvd2                    = 0;
    (*mSoundDesc)->desc.dataRefIndex              = 1;
    (*mSoundDesc)->desc.version                   = 1;
    (*mSoundDesc)->desc.revlevel                  = 0;
    (*mSoundDesc)->desc.vendor                    = 0;
    (*mSoundDesc)->desc.numChannels               = 2;
    (*mSoundDesc)->desc.sampleSize                = 16;
    (*mSoundDesc)->desc.compressionID             = 0;
    (*mSoundDesc)->desc.packetSize                = 0;
    (*mSoundDesc)->desc.sampleRate                = rate44khz;
    (*mSoundDesc)->samplesPerPacket               = 1024;
    (*mSoundDesc)->bytesPerPacket                 = 1024 * 4;
    (*mSoundDesc)->bytesPerFrame                  = 2;
    (*mSoundDesc)->bytesPerSample                 = 2;
    
#if 0
    SetHandleSize( (Handle) mSoundDesc, sizeof(SoundDescription) );

    SoundDescriptionPtr sndDescPtr = *mSoundDesc;

    sndDescPtr->dataFormat = kRawCodecType;   	/* uncompressed offset-binary data */
    
    sndDescPtr->numChannels = 2; 		/* number of channels of sound */
    
    sndDescPtr->sampleSize = 16;		/* number of bits per sample */
    
    sndDescPtr->sampleRate = 44100;		/* sample rate */
    
    sndDescPtr->resvd1        = 0;
    sndDescPtr->resvd2        = 0;
    sndDescPtr->dataRefIndex  = 1;
    sndDescPtr->compressionID = 0;
    sndDescPtr->packetSize    = 0;
    sndDescPtr->version       = 0;
    sndDescPtr->revlevel      = 0;
    sndDescPtr->vendor        = 0;
#endif

    Rect trackFrame = {0, 0, 288/mDivider, 360/mDivider };

    mTrackFrame = trackFrame;
    
    mResRefNum = 0;
    
    FSSpec mySpec;

#if 0        
    Str255 pstr;

    //strcpy( buf, mFileName.c_str() );

    CToPStr( mFileName.c_str(), pstr );

    FSMakeFSSpecFromPath( pstr, &mySpec );
#else

    printf("MovieFile(%s)\n", mFileName.c_str() );
    err = NativePathNameToFSSpec( mFileName.c_str(), &mySpec, 0 );

    //if( CheckError(err, "NativePathNameToFSSpec error") )
    //return;
    
#endif

    err = CreateMovieFile (&mySpec, kMyCreatorType, smCurrentScript,
                                                    createMovieFileDeleteCurFile | createMovieFileDontCreateResFile,
                                                    &mResRefNum,
                                                    &mQTMovie );
                                                    
    if( CheckError(err, "CreateMovieFile error") )
        return false;
        
    //SetMovieTimeScale( mQTMovie, 44100 );
    
    if( mUseAudio ) {
    
        if( ! CreateSoundTrack() ) {
            return false;
        }
    }
    

    if( mUseVideo ) {
    
        if( ! CreateVideoTrack() ) {
            return false;
        }
    }
    
    return true;
}

void QTRecorder::EndMovie()
{
    OSErr err;
    
    short resId = movieInDataForkResID;

    if( mUseAudio ) {
    
        err = InsertMediaIntoTrack ( mAudioTrack, kTrackStart,/* track start time */
                                    kMediaStart,/* media start time */
                                    GetMediaDuration ( mAudioMedia ),
                                    fixed1 );

        printf("Audio: MediaDuartion %10ld TrackDuration %10ld Scale %ld\n", 
                    GetMediaDuration ( mAudioMedia ) , GetTrackDuration( mAudioTrack ), GetMediaTimeScale( mAudioMedia ) );

        if( CheckError( err, "Audio: InsertMediaIntoTrack error" ) ) 
            goto fail;

    }
    
    if( mUseVideo ) {
    
        err = InsertMediaIntoTrack( mVideoTrack, kTrackStart,/* track start time */
                                    kMediaStart, /* media start time */
                                    GetMediaDuration ( mVideoMedia ),
                                    fixed1);

        printf("Video: MediaDuartion %10ld TrackDuration %10ld Scale %ld\n", 
                    GetMediaDuration ( mVideoMedia ) , GetTrackDuration( mVideoTrack ), GetMediaTimeScale( mVideoMedia ) );
    
        if( CheckError( err, "Video: InsertMediaIntoTrack error" ) )
            goto fail;

    }
    
    err = AddMovieResource( mQTMovie, mResRefNum, &resId, (unsigned char *) mFileName.c_str());

    if( CheckError(err, "AddMovieResource error") )
        goto fail;

fail:

    if( mResRefNum ) {
        printf("CloseMovieFile\n");
        CloseMovieFile( mResRefNum );
    }

    if( mImageDesc )  {
        DisposeHandle((Handle) mImageDesc );
    }

    if( mSoundDesc )  {
        DisposeHandle((Handle) mSoundDesc );
    }

    if( mQTMovie != 0 ) {
        DisposeMovie( mQTMovie);
    }
}

// --------- exported class QTRecorder

QTRecorder::QTRecorder( const string & aName, IODeviceID anID )
 : IOOutput( aName, anID ), mWorkThread( aName ), mAudioRecorder(0), mVideoRecorder(0)
{
    init();
}

QTRecorder::~QTRecorder()
{
    finit();
}

bool QTRecorder::isRecording()
{
    return mStarted;
}

void QTRecorder::toggleRecord()
{
    if( isRecording() ) {
        stop();
    }
    else {
        start();
    }
}

bool QTRecorder::start() 
{ 
    //puts("QTRecorder::start");
    
    if( mInited && ! mStarted  ) {

        if( BeginMovie() ) {
        
            mStartTime     = time(0);
            mAudioFrames   = 0;
            mVideoFrames   = 0;
            mGO            = false;
            mDroppedFrames = 0;
        
            if( mUseVideo ) {
                mVideoRecorder->start();
            }
            
            if( mUseAudio ) {
                mAudioRecorder->start();
            }
            
            if( mUseVideo && mVideoRecorder->attachedTo() ){
                mVideoRecorder->attachedTo()->start();                
            }
            
            if( mUseAudio && mAudioRecorder->attachedTo() ){
                mAudioRecorder->attachedTo()->start();                
            }
            
            mStarted = true;
            mGO      = true;
        }
    }

    return mStarted;
}

bool QTRecorder::stop() 
{ 
    mStarted = false;
    mGO      = false;

    printf( "QTRecorder::stop 1: %s\n",t2str(millitime()).c_str() ); fflush( stdout );
    
    mVideoRecorder->stop();
    mAudioRecorder->stop();
    
    mVideoRecorder->detachFromStream();
    mAudioRecorder->detachFromStream();

    lock();
    
    EndMovie();
    
    unlock();

    printf( "QTRecorder::stop 2: %s\n", t2str(millitime()).c_str() ); fflush( stdout );
    
    printf( "time %ld frames %ld fps = %f dropped(%d)\n", (time(0) - mStartTime), ( mVideoFrames - mDroppedFrames ),
        (float) mVideoFrames / (float) ((time(0) - mStartTime)), mDroppedFrames );
    
    return mStarted;
}

size_t QTRecorder::frameSize()
{
    return 0;
}

size_t QTRecorder::videoFrameSize()
{
    return 360*288*4;
}

size_t QTRecorder::audioFrameSize()
{
    return 0;
}

void QTRecorder::init()
{
    mVideoRecorder = new VideoRecorder( this, "VideoRecorder", 0 );
    mAudioRecorder = new AudioRecorder( this, "AudioRecorder", 1 );

    EnterMovies();
    
    mInited = true;
}

void QTRecorder::finit()
{
    IOManager::instance().unregisterNode ( this );
}

bool QTRecorder::updateFrom( IOBuffer * ioBuf )
{
    return super::updateFrom( ioBuf );
}

bool QTRecorder::command( const string & command, 
                          const string & param1, const string & param2, const string & param3, const string & param4 )
{
    if( command == "toggle" ) {
        toggleRecord();
    }
    else if( command == "setVideoStream" ) {

        IOStream * videoStream = IOManager::instance().openStream( param1 );
        
        return ( videoStream != 0 && mVideoRecorder->attachTo( videoStream, false ) );
    }
    else if( command == "setAudioStream" ) {

        IOStream * audioStream = IOManager::instance().openStream( param1 );

        return ( audioStream != 0 && mAudioRecorder->attachTo( audioStream, false ) );
    }
    else if( command == "setFilename" ) {

        mFileName = param1;
    }
    
    return true;
}

extern "C" int initQTRecorderPlugin( int argc, const char ** argv )
{
    QTRecorder * ow = new QTRecorder( "QTRecorder", 0 );
    
    IOManager::instance().registerNode (ow );

    return true;
}

