/*
	File:		EI_MovieExport.c
	
	Description: QuickTime component version information

	Author:		QuickTime Enginering, era
	
	Copyright: 	© Copyright 1999 - 2003 Apple Computer, Inc. All rights reserved.
	
	Disclaimer:	IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
				("Apple") in consideration of your agreement to the following terms, and your
				use, installation, modification or redistribution of this Apple software
				constitutes acceptance of these terms.  If you do not agree with these terms,
				please do not use, install, modify or redistribute this Apple software.

				In consideration of your agreement to abide by the following terms, and subject
				to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
				copyrights in this original Apple software (the "Apple Software"), to use,
				reproduce, modify and redistribute the Apple Software, with or without
				modifications, in source and/or binary forms; provided that if you redistribute
				the Apple Software in its entirety and without modifications, you must retain
				this notice and the following text and disclaimers in all such redistributions of
				the Apple Software.  Neither the name, trademarks, service marks or logos of
				Apple Computer, Inc. may be used to endorse or promote products derived from the
				Apple Software without specific prior written permission from Apple.  Except as
				expressly stated in this notice, no other rights or licenses, express or implied,
				are granted by Apple herein, including but not limited to any patent rights that
				may be infringed by your derivative works or by other works in which the Apple
				Software may be incorporated.

				The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
				WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
				WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
				PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
				COMBINATION WITH YOUR PRODUCTS.

				IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
				CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
				GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
				ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
				OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
				(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
				ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
				
	Change History (most recent first):
	   <2>		04/21/03	era			initial release updated for X and windows
	   <1>	 	11/28/99	QTE			first file

*/

/* Movie data export components provide functions that allow the Movie Toolbox to request a data
   conversion operation. The MovieExportToHandle function instructs your component to place the
   converted data into a specified handle. The MovieExportToFile function instructs you to put
   the data into a file. You should set the appropriate flags in your component's componentFlags
   field to indicate which of these functions your component supports. Note that your component
   may support both functions. Supporting MovieExportToDataRef will allow an application to request
   that data be exported to a data reference instead of specifically to a file. You can also implement
   MovieExportFromProceduresToDataRef function to support exporting data from sources other than
   QuickTime movies.

   Because many applications expect to be able to perform an export operation from a movie or track,
   export components should support MovieExportToFile, MovieExportFromProceduresToDataRef and MovieExportToDataRef
   at a minimum. Using the routines described at the following URL,
   < http://developer.apple.com/techpubs/quicktime/qtdevdocs/REF/refDataExchange.21.htm#31410 > it is
   possible to provide small implementations of older routines that simply call the newer
   MovieExportFromProceduresToDataRef to perform the actual operation. To do this, the export component
   must implement callback functions that provide services to the movie data export component.

   The export component's MovieExportFromProceduresToDataRef routine performs data exporting. When
   executed, that routine makes callbacks to retrieve characteristics (called properties) and media
   data from each data source.
   
Movie Export Component Registration:
   
   QuickTime 3 introduced a new movie export component routine that returns the same information that
   would have been previously stored in the componentManufacturer field of the registered Movie Export
   'spit' components. An export-specific component flag indicates that the export component implements
   the new protocol. This enables developers and QuickTime to differentiate between older components and those
   using the newer mechanism. By implementing the MovieExportGetSourceMediaType routine, the export component's
   componentManufacturer field can be used to differentiate components. 

   MovieExportGetSourceMediaType returns an OSType value through its mediaType parameter, which is interpreted
   in exactly the same way that the componentManufacturer was previously interpreted. If the export component
   requires a particular type of track to exist in a movie, it returns that media handler type for example, 
   VideoMediaType, SoundMediaType and so on through the mediaType argument. If the export component works
   for an entire movie, it returns 0 through this parameter. 
   
   The following component flag indicates that this routine is implemented: 
   		movieExportMustGetSourceMediaType = 1L << 19, 

   If you implement the MovieExportGetSourceMediaType routine, you must register the component with this flag.
   Otherwise, the Movie Toolbox will not know to call the routine and will assume the older semantics for
   the componentManufacturer field. As in the past, using this mechanism does NOT replace the need forimplementing
   MovieExportValidate. This mechanism is only used to find candidate components.
*/

#if TARGET_OS_WIN32
    #include "EIComponentWindowsPrefix.h"
    #define STAND_ALONE 1
#endif

#if __MACH__
    #include <Carbon/Carbon.h>
    #include <QuickTime/QuickTime.h>
    #define USE_NIB_FILE 1
#else    
    #include <ConditionalMacros.h>
   	#include <QuickTimeComponents.h>
   	#include <MediaHandlers.h>
    #include <ControlDefinitions.h>
	#include <Resources.h>
	#include <NumberFormatting.h>
	#include <FixMath.h>
	#include <StdDef.h>	// for offsetof
#endif

#include "EI_Image.h"
#include "EI_MovieExportVersion.h"

#pragma mark- Data Structures

// Data structures
#if PRAGMA_STRUCT_ALIGN
	#pragma options align=packed
#elif PRAGMA_STRUCT_PACKPUSH
	#pragma pack(push, 1)
#elif PRAGMA_STRUCT_PACK
	#pragma pack(1)
#endif

typedef struct {
	UInt8	red;
	UInt8	green;
	UInt8	blue;
} PackedColor;

typedef struct {
	UInt8	opcode;
	UInt8	pixelData[1];
} RLE8Packet;

typedef struct {
	UInt8	opcode;
	UInt16	pixelData[1];
} RLE16Packet;

typedef struct {
	UInt8	opcode;
	UInt32	pixelData[1];
} RLE32Packet;

#if PRAGMA_STRUCT_ALIGN
	#pragma options align=reset
#elif PRAGMA_STRUCT_PACKPUSH
	#pragma pack(pop)
#elif PRAGMA_STRUCT_PACK
	#pragma pack()
#endif

typedef struct OutputTrackRecord {
	long						trackID;
	MovieExportGetPropertyUPP	getPropertyProc;
	MovieExportGetDataUPP		getDataProc;
	void						*refCon;
	TimeScale					sourceTimeScale;
	
	Fixed						width;
	Fixed						height;
	Fixed						fps;
	short						depth;

	TimeValue					time;
	long						numOfFrames;
	Ptr							compressBuffer;
	Size						compressBufferSize;
	
	long						lastDescSeed;
	ImageSequence				decompressSequence;
	GWorldPtr					gw;
	PixMapHandle				hPixMap;
} OutputTrackRecord, *OutputTrackPtr;

typedef struct {
	ComponentInstance	self;
	ComponentInstance	quickTimeMovieExporter;
	OutputTrackPtr		outputTrack;
	MovieProgressUPP	progressProc;
	long				progressRefcon;
	Fixed				fps;
	short				depth;
	Boolean				canceled;
} EI_MovieExportGlobalsRecord, *EI_MovieExportGlobals;

enum {
	kColorPopupMenuItem	     = 3,
	kFrameRatePopupMenuItem  = 4
};

enum {
	kEI_MovieExportFileNameExtention = FOUR_CHAR_CODE('eim '),
	kEI_MovieExportSettingsColor     = FOUR_CHAR_CODE('colr'), // color depth container
	kEI_MovieExportSettingsDepth	 = FOUR_CHAR_CODE('dpth'), // depth value
	kEI_MovieExportSettingsFPS   	 = FOUR_CHAR_CODE('fps ')  // frames per second value	
};

#define	kEI_MovieExportDialogResID			   512
#define kEI_MovieExportShortFileTypeNamesResID 1025

#pragma mark- Internal Prototypes

// Prototypes
static OSErr WriteFrame(OutputTrackPtr outputTrack, DataHandler dataH, long *offsetPtr);
static OSErr WriteImageFrameHeader(OutputTrackPtr outputTrack, DataHandler dataH, long *offsetPtr);
static OSErr WriteColorTable(CTabHandle cTabHdl, DataHandler dataH, long *offsetPtr);
static void  EmptyOutputTrack(OutputTrackPtr outputTrack);
static OSErr ConfigureQuickTimeMovieExporter(EI_MovieExportGlobals store);
static void  GetExportProperty(EI_MovieExportGlobals store);
static OSErr CompressRLE(PixMapHandle pixMapHdl, Ptr compressBuffer, Size *compressBufferSizePtr);
static void  CompressRLE8(UInt8 *srcPtr, Size srcSize, Ptr compressBuffer, Size *compressBufferSizePtr);
static void  CompressRLE16(UInt16 *srcPtr, Size srcSize, Ptr compressBuffer, Size *compressBufferSizePtr);
static void  CompressRLE32(UInt32 *srcPtr, Size srcSize, Ptr compressBuffer, Size *compressBufferSizePtr);

#pragma mark- Component Dispatch

// Setup required for ComponentDispatchHelper.c
#define MOVIEEXPORT_BASENAME() 		EI_MovieExport
#define MOVIEEXPORT_GLOBALS() 		EI_MovieExportGlobals storage

#define CALLCOMPONENT_BASENAME()	MOVIEEXPORT_BASENAME()
#define	CALLCOMPONENT_GLOBALS()		MOVIEEXPORT_GLOBALS()

#define COMPONENT_DISPATCH_FILE		"EI_MovieExportDispatch.h"
#define COMPONENT_UPP_SELECT_ROOT()	MovieExport

#if __MACH__
	#include <CoreServices/Components.k.h>
	#include <QuickTime/QuickTimeComponents.k.h>
	#include <QuickTime/ComponentDispatchHelper.c>
#else
	#include <Components.k.h>
	#include <QuickTimeComponents.k.h>
	#include <ComponentDispatchHelper.c>
#endif

#pragma mark-

// Component Open Request - Required
pascal ComponentResult EI_MovieExportOpen(EI_MovieExportGlobals store, ComponentInstance self)
{		
	ComponentDescription cd;
	ComponentResult 	 err;
	
	// Allocate memory for our globals, and inform the component manager that we've done so
	store = (EI_MovieExportGlobals)NewPtrClear(sizeof(EI_MovieExportGlobalsRecord));
	err = MemError();
	if ( err ) goto bail;

	store->self = self;
	SetComponentInstanceStorage(self, (Handle)store);

	// Get the QuickTime Movie export component
	// Because we use the QuickTime Movie export component, search for
	// the 'MooV' exporter using the following ComponentDescription values
	cd.componentType = MovieExportType;
	cd.componentSubType = kQTFileTypeMovie;
	cd.componentManufacturer = kAppleManufacturer;
	cd.componentFlags = canMovieExportFromProcedures | movieExportMustGetSourceMediaType;
	cd.componentFlagsMask = cd.componentFlags;

	err = OpenAComponent(FindNextComponent(NULL, &cd), &store->quickTimeMovieExporter);
	
bail:	
	return err;
}

// Component Close Request - Required
pascal ComponentResult EI_MovieExportClose(EI_MovieExportGlobals store, ComponentInstance self)
{
#pragma unused(self)
	
	// Make sure to deallocate our storage
	if (store) {
		if (store->quickTimeMovieExporter)
			CloseComponent(store->quickTimeMovieExporter);

		if (store->outputTrack) {
			EmptyOutputTrack(store->outputTrack);
			DisposePtr((Ptr)store->outputTrack);
		}
		
		DisposePtr((Ptr)store);
	}

	return noErr;
}

// Component Version Request - Required
pascal ComponentResult EI_MovieExportVersion(EI_MovieExportGlobals store)
{
#pragma unused(store)

	return kEI_MovieExportVersion;
}

#pragma mark-

// MovieExportToFile
// 		Exports data to a file. The requesting program or Movie Toolbox must create the destination file
// before calling this function. Your component may not destroy any data in the destination file. If you
// cannot add data to the specified file, return an appropriate error. If your component can write data to
// a file, be sure to set the canMovieExportFiles flag in the componentFlags field of your component's
// ComponentDescription structure. Your component must be prepared to perform this function at any time.
// You should not expect that any of your component's configuration functions will be called first. 
pascal ComponentResult EI_MovieExportToFile(EI_MovieExportGlobals store, const FSSpec *theFilePtr,
                                             Movie theMovie, Track onlyThisTrack, TimeValue startTime,
                                             TimeValue duration)
{
	AliasHandle		alias;
	ComponentResult	err;

	err = QTNewAlias(theFilePtr, &alias, true);
	if ( err ) goto bail;

	// Implement the export though a file dataRef
	err = MovieExportToDataRef(store->self, (Handle)alias, rAliasType, theMovie, onlyThisTrack, startTime, duration);

	DisposeHandle((Handle)alias);

bail:
	return err;
}

// MovieExportToDataRef
//      Allows an application to request that data be exported to a data reference.
pascal ComponentResult EI_MovieExportToDataRef(EI_MovieExportGlobals store, Handle dataRef, OSType dataRefType,
                                                Movie theMovie, Track onlyThisTrack, TimeValue startTime, TimeValue duration)
{
	TimeScale 				  scale;
	MovieExportGetPropertyUPP getVideoPropertyProc = NULL;
	MovieExportGetDataUPP	  getVideoDataProc = NULL;
	void					  *videoRefCon;
	long					  trackID;
	ComponentResult			  err;
	
	// Set up the video source
	
	// This call returns a MovieExportGetPropertyProc (MovieExportGetPropertyUPP) and a MovieExportGetDataProc (MovieExportGetDataUPP)
	// callbacks that can be passed to MovieExportAddDataSource to create a new data source. This function provides a standard way of
	// getting data using this protocol out of a movie or track. The returned procedures must be disposed by calling
	// MovieExportDisposeGetDataAndPropertiesProcs. 
	err = MovieExportNewGetDataAndPropertiesProcs(store->quickTimeMovieExporter, // A movie export component instance
												  VideoMediaType,				 // The format of the data to be generated by the returned MovieExportGetDataProc 
												  &scale,						 // The time scale returned from this function - should be passed
												  								 //   on to MovieExportAddDataSource with the returned procedures 
												  theMovie,						 // The movie for this operation, Your component may use this identifier to
												  								 //   obtain sample data from the movie or to obtain information about the movie
												  onlyThisTrack,				 // The track for this operation
												  startTime,					 // The starting point of the track or movie segment to be converted - This
												                                 //   time value is expressed in the movie's time coordinate system
												  duration,						 // The duration of the track or movie segment to be converted - This
												  								 //   duration value is expressed in the movie's time coordinate system
												  &getVideoPropertyProc,		 // A callback that provides information about processing source samples 
												  &getVideoDataProc,			 // A callback that the export component uses to request sample data 
												  &videoRefCon);				 // Passed to the procedures specified in the getPropertyProc and getDataProc
												                                 //   parameters - Use this parameter to point to a data structure containing
												                                 //   any information your callbacks need 
	if (err) goto bail;

	// ** Add the video data source **
	
	// Before starting an export operation, all the data sources must be defined by calling this function once for each data source
	err = MovieExportAddDataSource(store->self, VideoMediaType, scale, &trackID, getVideoPropertyProc, getVideoDataProc, videoRefCon);
	if (err) goto bail;

	// Perform the export operation
	err = MovieExportFromProceduresToDataRef(store->self, dataRef, dataRefType);

bail:
	if (getVideoPropertyProc || getVideoDataProc)
		// Dispose the the memory associated with the procedures returned by MovieExportNewGetDataAndPropertiesProcs 
		MovieExportDisposeGetDataAndPropertiesProcs(store->quickTimeMovieExporter, getVideoPropertyProc, getVideoDataProc, videoRefCon);
	
	return err;
}

// MovieExportFromProceduresToDataRef
//		Exports data provided by MovieExportAddDataSource to a location specified by dataRef and dataRefType.
// Movie data export components that support export operations from procedures must set the canMovieExportFromProcedures
// flag in their component flags. 
pascal ComponentResult EI_MovieExportFromProceduresToDataRef(EI_MovieExportGlobals store, Handle dataRef, OSType dataRefType)
{
	OutputTrackPtr	outputTrack;
	long			offset;
	ImageHeader		imgHeader;
	long			numOfFrames = 0;
	TimeValue		duration = 0;
	DataHandler 	dataH = NULL;
	Boolean			progressOpen = false;
	ComponentResult	err;
	
	if (!dataRef || !dataRefType)
		return paramErr;

	// Get and open a Data Handler Component that can write to the dataRef
	err = OpenAComponent(GetDataHandler(dataRef, dataRefType, kDataHCanWrite), &dataH);
	if (err) goto bail;

	// Set the DataRef
	DataHSetDataRef(dataH, dataRef);

	// Create the file
	err = DataHCreateFile(dataH, FOUR_CHAR_CODE('TVOD'), false);
	if ( err ) goto bail;

	// Set the file type - this will fail on some platforms, and that's fine
	DataHSetMacOSFileType(dataH, FOUR_CHAR_CODE('EIDI'));

	// Open for write operations
	err = DataHOpenForWrite(dataH);
	if (err) goto bail;

	// If we have an outputTrack added in the MovieExportAddDataSource call, write some frames
	outputTrack = store->outputTrack;	
	if (outputTrack) {
		// Since the property proc we call may be a proc returned from MovieExportNewGetDataAndPropertiesProcs,
		// configure the QT movie exporter so that it's properties match what we have as our defaults.
		// Before we call the proc, we always init the video property to the current default. The proc can either
		// be a client proc or one of the procs returned from MovieExportNewGetDataAndPropertiesProcs. In the
		// first case, if the proc returns an error for a property selector, the default will be used. If the 
		// proc is a movie exporter proc, it will always return a property -- namely the property configured for
		// the current exporter. If we don't configure the movie exporter, the wrong default would be returned.
		err = ConfigureQuickTimeMovieExporter(store);
		if (err) goto bail;
		
		// Call the property proc to get our export properties
		GetExportProperty(store);

		// If there's a progress proc call it with open request
		if (store->progressProc) {
			TimeRecord durationTimeRec;
			
			// Get the track duration if it is available
			if (InvokeMovieExportGetPropertyUPP(outputTrack->refCon, 1, movieExportDuration, &durationTimeRec, outputTrack->getPropertyProc) == noErr) {
				
				ConvertTimeScale(&durationTimeRec, outputTrack->sourceTimeScale);
				duration = durationTimeRec.value.lo;

				InvokeMovieProgressUPP(NULL, movieProgressOpen, progressOpExportMovie, 0, store->progressRefcon, store->progressProc);
				progressOpen = true;	
			}
		}
		
		// Reserve some space for the image header
		offset = sizeof(imgHeader);

		while (true) {
			
			// Write out a frame
			err = WriteFrame(outputTrack, dataH, &offset);
			if (err) {
				if (err == eofErr) break;

				goto bail;
			}

			// Indicate our components progress if required
			if (progressOpen) {
				
				Fixed percentDone = FixDiv(outputTrack->time, duration);
				
				if (percentDone > 0x010000)
					percentDone = 0x010000;
				
				err = InvokeMovieProgressUPP(NULL, movieProgressUpdatePercent, progressOpExportMovie, percentDone, store->progressRefcon, store->progressProc);
				if (err) goto bail;
			}
		}
		
		numOfFrames = outputTrack->numOfFrames;
	}
	
	// Write the image header
	imgHeader.imageVersion = EndianU16_NtoB(5);
	imgHeader.imageFrames = EndianU32_NtoB(numOfFrames);
	
	err = DataHWrite(dataH, (Ptr)&imgHeader, 0, sizeof(imgHeader), NULL, 0);

bail:
	if (outputTrack)
		EmptyOutputTrack(outputTrack);

	if (dataH)
		CloseComponent(dataH);
	
	// Call the progress proc with a close request if required
	if (progressOpen)
		InvokeMovieProgressUPP(NULL, movieProgressClose, progressOpExportMovie, 0, store->progressRefcon, store->progressProc);

	return err;
}

// MovieExportAddDataSource
// 		Defines a data source for use with an export operation performed by MovieExportFromProceduresToDataRef.
// Before starting an export operation, all the data sources must be defined by calling this function once for each data source.
pascal ComponentResult EI_MovieExportAddDataSource(EI_MovieExportGlobals store, OSType trackType, TimeScale scale,
                                                    long *trackIDPtr, MovieExportGetPropertyUPP getPropertyProc,
                                                    MovieExportGetDataUPP getDataProc, void *refCon)
{
	ComponentResult	err = noErr;

	// We need these or we can't add the data source
	if (!scale || !trackType || !getDataProc || !getPropertyProc)
		return paramErr;

	if (trackType == VideoMediaType && !store->outputTrack) { 
		
		OutputTrackPtr	outputTrack;
	
		// Initialize all fields to 0 by calling NewPtrClear
		outputTrack = (OutputTrackPtr)NewPtrClear(sizeof(OutputTrackRecord));
		err = MemError();
		if (err) goto bail;

		outputTrack->trackID = 1;
		outputTrack->getPropertyProc = getPropertyProc;
		outputTrack->getDataProc = getDataProc;
		outputTrack->refCon = refCon;
		outputTrack->sourceTimeScale = scale;

		store->outputTrack = outputTrack;
		*trackIDPtr = outputTrack->trackID;
	}

bail:
	return err;
}

// MovieExportValidate 
//		Determines whether a movie export component can export all the data for a specified movie or track.
// This function allows an application to determine if a particular movie or track could be exported by the specified
// movie data export component. The movie or track is passed in the theMovie and onlyThisTrack parameters as they are
// passed to MovieExportToFile. Although a movie export component can export one or more media types, it may not be able
// to export all the kinds of data stored in those media. Movie data export components that implement this function must
// also set the canMovieExportValidateMovie flag.
pascal ComponentResult EI_MovieExportValidate(EI_MovieExportGlobals store, Movie theMovie, Track onlyThisTrack, Boolean *valid)
{
	OSErr err;

	// The QT movie export component must be cool with this before we can be
	err = MovieExportValidate(store->quickTimeMovieExporter, theMovie, onlyThisTrack, valid);
	if (err) goto bail;

	if (*valid == true) {
	
		// We need to check for some kind of image, or this won't work
		if (onlyThisTrack == NULL) {
			if (GetMovieIndTrackType(theMovie, 1, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly) == NULL)
				*valid = false;
		} else {
			MediaHandler mh = GetMediaHandler(GetTrackMedia(onlyThisTrack));
			Boolean hasIt = false;

			MediaHasCharacteristic(mh, VisualMediaCharacteristic, &hasIt);
			if (hasIt == false)
				*valid = false;
		}	
	}
	
bail:
	return err;
}

#pragma mark -

/* Your component may provide configuration functions. These functions allow applications to configure your component before
   the Movie Toolbox calls your component to start the export process. Note that applications may call these functions directly.
   These functions are optional. If your component receives a request that it does not support, you should return the badComponentSelector
   error code. In addition, your component should work properly even if none of these functions is called. 

   Applications may retrieve additional data from your component by calling MovieExportGetAuxiliaryData.
   Applications may specify a progress function callback for use by your component by calling MovieExportSetProgressProc.
   Applications may instruct your component to display it's configuration dialog box by calling MovieExportDoUserDialog.
*/

// MovieExportSetProgressProc
// 		Assigns a movie progress function to your component. This progress functions supports the same interface
// as Movie Toolbox progress functions. Note that this interface not only allows you to report progress to the
// application, but also allows the application to cancel the request.
// See http://developer.apple.com/qa/qa2001/qa1230.html
pascal ComponentResult EI_MovieExportSetProgressProc(EI_MovieExportGlobals store, MovieProgressUPP proc, long refcon)
{
	store->progressProc = proc;
	store->progressRefcon = refcon;

	return noErr;
}

#pragma mark -
#pragma mark ---- Using Interface Builder
// Settings Dialogs may be implemented in a number of ways. This sample demonstrates how to support both newer
// techniques for Mac OS X where Interface Builder is used to build a Nib file and Carbon Event Handlers drive the UI,
// as well as traditional techniques required for non-carbon components (this includes Windows) where tools such as ResEdit,
// Resorcerer(r)(www.mathemaesthetics.com), Rez, DeRez, RezWack and so on are required to create and work with resources.

// When building a Stand Alone Mach-O component for Mac OS X use a Nib file for the Export Settings Dialog instead
// of traditional Macintosh Resources (.rsrc or .r file containing the DLOG, DITL, CNTL etc.)
#if STAND_ALONE && USE_NIB_FILE

// SettingsWindowEventHandler
//		Carbon Event handler for the Modal Settings Dialog.
static pascal OSStatus SettingsWindowEventHandler(EventHandlerCallRef inHandler, EventRef inEvent, void *inUserData)
{
#pragma unused (inHandler,inUserData)

	WindowRef  window = NULL;
	HICommand  command;
	ControlRef colorPopupMenuCtrl, fpsPopupMenuCtrl;
	ControlID  colorMenuID = {'eiie', kColorPopupMenuItem},
			   fpsMenuID =   {'eiie', kFrameRatePopupMenuItem};
	UInt32     ctrlValue;
	short	   depth;
	Fixed	   fps;
	Str255	   fpsStr;
	long	   fpsLong;
	
	OSStatus   result = eventNotHandledErr;
	
	EI_MovieExportGlobals store = (EI_MovieExportGlobals)(inUserData);
	
	window = ActiveNonFloatingWindow();
	if (NULL == window) goto bail;
	
	GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &command);
	
	GetControlByID(window, &colorMenuID, &colorPopupMenuCtrl);
	GetControlByID(window, &fpsMenuID, &fpsPopupMenuCtrl);

	switch (command.commandID) {
	case kHICommandOK:
			// Get the selected color depth
			ctrlValue = GetControl32BitValue(colorPopupMenuCtrl);
			
			switch (ctrlValue) {
			case 1:
				depth = 0;
				break;				
			case 3:
				depth = 1;
				break;				
			case 4:
				depth = 40; // 256 Grays
				break;
			case 5:
				depth = 8;
				break;				
			case 6:
				depth = 16;
				break;				
			case 7:
				depth = 32;
				break;
			default:
				break;
			}			
				
			// Get the selected frame rate
			ctrlValue = GetControl32BitValue(fpsPopupMenuCtrl);
			
			if (ctrlValue == 1) {
				fps = 0;
			} else {
				GetMenuItemText(GetControlPopupMenuHandle(fpsPopupMenuCtrl), ctrlValue, fpsStr);
				StringToNum(fpsStr, &fpsLong);
				fps = Long2Fix(fpsLong);		
			}
			
		store->depth = depth;
		store->fps = fps;
		store->canceled = false;
		
		QuitAppModalLoopForWindow(window);
		result = noErr;
		break;
		
	case kHICommandCancel:
		store->canceled = true;
		QuitAppModalLoopForWindow(window);
		result = noErr;
		break;

	default:
		break; 
	}

bail:
	return result;
}

// MovieExportDoUserDialog 
// 		Requests that the export component display it's configuration dialog box.
pascal ComponentResult EI_MovieExportDoUserDialog(EI_MovieExportGlobals store, Movie theMovie, Track onlyThisTrack,
												   TimeValue startTime, TimeValue duration, Boolean *canceledPtr)
{
#pragma unused(theMovie, onlyThisTrack, startTime, duration)

	CFBundleRef bundle = NULL;
	IBNibRef	nibRef = NULL;
	WindowRef   window = NULL;
	Boolean     portChanged = false;
	ControlID   colorMenuID = {'eiie', kColorPopupMenuItem},
			    fpsMenuID =   {'eiie', kFrameRatePopupMenuItem};
	ControlRef  colorPopupMenuCtrl, fpsPopupMenuCtrl;
	MenuHandle  fpsMenuHdl;
	Fixed		fps;
	short		depth;
	UInt32		ctrlValue;
	Str255		fpsStr;
	long		fpsLong;
	short		i, numOfMenuItems;
	CGrafPtr    savedPort;
	OSErr		err = resFNotFound;
	
	EventTypeSpec eventList[] = {{kEventClassCommand, kEventCommandProcess}};
	EventHandlerUPP settingsWindowEventHandlerUPP = NewEventHandlerUPP(SettingsWindowEventHandler);
	
	bundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.ElectricImageComponent"));
	if (NULL == bundle) goto bail;
	
	err = CreateNibReferenceWithCFBundle(bundle, CFSTR("EI_Export"), &nibRef);
	if (err) goto bail;
	
	err = CreateWindowFromNib(nibRef, CFSTR("Settings"), &window);
	if (err) goto bail;
	
	portChanged = QDSwapPort(GetWindowPort(window), &savedPort);

	*canceledPtr = false;
	
	fps = store->fps;
	depth = store->depth;
	
	GetControlByID(window, &colorMenuID, &colorPopupMenuCtrl);
	GetControlByID(window, &fpsMenuID, &fpsPopupMenuCtrl);
	fpsMenuHdl = GetControlPopupMenuHandle(fpsPopupMenuCtrl);

	// Un-Check the default items as set up by IB - if we don't do this here the pop-up menu ends up
	// having both the current selection as well as the defaults checked at the same time - lame!
	CheckMenuItem(GetControlPopupMenuHandle(colorPopupMenuCtrl), 1, false);
	CheckMenuItem(fpsMenuHdl, 1, false);
	
	// Set current pixel depth
	switch (depth) {
	case 0:
		ctrlValue = 1;
		break;
	case 1:
		ctrlValue = 3;
		break;	
	case 40: // 256 Grays
		ctrlValue = 4;
		break;
	case 8:
		ctrlValue = 5;
		break;		
	case 16:
		ctrlValue = 6;
		break;	
	case 32:
		ctrlValue = 7;
		break;
	default:
		ctrlValue = 1;
		break;
	}			
	
	SetControl32BitValue(colorPopupMenuCtrl, ctrlValue);
	
	// Set current frame rate
	if (fps == 0) {
		SetControl32BitValue(fpsPopupMenuCtrl, 1);
	} else {
		numOfMenuItems = CountMenuItems(fpsMenuHdl);
		
		for (i = 3; i <= numOfMenuItems; i++) {
			GetMenuItemText(fpsMenuHdl, i, fpsStr);
			StringToNum(fpsStr, &fpsLong);
			
			if (fps == Long2Fix(fpsLong)) {
				SetControl32BitValue(fpsPopupMenuCtrl, i);
				break;
			}
		}
	}

	InstallWindowEventHandler(window, settingsWindowEventHandlerUPP, GetEventTypeCount(eventList), eventList, store, NULL); 
	
	ShowWindow(window);
	
	RunAppModalLoopForWindow(window);
	
	*canceledPtr = store->canceled;

bail:
	if (window) {
		if (portChanged) {
			QDSwapPort(savedPort, NULL);
		}
		DisposeWindow(window);
	}
		
	if (settingsWindowEventHandlerUPP)
		DisposeEventHandlerUPP(settingsWindowEventHandlerUPP);
	
	if (nibRef)
		DisposeNibReference(nibRef);
		
	return err;
}

#else
#pragma mark ---- Using Traditional Mac Resources
// Export Settings Dialog using traditional Mac Resources
// On Macintosh this uses a .rsrc file and on Windows a DeRezed .r version of the same resources.

pascal ComponentResult EI_MovieExportDoUserDialog(EI_MovieExportGlobals store, Movie theMovie, Track onlyThisTrack,
												   TimeValue startTime, TimeValue duration, Boolean *canceledPtr)
{
#pragma unused(theMovie, onlyThisTrack, startTime, duration)

	Fixed		   fps;
	short		   depth;
	short		   ctrlValue;
	#if TARGET_API_MAC_CARBON
		ControlRef     colorPopupMenuCtrl,
				       fpsPopupMenuCtrl;
	#else
		ControlHandle  colorPopupMenuCtrl,
					   fpsPopupMenuCtrl;
		DialogItemType itemType;
		Rect		   itemRect;
	#endif
	MenuHandle	   fpsMenuHdl;
	Str255		   fpsStr;
	long		   fpsLong;
	short		   i, numOfMenuItems;
	short		   itemHit;
	short		   resRef = kResFileNotOpened;
	DialogPtr	   dialog = NULL;
	short		   saveResFile = CurResFile();
	OSErr		   err = noErr;

	*canceledPtr = false;
	
	fps = store->fps;
	depth = store->depth;

	#ifdef STAND_ALONE
    	// If the component is not built into an application open the res file
		err = OpenAComponentResFile((Component)store->self, &resRef);
		if ( err ) goto bail;
	#endif

	dialog = GetNewDialog(kEI_MovieExportDialogResID, NULL, (WindowPtr)-1);
	if (!dialog) {
		err = resNotFound;
		goto bail;
	}

	SetDialogDefaultItem(dialog, ok);
	SetDialogCancelItem(dialog, cancel);

	#if TARGET_API_MAC_CARBON
		GetDialogItemAsControl(dialog, kColorPopupMenuItem, &colorPopupMenuCtrl);
		GetDialogItemAsControl(dialog, kFrameRatePopupMenuItem, &fpsPopupMenuCtrl);
		fpsMenuHdl = GetControlPopupMenuHandle(fpsPopupMenuCtrl);
	#else
		GetDialogItem(dialog, kColorPopupMenuItem, &itemType, (Handle *)&colorPopupMenuCtrl, &itemRect);
		GetDialogItem(dialog, kFrameRatePopupMenuItem, &itemType, (Handle *)&fpsPopupMenuCtrl, &itemRect);
		fpsMenuHdl = (**(PopupPrivateDataHandle)(**fpsPopupMenuCtrl).contrlData).mHandle;
	#endif

	// Set current pixel depth
	switch (depth) {
	case 0:
		ctrlValue = 1;
		break;
	case 1:
		ctrlValue = 3;
		break;	
	case 40: // 256 Grays
		ctrlValue = 4;
		break;
	case 8:
		ctrlValue = 5;
		break;		
	case 16:
		ctrlValue = 6;
		break;	
	case 32:
		ctrlValue = 7;
		break;
	default:
		ctrlValue = 1;
		break;
	}			
 
	SetControlValue(colorPopupMenuCtrl, ctrlValue);
	
	// Set current frame rate
	if (fps == 0) {
		SetControlValue(fpsPopupMenuCtrl, 1);
	} else {
		numOfMenuItems = CountMenuItems(fpsMenuHdl);
		
		for (i = 3; i <= numOfMenuItems; i++) {
			GetMenuItemText(fpsMenuHdl, i, fpsStr);
			StringToNum(fpsStr, &fpsLong);
			
			if (fps == Long2Fix(fpsLong)) {
				SetControlValue(fpsPopupMenuCtrl, i);
				break;
			}
		}
	}
	
	#if TARGET_API_MAC_CARBON
		ShowWindow(GetDialogWindow(dialog));
	#else
		MacShowWindow(dialog);
	#endif
	
	do {
		ModalDialog(NULL, &itemHit);
	} while (!(itemHit == ok || itemHit == cancel));

	if (itemHit == ok) {
		// Get the selected color depth
		ctrlValue = GetControlValue(colorPopupMenuCtrl);
		
		switch (ctrlValue) {
		case 1:
			depth = 0;
			break;				
		case 3:
			depth = 1;
			break;				
		case 4:
			depth = 40; // 256 Grays
			break;
		case 5:
			depth = 8;
			break;				
		case 6:
			depth = 16;
			break;				
		case 7:
			depth = 32;
			break;
		default:
			break;
		}			
			
		// Get the selected frame rate
		ctrlValue = GetControlValue(fpsPopupMenuCtrl);
		
		if (ctrlValue == 1) {
			fps = 0;
		} else {
			#if TARGET_API_MAC_CARBON
				GetMenuItemText(GetControlPopupMenuHandle(fpsPopupMenuCtrl), ctrlValue, fpsStr);
			#else
				GetMenuItemText((**(PopupPrivateDataHandle)(**fpsPopupMenuCtrl).contrlData).mHandle, ctrlValue, fpsStr);
			#endif
			StringToNum(fpsStr, &fpsLong);
			fps = Long2Fix(fpsLong);		
		}	

		store->fps = fps;
		store->depth = depth;
	} else {
		*canceledPtr = true;
	}

bail:
	if (dialog)
		DisposeDialog(dialog);

	if (resRef != kResFileNotOpened)
		CloseComponentResFile(resRef);
		
	UseResFile(saveResFile);

	return err;
}
#endif // USE_NIB_FILE

#pragma mark -

//	Below is the structure of our exporter settings.
//  At the root of the QT atom container are two settings atoms
//
//  kEI_MovieExportSettingsColor
// 		kEI_MovieExportSettingsDepth	???
//
//	kQTSettingsTime
// 		kEI_MovieExportSettingsFPS		???
		
// MovieExportGetSettingsAsAtomContainer
//		Retrieves the current settings from the movie export component.
// Applications can call this function to obtain a correctly formatted atom container to use with MovieExportSetSettingsFromAtomContainer.
// This might be done after a call to MovieExportDoUserDialog, for example, to apply the user-obtained settings to a series of exports.
pascal ComponentResult EI_MovieExportGetSettingsAsAtomContainer(EI_MovieExportGlobals store, QTAtomContainer *settings)
{
	QTAtom			atom;
	Fixed			tempFPS;
	short			tempDepth;
	QTAtomContainer	theSettings = NULL;
	OSErr			err;

	if(!settings)
		return paramErr;
	
	err = QTNewAtomContainer(&theSettings);
	if (err) goto bail;
		
	// Add the color depth setting
	err = QTInsertChild(theSettings, kParentAtomIsContainer, kEI_MovieExportSettingsColor, 1, 0, 0, NULL, &atom);
	if (err) goto bail;

	tempDepth = EndianS16_NtoB(store->depth);
	
	err = QTInsertChild(theSettings, atom, kEI_MovieExportSettingsDepth, 1, 0, sizeof(tempDepth), &tempDepth, NULL);
	if (err) goto bail;
	
	// Add the frames per second setting
	err = QTInsertChild(theSettings, kParentAtomIsContainer, kQTSettingsTime, 1, 0, 0, NULL, &atom);
	if (err) goto bail;

	tempFPS = EndianU32_NtoB(store->fps);

	err = QTInsertChild(theSettings, atom, kEI_MovieExportSettingsFPS, 1, 0, sizeof(tempFPS), &tempFPS, NULL);
	if (err) goto bail;

bail:
	if (err && theSettings) {
		QTDisposeAtomContainer(theSettings);
		theSettings = NULL;
	}

	*settings = theSettings;	

	return err;
}

// MovieExportSetSettingsFromAtomContainer
// 		Sets the movie export component's current configuration from passed settings data.
// The atom container may contain atoms other than those expected by the particular component type
// or may be missing certain atoms. This function should use only those settings it understands.
// Some movie export components can treat sample descriptions as part of their settings in which case
// the component may not implement MovieExportSetSampleDescription and may require applications to pass
// in the SampleDescriptions using this function. 
pascal ComponentResult EI_MovieExportSetSettingsFromAtomContainer(EI_MovieExportGlobals store, QTAtomContainer settings)
{
	QTAtom	atom;
	OSErr	err = noErr;

	if(!settings)
		return paramErr;

	// Set color depth setting
	atom = QTFindChildByID(settings, kParentAtomIsContainer, kEI_MovieExportSettingsColor, 1, NULL);
	if (atom) {
		atom = QTFindChildByID(settings, atom, kEI_MovieExportSettingsDepth, 1, NULL);
		if (atom) {
			short tempDepth;
			
			err = QTCopyAtomDataToPtr(settings, atom, false, sizeof(tempDepth), &tempDepth, NULL);
			if (err) goto bail;
			
			store->depth = EndianS16_BtoN(tempDepth);
		}
	}

	// Set frames per second setting
	atom = QTFindChildByID(settings, kParentAtomIsContainer, kQTSettingsTime, 1, NULL);
	if (atom) {
		atom = QTFindChildByID(settings, atom, kEI_MovieExportSettingsFPS, 1, NULL);
		if (atom) {
			Fixed tempFPS;
			
			err = QTCopyAtomDataToPtr(settings, atom, false, sizeof(tempFPS), &tempFPS, NULL);
			if (err) goto bail;
			
			store->fps = EndianU32_BtoN(tempFPS);
		}
	}

bail:
	return err;
}

#pragma mark-

// MovieExportGetFileNameExtension
// 		Returns an OSType of the movie export components current file name extention. 
pascal ComponentResult EI_MovieExportGetFileNameExtension(EI_MovieExportGlobals store, OSType *extension)
{
#pragma unused(store)

	*extension = kEI_MovieExportFileNameExtention;
	
	return noErr;
}

// MovieExportGetShortFileTypeString
//		Returns a pascal file type string for the exported file.
pascal ComponentResult EI_MovieExportGetShortFileTypeString(EI_MovieExportGlobals store, Str255 typeString)
{
	return GetComponentIndString((Component)store->self, typeString, kEI_MovieExportShortFileTypeNamesResID, 1);
}

// MovieExportGetSourceMediaType
//		Return either the track type if the movie export component is track-specific or 0 if it is track-independent.
// This routine returns the same values that were previously stored in the componentManufacturer field of the ComponentDescription
// structure. This frees up the manufacturer field to be used for, well, um...the manufacturer. Movie data export components that
// implement this function must set the movieExportMustGetSourceMediaType flag. The mechanism is only used to find candidate components. 
// By implementing the routine, the export component's componentManufacturer field can be used to differentiate export components. 
// This routine returns an OSType value through its mediaType parameter, which is interpreted in exactly the same way that the
// componentManufacturer was interpreted prior to QT 3. If the export component requires a particular type of track to exist in a movie,
// it returns that media handler type (e.g., VideoMediaType ,SoundMediaType , etc.) through the mediaType argument. If the export component
// works for an entire movie, it returns 0 through this parameter. 
pascal ComponentResult EI_MovieExportGetSourceMediaType(EI_MovieExportGlobals store, OSType *mediaType)
{
#pragma unused(store)

	if (!mediaType)
		return paramErr;
	
	// video track specific
	*mediaType = VideoMediaType;

	return noErr;
}

#pragma mark-

// WriteFrame
//		This funtions calls the MovieExportGetDataProc to get some source data, sets up a decompression
// so we can get RGB pixels from the source then RLE compresses the source frame and writes it out.
static OSErr WriteFrame(OutputTrackPtr outputTrack, DataHandler dataH, long *offsetPtr)
{
	MovieExportGetDataParams gdp;
	CodecFlags				 whoCares;	
	OSErr					 err;
	
	// Set up the param block used by the MovieExportGetDataProc 
	gdp.recordSize = sizeof(MovieExportGetDataParams);		// Contains the total size in bytes of the MovieExportGetDataParams struct
	gdp.trackID = outputTrack->trackID;						// Specifies the data source. The trackID is returned when the data source is added in MovieExportAddDataSource 
	gdp.requestedTime = outputTrack->time;					// Specifies the time scale for this data source. This value is the same time scale that is passed to MovieExportAddDataSource 
	gdp.sourceTimeScale = outputTrack->sourceTimeScale;		// Specifies the time of the media requested by the exporter. The time scale is the same one specified when adding a data source with MovieExportAddDataSource
	gdp.actualTime = 0;										// Specifies the time actually referred to by the returned media data. This value is provided by MovieExportGetDataProc, and is usually the same as requestedTime.
	gdp.dataPtr = NULL;										// Contains a 32-bit pointer to the media data.
	gdp.dataSize = 0;										// Specifies the size in bytes of the data pointed to by dataPtr.
	gdp.desc = NULL;										// Contains a SampleDescriptionHandle describing the format of the data pointed to by dataPtr. For video data, this is an ImageDescriptionHandle. For sound data, this is a SoundDescriptionHandle.
	gdp.descType = 0;										// Specifies the type of SampleDescriptionHandle; For example, if SampleDescriptionHandle is an ImageDescriptionHandle, descType is set to VideoMediaType.
	gdp.descSeed = 0;										// Specifies which SampleDescriptionHandle represented by the current value of desc. Some data sources contain different kinds of data at different times.
															// For example, a video data source may contain both JPEG and uncompressed raw data. Whenever the data source switches from one type of data to another,
															//   the descSeed is changed to notify the exporter. In the case of an export operation that is providing its source data from a QuickTime movie track,
															//   descSeed is equal to the sample description index of the sample being returned. 
	gdp.requestedSampleCount = 0;							// Specifies the number of samples the exporter can work with. The function can return more or fewer samples than requested. For video, this value is always 1.
	gdp.actualSampleCount = 0;								// Specifies the number of samples actually returned. The MovieExportGetDataProc function must fill in this field. 
	gdp.durationPerSample = 1;								// Specifies the duration of every sample returned. For sound data, durationPerSample always contains 1. For video data, durationPerSample contains the duration of the returned sample,
															//   expressed in the time scale defined when the data source was created. 
	gdp.sampleFlags = 0;									// Contains flags for the returned samples. For example, mediaSampleNotSync. The MovieExportGetDataProc function must fill in this field. 

	// Call the MovieExportGetDataProc -  This function was passed to MovieExportAddDataSource
	// to define a new data source for the export operation and is used to request source media
	// data to be used in the export operation. For example, in a video export operation, frames
	// of video data (either compressed or uncompressed) are provided. In a sound export operation,
	// buffers of audio (either compressed or uncompressed) are provided. The data pointed to by
	// gdp.dataPtr must remain valid until the next call to the MovieExportGetDataProc function.
	// The MovieExportGetDataProc function is responsible for allocating and disposing of the memory
	// associated with this data pointer. 
	err = InvokeMovieExportGetDataUPP(outputTrack->refCon, &gdp, outputTrack->getDataProc);
	if (err) goto bail;

	// If it's not image data we can't handle it
	if (gdp.descType != VideoMediaType) {
		err = paramErr;
		goto bail;
	}

	// Good to go, set up a Decompression Sequence for the source
	if (gdp.descSeed != outputTrack->lastDescSeed) {
		MatrixRecord mr;
		short		 depth;
		short		 width, height;
		Rect		 dstRect, srcRect;
		
		// Initialize outputTrack...
		if (outputTrack->compressBuffer) {
			DisposePtr(outputTrack->compressBuffer);
			outputTrack->compressBuffer = NULL;
		}

		if (outputTrack->decompressSequence) {
			CDSequenceEnd(outputTrack->decompressSequence);
			outputTrack->decompressSequence = 0;
		}

		if (outputTrack->gw) {
			DisposeGWorld(outputTrack->gw);
			outputTrack->gw = NULL;
			outputTrack->hPixMap = NULL;
		}
		
		// ... using values returned in the ImageDescription from the call to
		// MovieExportGetDataParams or values set up previously by SetSettingsFromAtomContainer
		if (outputTrack->width == 0)
			width = (**(ImageDescriptionHandle)gdp.desc).width;
		else
			width = FixRound(outputTrack->width);
			
		if (outputTrack->height == 0)
			height = (**(ImageDescriptionHandle)gdp.desc).height;
		else
			height = FixRound(outputTrack->height);

		dstRect.left = 0;
		dstRect.top = 0;
		dstRect.right = width;
		dstRect.bottom = height;

		srcRect.left = 0;
		srcRect.top = 0;
		srcRect.right = (**(ImageDescriptionHandle)gdp.desc).width;
		srcRect.bottom = (**(ImageDescriptionHandle)gdp.desc).height;

		RectMatrix(&mr, &srcRect, &dstRect);
		
		if (outputTrack->depth == 0)
			depth = (**(ImageDescriptionHandle)gdp.desc).depth;
		else
			depth = outputTrack->depth;
			
		// Depth may be 24 if the 'default' (i.e. BEST) preference was chosen resulting
		// in the depth setting coming from the ImageDescription -- normally our settings
		// would result in 1, 8, 16 or 32 - Because we always need to write from 32bit
		// data for both 24 or 32 (a, r, g, b) (see EI_Image.h), in this case we need to
		// make sure the source we compress from is 32bit. On Macintosh this isn't really
		// an issue as k24RGBPixelFormat is just a synonym for k32ARGBPixelFormat with no
		// alpha -- On Windows however, asking for a k24RGBPixelFormat GWorld results in
		// 24bit PixMap data which is no good to us -- so, we make it easy and always ask
		// for k32ARGBPixelFormat when presented with 24.
		if (k24RGBPixelFormat == depth)
			depth = k32ARGBPixelFormat;
		
		// Create a GWorld for the approprate depth property
		err = QTNewGWorld(&outputTrack->gw, depth, &dstRect, NULL, NULL, kICMTempThenAppMemory);
		if (err || NULL == outputTrack->gw) goto bail;
		
		outputTrack->hPixMap = GetGWorldPixMap(outputTrack->gw);
		
		LockPixels(outputTrack->hPixMap);
		
		err = DecompressSequenceBeginS(&outputTrack->decompressSequence, (ImageDescriptionHandle)gdp.desc, gdp.dataPtr,
									   gdp.dataSize, outputTrack->gw, NULL, NULL, &mr, ditherCopy, NULL, 0, codecHighQuality, NULL);
		if (err) goto bail;
		
		// Allocate memory enough to store maximum compressed data
		outputTrack->compressBuffer = NewPtrClear(width * height * depth * 2);
		err = MemError();
		if (err) goto bail;
		
		outputTrack->lastDescSeed = gdp.descSeed;
	}
	
	// At this point we have some source data, a destination GWorld and a DecompressionSequence so we can decompress
	// the source into this GWorld to give us some pixels. We then take the pixels from the GWorld and RLE compress
	// into the CompressBuffer -- this is the image data written out to the file.

	// Decompress a frame to our GWorld
	err = DecompressSequenceFrameS(outputTrack->decompressSequence, gdp.dataPtr, gdp.dataSize, 0, &whoCares, NULL);
	if (err) goto bail;
	
	// Compress the pixels from the GWorld so we get our RLE compressed image data
	err = CompressRLE(outputTrack->hPixMap, outputTrack->compressBuffer, &outputTrack->compressBufferSize);
	if (err) goto bail;

	// Write the image frame header
	err = WriteImageFrameHeader(outputTrack, dataH, offsetPtr);
	if (err) goto bail;

	// Write the colorTable
	err = WriteColorTable((**(outputTrack->hPixMap)).pmTable, dataH, offsetPtr);
	if (err) goto bail;
	
	// Write the image data
	err = DataHWrite(dataH, outputTrack->compressBuffer, *offsetPtr, outputTrack->compressBufferSize, NULL, 0);
	if (err) goto bail;

	// Advance the offset
	*offsetPtr += outputTrack->compressBufferSize;

	// Advance a frame number
	outputTrack->numOfFrames++;

	// Advance the time
	if ( outputTrack->fps == 0 )
		outputTrack->time += gdp.durationPerSample;
	else
		outputTrack->time = FixDiv(outputTrack->numOfFrames * outputTrack->sourceTimeScale, outputTrack->fps);

bail:		
	return err;
}

// WriteImageFrameHeader
//		The routine writes the image frame header out to the file, see 'EI_Image.h'.
static OSErr WriteImageFrameHeader(OutputTrackPtr outputTrack, DataHandler dataH, long *offsetPtr)
{
	ImageFrame	  imgFrame;		
	CTabHandle	  cTabHdl = (**(outputTrack->hPixMap)).pmTable;
	OSType		  pixelFormat = GETPIXMAPPIXELFORMAT(*(outputTrack->hPixMap));
	short		  depth = QTGetPixelSize(pixelFormat);
	QTFloatSingle frameTime = (QTFloatSingle)(outputTrack->time / (QTFloatSingle)outputTrack->sourceTimeScale);
	OSErr		  err;

	// Endian flipping floats is damn ugly
	*(UInt32 *)&imgFrame.frameTime = EndianU32_NtoB(*(UInt32 *)&frameTime);
	imgFrame.frameRect.top = 0;						
	imgFrame.frameRect.left = 0;
	#if TARGET_API_MAC_CARBON
	{
		Rect theRect;	
		GetPortBounds(outputTrack->gw, &theRect);
		imgFrame.frameRect.bottom = EndianU16_NtoB(theRect.bottom);						
		imgFrame.frameRect.right = EndianU16_NtoB(theRect.right);
	}
	#else					
		imgFrame.frameRect.bottom = EndianU16_NtoB(outputTrack->gw->portRect.bottom);						
		imgFrame.frameRect.right = EndianU16_NtoB(outputTrack->gw->portRect.right);
	#endif					
	imgFrame.frameBitDepth = (depth == 32 ? 24 : depth);
	imgFrame.frameType = (depth > 8 ? 0 : 1);
	imgFrame.framePackRect = imgFrame.frameRect;
	imgFrame.framePacking = 1;
	imgFrame.frameAlpha = (depth == 32 ? 8 : 0);
	imgFrame.frameSize = EndianU32_NtoB(outputTrack->compressBufferSize);
	imgFrame.framePalettes = EndianU16_NtoB((**cTabHdl).ctSize + 1);
	imgFrame.frameBackground = EndianU16_NtoB((**cTabHdl).ctSize);
	
	err = DataHWrite(dataH, (Ptr)&imgFrame, *offsetPtr, sizeof(imgFrame), NULL, 0);
	if ( err ) goto bail;

	// Advance the offset
	*offsetPtr += sizeof(imgFrame);

bail:
	return err;
}

// WriteColorTable
//		This rountine writes out the color table and get called after the image frame header is written.
static OSErr WriteColorTable(CTabHandle cTabHdl, DataHandler dataH, long *offsetPtr)
{
	PackedColor	colors[256];
	ColorSpec	*ctTable = (**cTabHdl).ctTable;
	UInt16		i, numOfColorEntries = (**cTabHdl).ctSize + 1;	// ctSize is number of entries in array of ColorSpec records minus 1
	OSErr		err;

	for (i = 0; i < numOfColorEntries; i++) {
		colors[i].red = (UInt8)(ctTable[i].rgb.red >> 8);
		colors[i].green = (UInt8)(ctTable[i].rgb.green >> 8);
		colors[i].blue = (UInt8)(ctTable[i].rgb.blue >> 8);
	}
	
	err = DataHWrite(dataH, (Ptr)colors, *offsetPtr, sizeof(PackedColor) * numOfColorEntries, NULL, 0);
	if (err) goto bail;

	// Advance the offset
	*offsetPtr += sizeof(PackedColor) * numOfColorEntries;

bail:
	return err;
}

// EmptyOutputTrack
//		Dispose and clean up what was allocated in AddDataSource.
static void EmptyOutputTrack(OutputTrackPtr outputTrack)
{
	if (outputTrack->compressBuffer) {
		DisposePtr(outputTrack->compressBuffer);
		outputTrack->compressBuffer = NULL;
	}

	if ( outputTrack->decompressSequence ) {
		CDSequenceEnd(outputTrack->decompressSequence);
		outputTrack->decompressSequence = NULL;
	}

	if (outputTrack->gw ) {
		DisposeGWorld(outputTrack->gw);
		outputTrack->gw = NULL;
	}

	outputTrack->width = 0;
	outputTrack->height = 0;
	outputTrack->fps = 0;
	outputTrack->depth = 0;
	outputTrack->time = 0;
	outputTrack->numOfFrames = 0;
	outputTrack->compressBufferSize = 0;
	outputTrack->lastDescSeed = 0;
}

// ConfigureQuickTimeMovieExporter
// 		Since the property proc we call may be a proc returned from MovieExportNewGetDataAndPropertiesProcs,
// configure the QuickTime Movie Exporter so that it's properties match what we have as our defaults.	
static OSErr ConfigureQuickTimeMovieExporter(EI_MovieExportGlobals store)
{
	SCTemporalSettings temporalSettings;
	SCSpatialSettings  spatialSettings;
	ComponentInstance  stdImageCompression = NULL;
	QTAtomContainer	   movieExporterSettings = NULL;
	OSErr			   err;

	// Open the Standard Compression component
	err = OpenADefaultComponent(StandardCompressionType, StandardCompressionSubType, &stdImageCompression);
	if (err) goto bail;
	
	// Set the frame rate
	temporalSettings.frameRate = store->fps;
	err = SCSetInfo(stdImageCompression, scTemporalSettingsType, &temporalSettings);
	if (err) goto bail;

	// Set the depth
	spatialSettings.depth = store->depth;
	err = SCSetInfo(stdImageCompression, scSpatialSettingsType, &spatialSettings);
	if (err) goto bail;

	// Get the settings atom
	err = SCGetSettingsAsAtomContainer(stdImageCompression, &movieExporterSettings);
	if (err) goto bail;
	
	// Set the compression settings for the QT Movie Exporter
	err = MovieExportSetSettingsFromAtomContainer(store->quickTimeMovieExporter, movieExporterSettings);
	
bail:
	if (stdImageCompression)
		CloseComponent(stdImageCompression);
	
	if (movieExporterSettings)
		DisposeHandle(movieExporterSettings);
		
	return err;
}

// GetExportProperty
//		This routine calls the MovieExportGetPropertyProc to set up our output properties
static void GetExportProperty(EI_MovieExportGlobals store)
{
	SCTemporalSettings	temporalSettings;
	SCSpatialSettings	spatialSettings;
	Fixed				width, height;
	OutputTrackPtr		outputTrack = store->outputTrack;
	
	// Get the defaults
	outputTrack->fps = store->fps;
	outputTrack->depth = store->depth;
	
 	// The getPropertyProc may override some values
 	if (InvokeMovieExportGetPropertyUPP(outputTrack->refCon, outputTrack->trackID, scTemporalSettingsType, &temporalSettings, outputTrack->getPropertyProc) == noErr)
		outputTrack->fps = temporalSettings.frameRate;

	if (InvokeMovieExportGetPropertyUPP(outputTrack->refCon, outputTrack->trackID, scSpatialSettingsType, &spatialSettings, outputTrack->getPropertyProc) == noErr)
		outputTrack->depth = spatialSettings.depth;

	if (InvokeMovieExportGetPropertyUPP(outputTrack->refCon, outputTrack->trackID, movieExportWidth, &width, outputTrack->getPropertyProc) == noErr)
		outputTrack->width = width;
	
	if (InvokeMovieExportGetPropertyUPP(outputTrack->refCon, outputTrack->trackID, movieExportHeight, &height, outputTrack->getPropertyProc) == noErr)
		outputTrack->height = height;
}

#pragma mark-

// CompressRLE
//		Main compress routine, this function will call the appropriate RLE compression
// method depending on the pixel depth of the source image.
static OSErr CompressRLE(PixMapHandle pixMapHdl, Ptr compressBuffer, Size *compressBufferSizePtr)
{
	Handle hdl = NULL;
	Ptr	   tempPtr = NULL;
	Ptr	   pixBaseAddr = GetPixBaseAddr(pixMapHdl);
	OSType pixelFormat = GETPIXMAPPIXELFORMAT(*pixMapHdl);
	short  depth = QTGetPixelSize(pixelFormat);
	long   rowBytes = QTGetPixMapHandleRowBytes(pixMapHdl);
	short  width = (**pixMapHdl).bounds.right - (**pixMapHdl).bounds.left;
	short  i, height = (**pixMapHdl).bounds.bottom - (**pixMapHdl).bounds.top;
	Size   widthByteSize = (depth * width + 7) >> 3;
	OSErr  err = noErr;

	// Make a temp buffer for the source
	hdl = NewHandleClear(height * widthByteSize);
	err = MemError();
	if (err) goto bail;
	
	HLock(hdl);
	
	tempPtr = *hdl;
	
	// Get rid of row bytes padding
	for (i = 0; i < height; i++) {
		BlockMoveData(pixBaseAddr, tempPtr, widthByteSize);
		
		tempPtr += widthByteSize;
		pixBaseAddr += rowBytes;
	}

	// Compress
	switch (depth) {
	case 1:
		CompressRLE8((UInt8 *)*hdl, height * widthByteSize, compressBuffer, compressBufferSizePtr);
		break;
	case 8:
		CompressRLE8((UInt8 *)*hdl, height * widthByteSize, compressBuffer, compressBufferSizePtr);
		break;
	case 16:
		CompressRLE16((UInt16 *)*hdl, height * (widthByteSize >> 1), compressBuffer, compressBufferSizePtr);
		break;
	case 32:
		CompressRLE32((UInt32 *)*hdl, height * (widthByteSize >> 2), compressBuffer, compressBufferSizePtr);
		break;
	}
	
bail:
	if (hdl)
		DisposeHandle(hdl);

	return err;
}

// CompressRLE8
static void CompressRLE8(UInt8 *srcPtr, Size srcSize, Ptr compressBuffer, Size *compressBufferSizePtr)
{
	UInt8	   prevPixel, currentPixel;
	UInt8	   sameCharCount;
	UInt8	   diffCharCount;
	RLE8Packet *packetPtr;
		
	// Initialize some stuff
	sameCharCount = 1;
	diffCharCount = 0;

	packetPtr = (RLE8Packet *)compressBuffer;

	prevPixel = *srcPtr++;

	while (srcSize >= 2 ) {
		currentPixel = *srcPtr++;
		
		if (prevPixel == currentPixel) {
			// If diffCharCount > 0, we are holding pixels which should be read literally
			if (diffCharCount > 0) {
				packetPtr->opcode = 127 + diffCharCount;
				
				packetPtr = (RLE8Packet *)((Ptr)packetPtr + offsetof(RLE8Packet, pixelData[diffCharCount]));
				diffCharCount = 0;
			}
			
			if (sameCharCount < 128) {
				sameCharCount++;
			} else {
				packetPtr->opcode = sameCharCount - 1;
				packetPtr->pixelData[0] = prevPixel;
				
				packetPtr = (RLE8Packet *)((Ptr)packetPtr + offsetof(RLE8Packet, pixelData[1]));
				sameCharCount = 1;
			}
		} else {
			// If sameCharCount > 1, we are holding pixels which should be read repeatedly
			if (sameCharCount > 1) {
				packetPtr->opcode = sameCharCount - 1;
				packetPtr->pixelData[0] = prevPixel;
								
				packetPtr = (RLE8Packet *)((Ptr)packetPtr + offsetof(RLE8Packet, pixelData[1]));
				sameCharCount = 1;
			} else {
				packetPtr->pixelData[diffCharCount++] = prevPixel;
				
				if (diffCharCount == 128) {
					packetPtr->opcode = 127 + diffCharCount;
					
					packetPtr = (RLE8Packet *)((Ptr)packetPtr + offsetof(RLE8Packet, pixelData[diffCharCount]));
					diffCharCount = 0;
				}
			}
			
			prevPixel = currentPixel;
		}
		
		srcSize--;
	}
	
	// If sameCharCount > 1, we are holding pixels which should be read repeatedly
	// If not so, we are holding pixels which should be read literally
	if (sameCharCount > 1) {
		packetPtr->opcode = sameCharCount - 1;
		packetPtr->pixelData[0] = prevPixel;

		packetPtr = (RLE8Packet *)((Ptr)packetPtr + offsetof(RLE8Packet, pixelData[1]));
	} else {
		packetPtr->pixelData[diffCharCount++] = prevPixel;
		packetPtr->opcode = 127 + diffCharCount;
		
		packetPtr = (RLE8Packet *)((Ptr)packetPtr + offsetof(RLE8Packet, pixelData[diffCharCount]));
	}
		
	*compressBufferSizePtr = (Ptr)packetPtr - compressBuffer;
}

// CompressRLE16
static void CompressRLE16(UInt16 *srcPtr, Size srcSize, Ptr compressBuffer, Size *compressBufferSizePtr)
{
	UInt16		prevPixel, currentPixel;
	UInt8		sameCharCount;
	UInt8		diffCharCount;
	RLE16Packet	*packetPtr;
		
	// Initialize some stuff
	sameCharCount = 1;
	diffCharCount = 0;

	packetPtr = (RLE16Packet *)compressBuffer;

	prevPixel = *srcPtr++;

	while (srcSize >= 2) {
		currentPixel = *srcPtr++;
		
		if (prevPixel == currentPixel) {
			// If diffCharCount > 0, we are holding pixels which should be read literally
			if (diffCharCount > 0) {
				packetPtr->opcode = 127 + diffCharCount;
				
				packetPtr = (RLE16Packet *)((Ptr)packetPtr + offsetof(RLE16Packet, pixelData[diffCharCount]));
				diffCharCount = 0;
			}
			
			if (sameCharCount < 128) {
				sameCharCount++;
			} else {
				packetPtr->opcode = sameCharCount - 1;
				packetPtr->pixelData[0] = prevPixel;

				packetPtr = (RLE16Packet *)((Ptr)packetPtr + offsetof(RLE16Packet, pixelData[1]));
				sameCharCount = 1;
			}
		} else {
			// If sameCharCount > 1, we are holding pixels which should be read repeatedly
			if (sameCharCount > 1)
			{
				packetPtr->opcode = sameCharCount - 1;
				packetPtr->pixelData[0] = prevPixel;

				packetPtr = (RLE16Packet *)((Ptr)packetPtr + offsetof(RLE16Packet, pixelData[1]));
				sameCharCount = 1;
			} else {
				packetPtr->pixelData[diffCharCount++] = prevPixel;
				
				if (diffCharCount == 128)
				{
					packetPtr->opcode = 127 + diffCharCount;
	
					packetPtr = (RLE16Packet *)((Ptr)packetPtr + offsetof(RLE16Packet, pixelData[diffCharCount]));
					diffCharCount = 0;
				}
			}
			
			prevPixel = currentPixel;
		}
		
		srcSize--;
	}
	
	// If sameCharCount > 1, we are holding pixels which should be read repeatedly
	// If not so, we are holding pixels which should be read literally
	if (sameCharCount > 1) {
		packetPtr->opcode = sameCharCount - 1;
		packetPtr->pixelData[0] = prevPixel;

		packetPtr = (RLE16Packet *)((Ptr)packetPtr + offsetof(RLE16Packet, pixelData[1]));
	} else {
		packetPtr->pixelData[diffCharCount++] = prevPixel;
		packetPtr->opcode = 127 + diffCharCount;

		packetPtr = (RLE16Packet *)((Ptr)packetPtr + offsetof(RLE16Packet, pixelData[diffCharCount]));
	}
		
	*compressBufferSizePtr = (Ptr)packetPtr - compressBuffer;
}

// CompressRLE32
static void CompressRLE32(UInt32 *srcPtr, Size srcSize, Ptr compressBuffer, Size *compressBufferSizePtr)
{
	UInt32		prevPixel, currentPixel;
	UInt8		sameCharCount;
	UInt8		diffCharCount;
	RLE32Packet	*packetPtr;
		
	// Initialize some stuff
	sameCharCount = 1;
	diffCharCount = 0;

	packetPtr = (RLE32Packet *)compressBuffer;

	prevPixel = *srcPtr++;

	while (srcSize >= 2) {
		currentPixel = *srcPtr++;
		
		if (prevPixel == currentPixel) {
			// If diffCharCount > 0, we are holding pixels which should be read literally
			if (diffCharCount > 0) {
				packetPtr->opcode = 127 + diffCharCount;

				packetPtr = (RLE32Packet *)((Ptr)packetPtr + offsetof(RLE32Packet, pixelData[diffCharCount]));
				diffCharCount = 0;
			}
			
			if (sameCharCount < 128) {
				sameCharCount++;
			} else {
				packetPtr->opcode = sameCharCount - 1;
				packetPtr->pixelData[0] = prevPixel;

				packetPtr = (RLE32Packet *)((Ptr)packetPtr + offsetof(RLE32Packet, pixelData[1]));
				sameCharCount = 1;
			}
		} else {
			// If sameCharCount > 1, we are holding pixels which should be read repeatedly
			if (sameCharCount > 1) {
				packetPtr->opcode = sameCharCount - 1;
				packetPtr->pixelData[0] = prevPixel;

				packetPtr = (RLE32Packet *)((Ptr)packetPtr + offsetof(RLE32Packet, pixelData[1]));
				sameCharCount = 1;
			} else {
				packetPtr->pixelData[diffCharCount++] = prevPixel;
				
				if (diffCharCount == 128) {
					packetPtr->opcode = 127 + diffCharCount;

					packetPtr = (RLE32Packet *)((Ptr)packetPtr + offsetof(RLE32Packet, pixelData[diffCharCount]));
					diffCharCount = 0;
				}
			}
			
			prevPixel = currentPixel;
		}
		
		srcSize--;
	}
	
	// If sameCharCount > 1, we are holding pixels which should be read repeatedly
	// If not so, we are holding pixels which should be read literally
	if (sameCharCount > 1) {
		packetPtr->opcode = sameCharCount - 1;
		packetPtr->pixelData[0] = prevPixel;

		packetPtr = (RLE32Packet *)((Ptr)packetPtr + offsetof(RLE32Packet, pixelData[1]));
	} else {
		packetPtr->pixelData[diffCharCount++] = prevPixel;
		packetPtr->opcode = 127 + diffCharCount;
		
		packetPtr = (RLE32Packet *)((Ptr)packetPtr + offsetof(RLE32Packet, pixelData[diffCharCount]));
	}
		
	*compressBufferSizePtr = (Ptr)packetPtr - compressBuffer;
}

#pragma mark -

// When building the *Application Version Only* make our component available for use by applications (or other clients).
// Once the Component Manager has registered a component, applications can find and open the component using standard
// Component Manager routines.
#if !STAND_ALONE && !TARGET_OS_WIN32
void EI_MovieExporterRegister(void);
void EI_MovieExporterRegister(void)
{
	ComponentDescription foo;	
	#if !TARGET_API_MAC_CARBON
		ComponentRoutineUPP componentEntryPoint = NewComponentRoutineProc(EI_MovieExportComponentDispatch);
	#else
		ComponentRoutineUPP componentEntryPoint = NewComponentRoutineUPP((ComponentRoutineProcPtr)EI_MovieExportComponentDispatch);
	#endif

  	foo.componentType = MovieExportType;
  	foo.componentSubType = FOUR_CHAR_CODE('EIDI');
  	foo.componentManufacturer = kAppleManufacturer;
  	foo.componentFlags = canMovieExportFiles | canMovieExportFromProcedures | movieExportMustGetSourceMediaType | hasMovieExportUserInterface | canMovieExportValidateMovie;
  	foo.componentFlagsMask = 0;

	RegisterComponent(&foo, componentEntryPoint, 0, NULL, NULL, NULL);
}
#endif // !STAND_ALONE && !TARGET_OS_WIN32