EI_MovieExport/EI_MovieExport.c

/*
    File:       EI_MovieExport.c
    
    Description: QuickTime component version information
 
    Author:     QuickTime Enginering, dts
    
    Copyright:  © Copyright 1999 - 2005 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):
       <3>      06/22/05    dts         added component property handling for QuickTime 7
       <2>      04/21/03    dts         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;
    SInt16                      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;
    SInt16              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 QT_BASENAME()               MOVIEEXPORT_BASENAME()
#define QT_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/ImageCompression.k.h>   // for ComponentProperty selectors
    #include <QuickTime/ComponentDispatchHelper.c>
#else
    #include <Components.k.h>
    #include <QuickTimeComponents.k.h>
    #include <ImageCompression.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;
}
 
#if __MACH__
// Component data types, these are not yet public but they're just a FourCC
// indicating the value type - turn this off if one day they do go public
#if 1
enum {
    kComponentDataTypeFixed     = 'fixd', // Fixed (same as typeFixed )
    kComponentDataTypeInt16     = 'shor', // SInt16 (same as typeSInt16) 
    kComponentDataTypeCFDataRef = 'cfdt'  // CFDataRef 
};
#endif
 
// Component Properties specific to the Electric Image Movie Exporter component
enum {
    kComponentPropertyClass_ElectricImage = 'EIDI', // Electric Image Component property class
    
    kElectricImageMovieExporterPropertyID_FramesPerSecond = 'frps', // value is a fixed
    kElectricImageMovieExporterPropertyID_Depth = 'dpth'            // value is a SInt16
};
 
static const ComponentPropertyInfo kExportProperties[] = 
{
    { kComponentPropertyClassPropertyInfo,   kComponentPropertyInfoList,                            kComponentDataTypeCFDataRef, sizeof(CFDataRef), kComponentPropertyFlagCanGetNow | kComponentPropertyFlagValueIsCFTypeRef | kComponentPropertyFlagValueMustBeReleased },
    { kComponentPropertyClass_ElectricImage, kElectricImageMovieExporterPropertyID_FramesPerSecond, kComponentDataTypeFixed,     sizeof(Fixed),     kComponentPropertyFlagCanGetNow | kComponentPropertyFlagCanSetNow },
    { kComponentPropertyClass_ElectricImage, kElectricImageMovieExporterPropertyID_Depth,           kComponentDataTypeInt16,     sizeof(SInt16),    kComponentPropertyFlagCanGetNow | kComponentPropertyFlagCanSetNow }
};
 
// GetComponentPropertyInfo
// Component Property Info request - Optional but good practice for QuickTime 7 forward
// Returns information about the properties of a component
pascal ComponentResult EI_MovieExportGetComponentPropertyInfo(EI_MovieExportGlobals   store,
                                                               ComponentPropertyClass inPropClass,
                                                               ComponentPropertyID    inPropID,
                                                               ComponentValueType     *outPropType,
                                                               ByteCount              *outPropValueSize,
                                                               UInt32                 *outPropertyFlags)
{
#pragma unused (store)
 
    // we support kComponentPropertyClassPropertyInfo and our own kComponentPropertyClass_ElectricImage('EIDI') class
    ComponentResult err = kQTPropertyNotSupportedErr;
    
    switch (inPropClass) {
    case kComponentPropertyClassPropertyInfo:
        switch (inPropID) {
        case kComponentPropertyInfoList:
            if (outPropType) *outPropType = kExportProperties[0].propType;
            if (outPropValueSize) *outPropValueSize = kExportProperties[0].propSize;
            if (outPropertyFlags) *outPropertyFlags = kExportProperties[0].propFlags;
            err = noErr;
            break;
        default:
            break;
        }
        break;
    case kComponentPropertyClass_ElectricImage:
        switch (inPropID) {
        case kElectricImageMovieExporterPropertyID_FramesPerSecond:
            if (outPropType) *outPropType = kExportProperties[1].propType;
            if (outPropValueSize) *outPropValueSize = kExportProperties[1].propSize;
            if (outPropertyFlags) *outPropertyFlags = kExportProperties[1].propFlags;
            err = noErr;
            break;
        case kElectricImageMovieExporterPropertyID_Depth:
            if (outPropType) *outPropType = kExportProperties[2].propType;
            if (outPropValueSize) *outPropValueSize = kExportProperties[2].propSize;
            if (outPropertyFlags) *outPropertyFlags = kExportProperties[2].propFlags;
            err = noErr;
            break;
        default:
            break;
        }
    default:
        break;
    }
        
    return err;
}
 
// GetComponentProperty
// Get Component Property request - Optional but good practice for QuickTime 7 forward
// Returns the value of a specific component property
pascal ComponentResult EI_MovieExportGetComponentProperty(EI_MovieExportGlobals  store,
                                                          ComponentPropertyClass inPropClass,
                                                          ComponentPropertyID    inPropID,
                                                          ByteCount              inPropValueSize,
                                                          ComponentValuePtr      outPropValueAddress,
                                                          ByteCount              *outPropValueSizeUsed)
{
    ByteCount size = 0;
    UInt32 flags = 0;
    CFDataRef *outPropCFDataRef;
    
    ComponentResult err = noErr;
    
    // sanity check
    if (NULL == outPropValueAddress) return paramErr;
    
    err = QTGetComponentPropertyInfo(store->self, inPropClass, inPropID, NULL, &size, &flags);
    if (err) goto bail;
    
    if (size > inPropValueSize) return kQTPropertyBadValueSizeErr;
    
    if (flags & kComponentPropertyFlagCanGetNow) {
        switch (inPropID) {
        case kComponentPropertyInfoList:
            outPropCFDataRef = (CFDataRef *)outPropValueAddress;
            *outPropCFDataRef = CFDataCreate(kCFAllocatorDefault, (UInt8 *)((ComponentPropertyInfo *)kExportProperties), sizeof(kExportProperties));
            if (outPropValueSizeUsed) *outPropValueSizeUsed = size;
            break;
        case kElectricImageMovieExporterPropertyID_FramesPerSecond:
            *(FixedPtr)outPropValueAddress = store->fps;
            if (outPropValueSizeUsed) *outPropValueSizeUsed = size;
            break;
        case kElectricImageMovieExporterPropertyID_Depth:
            *(SInt16 *)outPropValueAddress = store->depth;
            if (outPropValueSizeUsed) *outPropValueSizeUsed = size;
        default:
            break;
        }
    }
    
bail:
    return err;
}
 
// SetComponentProperty
// Set Component Property request - Optional but good practice for QuickTime 7 forward
// Sets the value of a specific component property
pascal ComponentResult EI_MovieExportSetComponentProperty(EI_MovieExportGlobals  store,
                                                          ComponentPropertyClass inPropClass,
                                                          ComponentPropertyID    inPropID,
                                                          ByteCount              inPropValueSize,
                                                          ConstComponentValuePtr inPropValueAddress)
{
    ByteCount size = 0;
    OSType type;
    UInt32 flags;
    SInt16 tempDepth;
        
    ComponentResult err = noErr;
        
    // validate the caller's params    
    err = QTGetComponentPropertyInfo(store->self, inPropClass, inPropID, &type, &size, &flags);
    if (err) goto bail;
    
    if (size != inPropValueSize) return kQTPropertyBadValueSizeErr;
    if (NULL == inPropValueAddress) return paramErr;
    
    if (!(flags & kComponentPropertyFlagCanSetNow)) return kQTPropertyReadOnlyErr;
    
    switch (inPropID) {
    case kElectricImageMovieExporterPropertyID_FramesPerSecond:
        store->fps = *(FixedPtr)inPropValueAddress;
        break;
    case kElectricImageMovieExporterPropertyID_Depth:
        tempDepth = *(SInt16 *)inPropValueAddress;
        switch (tempDepth) {
        case 0:
        case 1:
        case 40:    // 256 Grays
        case 8:
        case 16:
        case 32:
            store->depth = tempDepth;
            break;
        default:
            err = paramErr;
            break;
        }
        break;
    default:
        break;
    }
    
bail:
    return err;
}
#endif
 
#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;
    SInt16     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;
    SInt16      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;
    SInt16         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;
    SInt16          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) {
            SInt16 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;
        SInt16       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));
    SInt16        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 = 0;
    }
 
    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);
    SInt16  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