ConvertToMovie Jr.c

/*
    File:       ConvertToMovie Jr.c
 
    Contains:   Movie Recompression Routines.
                ConvertToMovie Jr. is an example application to recompress QuickTime
                movies and is based heavily on the venerable ConvertToMovieª.  It shows
                how to use various parts of the Movie Toolbox, Image Compression Manager,
                and Standard Compression Component.
    
                This code is not intended to show coding style, user interface
                or graceful error handling.
 
    Written by:     
 
    Copyright:  Copyright © 1991-2001 by 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):
                11/05/2001  srk         fully carbonized
                7/28/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
 
 
//  
 
 
 
// INCLUDES
#ifndef __CONVERTTOMOVIEJR__
#include "ConvertToMovieJr.h"
#endif
 
// DEFINES
#define BailNil(n) if (!n) goto bail;
#define BailError(n)    if (n) goto bail;
 
#ifdef  TARGET_API_MAC_CARBON
#define kTypeListCount  2
#endif
 
ConstStr255Param kPutFileName = "\pUntitled.mov";
ConstStr255Param kPutFilePrompt =   "\pSave new movie file as:";
 
// FUNCTION PROTOTYPES
pascal short
DefaultsHookProc(DialogPtr theDialog, short itemHit, void *params, long refcon);
 
 
// MAIN
int main()
{
    short       frameNum;
    OSErr       result;
    short       abort;
    short       firstPass = true;
    
    Point       where;
    OSType      myTypeList[kTypeListCount] = {kQTFileTypeMovie, kQTFileTypeQuickTimeImage};
    short       movieRefNum = 0;
    FSSpec      theFSSpec,dstFile;
    Boolean     theIsSelected;
    Boolean     theIsReplacing;
    
    ComponentInstance ci;
    
    Movie       srcMovie = nil;
    Rect        srcRect, portRect;
    GWorldPtr   srcGWorld = nil;
    Movie       dstMovie = nil;
    Track       dstTrack;
    Media       dstMedia;
    long        frameCount;
    TimeValue   curMovieTime;
    
    WindowRef   progressWindow = nil;
    
    ImageSequence   dstSeqID;
    
    ImageDescription **idh;
    SCTemporalSettings ts;
    SCDataRateSettings ds;
    
    GDHandle    saveWinGDH;
    CGrafPtr    saveWinPort;
    
    FlushEvents(0xffff,0);
    InitCursor();
 
    //---------------------------------------------------------------------------------------------
    //  Initialize the Movie Toolbox.
    //  Make sure you do this.  It's very easy to forget about.
    //---------------------------------------------------------------------------------------------
    
    result = EnterMovies();
    BailError(result);
    
    //---------------------------------------------------------------------------------------------
    //  Open the Standard Compression component and adjust it to meet our needs.
    //---------------------------------------------------------------------------------------------
    
    ci = OpenDefaultComponent(StandardCompressionType,StandardCompressionSubType);
    BailNil(ci);
    
    //  Turn off best depth option in the compression dialog.
    //  Because all of our buffering is done at 32-bits, regardless
    //  of the depth of the source data, best depth isn't very useful
    //  as it will always choose 32-bit.
    //
    //  A more ambitious approach would be to loop through each of the
    //  video sample descriptions in each of the video tracks looking
    //  for the deepest depth, and using that for the best depth.
    //  Better yet, we could find out which compressors were used
    //  and set one of those as the default in the compression dialog.
    
    {
        long flags;
        
        SCGetInfo(ci,scPreferenceFlagsType,&flags);
        flags &= ~scShowBestDepth;
        SCSetInfo(ci,scPreferenceFlagsType,&flags);
    }
    
    //  Because we are recompressing a movie that may have a variable frame rate,
    //  we want to allow the user to leave the frame rate text field blank.  We
    //  can then preserve the frame durations of the source movie.  If they enter
    //  a number we will resample the movie at a new frame rate.  If we don't clear
    //  this flag, the compression dialog will not allow zero in the frame rate field.
    //
    //  NOTE: Setting this flag could have been done above when we cleared the
    //  scShowBestDepth flag.  It is done separately for clarity.
    
    {
        long flags;
        
        SCGetInfo(ci,scPreferenceFlagsType,&flags);
        flags |= scAllowZeroFrameRate;
        SCSetInfo(ci,scPreferenceFlagsType,&flags);
    }
    
 
    //---------------------------------------------------------------------------------------------
    //  Ask for a source movie file using Standard Preview.
    //  Use (-2,-2) to center on the best device.
    //---------------------------------------------------------------------------------------------
 
NextPass:
        {
         result = GetOneFileWithPreview(kTypeListCount, myTypeList, &theFSSpec, NULL);
        if (result == userCanceledErr)
            goto bail;
        }
 
    //---------------------------------------------------------------------------------------------
    //  Open up the movie file and get a movie from it.
    //---------------------------------------------------------------------------------------------
    
    {
        short   refnum;
        
        //  Open a movie file using the FSSpec and create a movie from that file.
        
        result = OpenMovieFile(&theFSSpec, &refnum, 0);
        BailError(result);
        
        result = NewMovieFromFile(&srcMovie,refnum, nil, nil,newMovieActive, nil);
        BailError(result);
        
        //  We're done with the movie file.
        
        CloseMovieFile(refnum);
    }
    
    
    //---------------------------------------------------------------------------------------------
    //  Count the number of video "frames" in the movie by stepping through all of the
    //  video "interesting times", or in other words, the places where the movie displays
    //  a new video sample. The time between these interesting times is not necessarily constant.
    //---------------------------------------------------------------------------------------------
    
    {
        OSType  whichMediaType = VIDEO_TYPE;
        short       flags = nextTimeMediaSample + nextTimeEdgeOK;
        TimeValue   duration;
        TimeValue   theTime = 0;
        
        frameCount = -1;
        while (theTime >= 0) 
                 {
            frameCount++;
            GetMovieNextInterestingTime(srcMovie,flags,1,&whichMediaType,theTime,0,&theTime,&duration);
            
            //  After the first interesting time, don't include the time we are currently at.
            flags = nextTimeMediaSample;
        }
    }
    
    
    //---------------------------------------------------------------------------------------------
    //  Get the bounding rectangle of the movie, create a 32-bit gworld with those dimensions,
    //  and draw the movie poster picture into it.  This gworld will be used for the test image
    //  in the compression dialog and for rendering movie frames into.
    //---------------------------------------------------------------------------------------------
    
    {
        PicHandle   ph = GetMoviePosterPict(srcMovie);
        CGrafPtr    savePort;
        GDHandle    saveDevice;
        
        result = GetMoviesError();
        GetMovieBox(srcMovie,&srcRect);
        result = NewGWorld(&srcGWorld,32,&srcRect,nil,nil,0);
        BailError(result);
        if (ph) {
            GetGWorld(&savePort,&saveDevice);
            SetGWorld(srcGWorld,nil);
            EraseRect(&srcRect);
            DrawPicture(ph,&srcRect);
            KillPicture(ph);
            SetGWorld(savePort,saveDevice);
 
            //  Use the gworld image of the movie poster frame as a compression dialog test image.
            //  Pass nil for srcRect to use the entire image, and pass zero to use the default display method.
#if TARGET_OS_WIN32
            result = SCSetTestImagePixMap(ci,srcGWorld->portPixMap,nil,0);
#else       
            result = SCSetTestImagePixMap(ci,GetPortPixMap(srcGWorld),nil,0);
#endif
            BailError(result);
        }
    }
    
    //---------------------------------------------------------------------------------------------
    //  Set compression dialog extend procs for the "Defaults" button.
    //  We wait until now so we can pass the srcGWorld pixmap in as a refcon.
    //---------------------------------------------------------------------------------------------
 
    {
        SCExtendedProcs xprocs;
        
        //  Because the compression dialog is movable modal, a filter proc
        //  to handle update events for application windows is required.
        //  Since our example application is completely modal, and we have
        //  no other windows to update, we don't have a filter proc.
        
        xprocs.filterProc = nil;
        
        //  Proc to handle custom button click.
        
        xprocs.hookProc = NewSCModalHookUPP(DefaultsHookProc);
        
        //  Any information useful to the extended procs can be put
        //  in the refcon.  For an application that has globals, a refcon
        //  of the current A5 is handy for getting at those globals in
        //  the extended procs.  In our case, we put the srcGWorld pixmap
        //  in the refcon so we can set defaults on it in our hook proc.
        
#if TARGET_OS_WIN32
        xprocs.refcon = (long)srcGWorld->portPixMap;
#else
        xprocs.refcon = (long)GetPortPixMap(srcGWorld);
#endif      
        //  Set the custom button name.
        
        BlockMove("\pDefaults",xprocs.customName,9);
        
        //  Tell the compression dialog about the extended procs.
        
        SCSetInfo(ci,scExtendedProcsType,&xprocs);
    }
    
    
    //---------------------------------------------------------------------------------------------
    //  Set up some default settings for the compression dialog if needed, and ask
    //  for compression settings from the user.
    //---------------------------------------------------------------------------------------------
    
    if (firstPass) {
#if TARGET_OS_WIN32
        result = SCDefaultPixMapSettings(ci,srcGWorld->portPixMap,true);
#else
        result = SCDefaultPixMapSettings(ci,GetPortPixMap(srcGWorld),true);
#endif
        BailError(result);
    }
    
    result = SCGetInfo(ci,scTemporalSettingsType,&ts);
    BailError(result);
    
    //  The first time through, clear out the default frame rate chosen by Standard Compression.
    //  We know that a frame rate of 0 means use the rate of the source movie.  It probably should
    //  have been smart enough to figure that out because we set the scAllowZeroFrameRate flag.
    //  Oh well.
    
    if (firstPass) {
        ts.frameRate = 0;
        SCSetInfo(ci,scTemporalSettingsType,&ts);
    }
    
    //  Get compression settings from the user.  The first time through this loop,
    //  we choose default compression settings for the test image we set above.
    //  On subsequent passes, the settings previously chosen by the user will be the defaults.
    
    result = SCRequestSequenceSettings(ci);
    if (result == scUserCancelled) {
        // deal with user cancelling.
    }
    BailError(result);
    
    //  Get a copy of the temporal settings the user entered.  We'll need them for
    //  some of our calculations.  In a simpler application we'd never have to look at them.
    
    result = SCGetInfo(ci,scTemporalSettingsType,&ts);
    BailError(result);
    
    
    //---------------------------------------------------------------------------------------------
    //  Take the overall data rate value entered by the user and subtract out the
    //  part of the data rate allocated to sound.  We do this by looking at all of the
    //  sound tracks in the source movie and using the one with the highest sample rate.
    //  This number is then subtracted from the data rate leaving the amount of data per
    //  second available to the compressed video data.  This is obviously only an approximation
    //  because the various sound tracks may or may not overlap, be stereo, be compressed, etc.
    //  A more accurate approximation would be almost mandatory for a real QuickTime movie
    //  creating/editing application.
    //---------------------------------------------------------------------------------------------
    
    if (!SCGetInfo(ci,scDataRateSettingsType,&ds)) {
        if (ds.dataRate) {
            short   i;
            short   trackCount = GetMovieTrackCount(srcMovie);
            long    maxSoundRate = 0;
            for (i = 1; i <= trackCount; i++) {
                OSType  trackType;
                Track   strack = GetMovieIndTrack(srcMovie,i);
                Media   smedia = GetTrackMedia(strack);
                BailError(GetMoviesError());
                GetMediaHandlerDescription(smedia,&trackType,0,0);
                if (trackType == SOUND_TYPE) {
                    long rate;
                    SampleDescriptionHandle desc = (SampleDescriptionHandle)NewHandle(sizeof(SampleDescription));
                    GetMediaSampleDescription(smedia,1,desc);
                    if (GetMoviesError()) {
                        DisposeHandle((Handle)desc);
                        continue;
                    }
                    rate = (*(SoundDescriptionHandle)desc)->sampleRate >> 16;
                    if (rate > maxSoundRate)
                        maxSoundRate = rate;
                }
            }
            ds.dataRate -= maxSoundRate;
        }
        SCSetInfo(ci,scDataRateSettingsType,&ds);
    }
 
 
    //---------------------------------------------------------------------------------------------
    //  If the user said they want to resample the frame rate of the movie,
    //  (by entering a non-zero value in the frame rate field) calculate
    //  the number of frames and duration for the new movie.
    //---------------------------------------------------------------------------------------------
    
    if (ts.frameRate) {
        long    dur = GetMovieDuration(srcMovie);
        long    timescale = GetMovieTimeScale(srcMovie);
        float   f = (float)dur * ts.frameRate;
        frameCount = f / timescale / 65536;
        if (frameCount == 0)
            frameCount = 1;
    }
    
    
    //---------------------------------------------------------------------------------------------
    //  Ask the user for the name of the new movie file.
    //  We'll be lazy here and just use "Untitled".  A real app would
    //  base it on the name of the source movie and check if the user
    //  tried to enter a name the same as the source movie.
    //
    //  Note we use the SCPositionDialog call to get a good position
    //  point for the SFPutFile.  -3999 is the resource I.D. for
    //  SFPutFile dialog.
    //---------------------------------------------------------------------------------------------
    
    where.h = where.v = -2;
    SCPositionDialog(ci,-3999,&where);
         result = PutFile (kPutFilePrompt, kPutFileName, &dstFile, &theIsSelected, &theIsReplacing);
    if (!theIsSelected)
        goto bail;          // deal with user cancelling
    
    
    //---------------------------------------------------------------------------------------------
    //  Open a progress window to frames as they are compressed.  Use Standard
    //  Compression utility routines to position the window.
    //---------------------------------------------------------------------------------------------
    
    {
        Rect r = srcRect;
        where.h = where.v = -2;
        result = SCPositionRect(ci,&r,&where);
                  GetGWorld(&saveWinPort,&saveWinGDH);
        progressWindow = NewCWindow(0,&r,dstFile.name,true,documentProc,(WindowPtr)-1,true,0);
    }
    
    
    //---------------------------------------------------------------------------------------------
    //  Create the new movie file and prepare it for edits.
    //---------------------------------------------------------------------------------------------
    
    {
        MatrixRecord    matrix;
        
        //  Using the FSSpec create a movie file for the destination movie.
        
        result = CreateMovieFile(&dstFile,'TVOD',0,createMovieFileDeleteCurFile,&movieRefNum,&dstMovie);
        BailError(result);
        
        //  Create a new video movie track with the same dimensions as the entire source movie.
        
        dstTrack = NewMovieTrack(dstMovie,
                (long)(srcRect.right - srcRect.left) << 16,
                (long)(srcRect.bottom - srcRect.top) << 16,0);
        
        //  Create a media for the new track with the same time scale as the
        //  source movie.  Because the time scales are the same, we don't have
        //  to do any time scale conversions.
        
        dstMedia = NewTrackMedia(dstTrack,VIDEO_TYPE,GetMovieTimeScale(srcMovie),0,0);
        BailError(GetMoviesError());
        
        //  Copy the user data and settings from the source to the dest movie.
        //  These settings include information like user data.
        
        CopyMovieSettings(srcMovie,dstMovie);
        
        //  Set movie matrix to identity and clear the movie clip region
        //  because conversion process transforms and composites all video
        //  tracks into one untransformed video track.
        
        SetIdentityMatrix(&matrix);
        SetMovieMatrix(dstMovie,&matrix);
        SetMovieClipRgn(dstMovie,nil);
    }
    
 
    //---------------------------------------------------------------------------------------------
    //  Start a compression sequence using the parameters chosen by the user.
    //  Pass nil for the source rect to use the entire image.  An image description
    //  handle will be returned in idh.  Nil could be passed for that if we
    //  didn't need it for our progress window display.  We do not have dispose
    //  the image description handle.  It is disposed for use by SCCompressSequenceEnd.
    //---------------------------------------------------------------------------------------------
    
#if TARGET_OS_WIN32
    result = SCCompressSequenceBegin(ci,srcGWorld->portPixMap,nil,&idh);
#else
    result = SCCompressSequenceBegin(ci,GetPortPixMap(srcGWorld),nil,&idh);
#endif
    BailError(result);
    
    //  Clear out our image gworld and set movie to draw into it.
 
    SetGWorld(srcGWorld,nil);
         GetPortBounds(srcGWorld, &portRect);
    EraseRect(&portRect);
    SetMovieGWorld(srcMovie,srcGWorld,GetGWorldDevice(srcGWorld));
    
    //  Set current time value to begining of movie.
    
    curMovieTime = 0;
    
    //  Loop through all of the interesting times we counted above.
    
    for (frameNum = 0; frameNum < frameCount; frameNum++) {
        
        short           syncFlag;
        TimeValue       duration;
        long            dataSize;
        Handle      compressedData;
        
        //  Abort if the user clicked the mouse or pressed a key.
        
        {
              EventRecord event;
              abort = false;
 
              WaitNextEvent(everyEvent,&event,0,NULL);
              switch (event.what) 
              {
                  case mouseDown:
                  {
                      WindowPtr     myWindow = NULL;
                      short         myWindowPart;
 
                      myWindowPart = FindWindow(event.where, &myWindow);
                      // menu bar and window-related events:            
                      switch (myWindowPart) {
                            case inGoAway:
                                if (TrackGoAway(myWindow, event.where)) {
                                     DisposeWindow(myWindow);
                                     progressWindow = nil;
                                     abort = true;
                                }
                                break;
                              
                      }
                  }
                      break;
                  case keyDown:
                  case keyUp:
                      abort = true;
                  break;
              }
        }
        
        //  Get the next frame of the source movie.
        
        {
            //  If we are resampling the movie, step to the next frame.
            
            if (ts.frameRate) {
                
                //  This code could be much smarter about its calculations.
                //  The srcMovie duration and dstMovie frame duration are both
                //  constant and could be calculated once outside this loop
                
                long dur = GetMovieDuration(srcMovie);
                curMovieTime = frameNum*dur/(frameCount-1);
                duration = dur / frameCount;
            } else {
                short flags = nextTimeMediaSample;
                OSType  whichMediaType = VIDEO_TYPE;
                
                //  If this is the first frame, include the frame we are currently on.
                
                if (frameNum == 0)
                    flags |= nextTimeEdgeOK;
                
                //  If we are maintaining the frame durations of the source movie,
                //  skip to the next interesting time and get the duration for that frame.
                
                                GetMovieNextInterestingTime(srcMovie,flags,1,&whichMediaType,curMovieTime,0,&curMovieTime,&duration);
            }
            SetMovieTimeValue(srcMovie,curMovieTime);
            MoviesTask(srcMovie,0);
            MoviesTask(srcMovie,0);
            MoviesTask(srcMovie,0);
        }
        
        //  If data rate constraining is being done, tell Standard Compression the
        //  duration of the current frame in milliseconds.  We only need to do this
        //  if the frames have variable durations.
        
        {
            SCDataRateSettings ds;
            if (!SCGetInfo(ci,scDataRateSettingsType,&ds)) {
                ds.frameDuration = duration * 1000 / GetMovieTimeScale(srcMovie);
                SCSetInfo(ci,scDataRateSettingsType,&ds);
            }
        }
        
        //  Compress the frame.  compressedData will hold a handle to the newly compressed
        //  image data.  dataSize is the size of the compressed data, which will usually be
        //  different than the size of the compressedData handle.  syncFlag is a value that
        //  can be passed directly to AddMediaSample which indicates whether or not the frame
        //  is a key frame.  Note that we do not have to dispose of the compressedData handle.
        //  It will be dispose for us when we call SCCompressSequenceEnd.
        
#if TARGET_OS_WIN32
        result = SCCompressSequenceFrame(ci,srcGWorld->portPixMap,&srcRect,&compressedData,&dataSize,&syncFlag);
#else
        result = SCCompressSequenceFrame(ci,GetPortPixMap(srcGWorld),&srcRect,&compressedData,&dataSize,&syncFlag);
#endif
        BailError(result);
        
        //  Prepare for adding frames to the movie.
        //  Make sure you do this.  It's very easy to forget about.
                  result = BeginMediaEdits(dstMedia);
                  BailError(result);
 
        //  Append the compressed image data to the media.
        result = AddMediaSample(dstMedia,compressedData,0,dataSize,duration,
                (SampleDescriptionHandle)idh,1,syncFlag,nil);
        BailError(result);
 
        //  End changes to the media.
                  result = EndMediaEdits(dstMedia);
                  BailError(result);
        
        //  Decompress the compressed frame into the progress window.
        
        if (progressWindow) {
            char hstate;
            
            //  Set port to progress window.
            
            SetGWorld(GetWindowPort(progressWindow),saveWinGDH);
            //  If this is the first frame, start up a decompression sequence.
            
            if (frameNum == 0) {
                result = DecompressSequenceBegin(&dstSeqID,idh,nil,nil,&srcRect,nil,ditherCopy,
                    nil,0,codecNormalQuality,anyCodec);
                BailError(result);
            }
            
            //  Save the locked state of the compressed data and then lock it.
            //  We want it locked but standard compression may or may not.
            
            hstate = HGetState(compressedData);
            HLock(compressedData);
            
            //  Decompress the frame to the progress window.
            
            result = DecompressSequenceFrame(dstSeqID,*compressedData,0,nil,nil);
            
            //  Restore the locked state of the data handle.
            
            HSetState(compressedData,hstate);
            BailError(result);
        }
    }
    
    //  Close the compression sequence.  This will dispose of the image description
    //  and compressed data handles allocated by SCCompressSequenceBegin.
    
    SCCompressSequenceEnd(ci);
    
    //  Close the decompression sequence.  Note that this is an Image Compression
    //  Manager call, not Standard Compression.
    
    CDSequenceEnd(dstSeqID);
    
    
    //  Copy all sound tracks from the source to dest movie.
    //
    //  NOTE: We are not copying any other track types here.  That means
    //  text tracks, alternate tracks, etc. are not being copied.  A real
    //  application would give the user some options here.
    
    {
        short   i;
        short   trackCount;
        
        //  Get a count of all the tracks present in the source movie.
        
        trackCount = GetMovieTrackCount(srcMovie);
        
        //  Loop through each of the tracks, looking for sound tracks.
        
        for (i = 1; i <= trackCount; i++) {
            OSType  trackType;
            Track   strack;
            Media   smedia;
            
            //  Get the next track and its media.
            
            strack = GetMovieIndTrack(srcMovie,i);
            smedia = GetTrackMedia(strack);
            BailError(GetMoviesError());
            
            //  Find out what type of media this track has.
            //  We only care about sound.
            
            GetMediaHandlerDescription(smedia,&trackType,0,0);
            if (trackType == SOUND_TYPE) {
                Track   dtrack;
                Media   dmedia;
                
                //  Create a new sound track in the destination movie.
                
                dtrack = NewMovieTrack(dstMovie,0,0,GetTrackVolume(strack));
                BailError(GetMoviesError());
                
                //  Create a media for that sound track and prepare it for editing.
                
                dmedia = NewTrackMedia(dtrack,SOUND_TYPE,GetMediaTimeScale(smedia),0,0);
                BailError(GetMoviesError());
                
                result = BeginMediaEdits(dmedia);
                BailError(GetMoviesError());
                
                //  Insert the new track into the dest movie starting at time
                //  zero and lasting for the entire duration of the movie.
                
                InsertTrackSegment(strack,dtrack,0,GetTrackDuration(strack),0);
                BailError(GetMoviesError());
                
                //  We're done editing the media.
                
                EndMediaEdits(dmedia);
            }
        }
    }
    
    //---------------------------------------------------------------------------------------------
    //  Now that we're finished compressing video data, make that data part of our movie. 
    //---------------------------------------------------------------------------------------------
    
    if (dstTrack) {
        short resID = 128;
        
        //  Insert the newly created media into the newly created track at
        //  the begining of the track and lasting for the entire duration of
        //  the media.  The media rate is 1.0 for normal playback rate.
        
        InsertMediaIntoTrack(dstTrack,0,0,GetMediaDuration(dstMedia),fixed1);
        
        //  Add the movie resource to the dst movie file.
        
        result = AddMovieResource(dstMovie,movieRefNum,&resID,"\pMovie 1");
        BailError(result);
 
        //---------------------------------------------------------------------------------------------
        //  Next we flatten the movie.  The movie file we just created has all of
        //  the video data at the beginning and all of the sound data at the end.
        //  This unnatural situation may cause the movie to play poorly.  By flattening
        //  the movie, we are reinterleaving all of the tracks in a way optimal for playback.
        //  Because we cannot flatten a movie in place, we create a temp file, flatten to
        //  that file and if successful, delete the first file and rename the temp file to
        //  be the real file.  For large files this takes some time.
        //
        //  Once again, a real application would choose a temp file name that doesn't
        //  possibly conflict with other file names, as well as delete the temp file
        //  if the flatten fails.
        //---------------------------------------------------------------------------------------------
        
        {
            FSSpec  theFSSpec;
            short   resID = 128;
            dstFile.name[++dstFile.name[0]] = '@';
            result = FSMakeFSSpec(dstFile.vRefNum,0,dstFile.name,&theFSSpec);
            if (result != fnfErr)
                BailError(result);
            FlattenMovie(dstMovie,0,&theFSSpec,'TVOD',-1,createMovieFileDeleteCurFile,&resID,"\p");
            result = GetMoviesError();
            CloseMovieFile(movieRefNum);
            DisposeMovie(dstMovie);
            dstTrack = nil;
            if (!result) {
                Str255 tempStr;
                BlockMove(dstFile.name,tempStr,256);
                tempStr[0]--;
                                    result = FSpDelete(&dstFile);
                                    result = FSpRename(&dstFile, tempStr);
            }
        }
    }
    
    //  Throw away the progress window.
    
    if (progressWindow) {
        DisposeWindow(progressWindow);
        progressWindow = nil;
    }
    
    //  Throw away the movie buffer gworld.
    
    if (srcGWorld) {
        DisposeGWorld(srcGWorld);
        srcGWorld = nil;
    
        //  Just to be overly safe, we clear our the test image
        //  because we just disposed the pixmap it depends on.
    
        SCSetTestImagePixMap(ci,nil,nil,0);
    }
    
    if (!abort) {
        firstPass = false;
        goto NextPass;
    }
    
    //  Close the Standard Compression component.
    
    CloseComponent(ci);
bail:;
 
    return 0;
}
 
 
//  Handle clicks in the "Defaults" custom button.
 
pascal short
DefaultsHookProc(DialogPtr theDialog, short itemHit, void *params, long refcon)
{
    #pragma unused(theDialog)
    if (itemHit == scCustomItem) {
        SCTemporalSettings ts;
        
        //  Set defaults for our test image (passed in refcon).
        
        SCDefaultPixMapSettings(params,(PixMapHandle)refcon,true);
        
        //  Once again, don't use the default frame rate chosen
        //  by Standard Compression.  Clear it out to zero.
        
        SCGetInfo(params,scTemporalSettingsType,&ts);
        ts.frameRate = 0;
        SCSetInfo(params,scTemporalSettingsType,&ts);
    }
    return (itemHit);
}