QTMakeMovie.c

//////////
//
//  File:       QTMakeMovie.c
//
//  Contains:   QuickTime movie-making sample code.
//
//  Written by: Tim Monroe
//              Parts based on the movie-making code in Inside Macintosh: QuickTime.
//
//  Copyright:  © 2000 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <1>      03/09/00    rtm     first file
//
//  This file contains functions that illustrate the standard way of building QuickTime movies.
//  The function QTMM_CreateVideoMovie elicits a destination file from the user and then builds
//  a 10-second movie that contains a single video track. The video track is created from a picture
//  read from the application's resource file; the picture is drawn with varying levels of opacity
//  (using QuickDraw's blend mode), starting with full transparency and progressing up to full
//  opacity. Each image thus generated is compressed using JPEG compression and added to the
//  movie's video track. The resulting movie shows the picture gradually appearing from an empty
//  rectangle.
//   
//////////
 
#include "QTMakeMovie.h"
 
 
//////////
//
// QTMM_CreateVideoMovie
// Create a movie with a video track.
//
//////////
 
OSErr QTMM_CreateVideoMovie (void)
{
    Movie                   myMovie = NULL;
    Track                   myTrack = NULL;
    Media                   myMedia = NULL;
    FSSpec                  myFile;
    Boolean                 myIsSelected = false;
    Boolean                 myIsReplacing = false;  
    StringPtr               myPrompt = QTUtils_ConvertCToPascalString(kNewMoviePrompt);
    StringPtr               myFileName = QTUtils_ConvertCToPascalString(kNewMovieFileName);
    long                    myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
    short                   myResRefNum = 0;
    short                   myResID = movieInDataForkResID;
    OSErr                   myErr = noErr;
    
    // prompt the user for new file name
    QTFrame_PutFile(myPrompt, myFileName, &myFile, &myIsSelected, &myIsReplacing);
    myErr = myIsSelected ? noErr : userCanceledErr;
    if (myErr != noErr)
        goto bail;
 
    // create a movie file for the destination movie
    myErr = CreateMovieFile(&myFile, sigMoviePlayer, smCurrentScript, myFlags, &myResRefNum, &myMovie);
    if (myErr != noErr)
        goto bail;
 
    // create the movie track and media
    myTrack = NewMovieTrack(myMovie, FixRatio(kVideoTrackWidth, 1), FixRatio(kVideoTrackHeight, 1), kNoVolume);
    myErr = GetMoviesError();
    if (myErr != noErr)
        goto bail;
        
    myMedia = NewTrackMedia(myTrack, VideoMediaType, kVideoTimeScale, NULL, 0);
    myErr = GetMoviesError();
    if (myErr != noErr)
        goto bail;
 
    // create the media samples
    myErr = BeginMediaEdits(myMedia);
    if (myErr != noErr)
        goto bail;
 
    myErr = QTMM_AddVideoSamplesToMedia(myMedia, kVideoTrackWidth, kVideoTrackHeight);
    if (myErr != noErr)
        goto bail;
 
    myErr = EndMediaEdits(myMedia);
    if (myErr != noErr)
        goto bail;
    
    // add the media to the track
    myErr = InsertMediaIntoTrack(myTrack, 0, 0, GetMediaDuration(myMedia), fixed1);
    if (myErr != noErr)
        goto bail;
    
    // add the movie atom to the movie file
    myErr = AddMovieResource(myMovie, myResRefNum, &myResID, NULL);
 
bail:
    if (myResRefNum != 0)
        CloseMovieFile(myResRefNum);
 
    if (myMovie != NULL)
        DisposeMovie(myMovie);
        
    free(myPrompt);
    free(myFileName);
    
    return(myErr);
}
 
 
//////////
//
// QTMM_AddVideoSamplesToMedia
// Add video media samples to the specified media.
//
//////////
 
OSErr QTMM_AddVideoSamplesToMedia (Media theMedia, short theTrackWidth, short theTrackHeight)
{
    GWorldPtr                   myGWorld = NULL;
    PixMapHandle                myPixMap = NULL;
    CodecType                   myCodecType = kJPEGCodecType;
    long                        myNumSample;
    long                        myMaxComprSize = 0L;
    Handle                      myComprDataHdl = NULL;
    Ptr                         myComprDataPtr = NULL;
    ImageDescriptionHandle      myImageDesc = NULL;
    CGrafPtr                    mySavedPort = NULL;
    GDHandle                    mySavedDevice = NULL;
    Rect                        myRect;
    OSErr                       myErr = noErr;
 
    MacSetRect(&myRect, 0, 0, theTrackWidth, theTrackHeight);
 
    myErr = NewGWorld(&myGWorld, kPixelDepth, &myRect, NULL, NULL, (GWorldFlags)0);
    if (myErr != noErr)
        goto bail;
 
    myPixMap = GetGWorldPixMap(myGWorld);
    if (myPixMap == NULL)
        goto bail;
 
    LockPixels(myPixMap);
    myErr = GetMaxCompressionSize(  myPixMap,
                                    &myRect, 
                                    0,                          // let ICM choose depth
                                    codecNormalQuality, 
                                    myCodecType, 
                                    (CompressorComponent)anyCodec,
                                    &myMaxComprSize);
    if (myErr != noErr)
        goto bail;
 
    myComprDataHdl = NewHandle(myMaxComprSize);
    if (myComprDataHdl == NULL)
        goto bail;
 
    HLockHi(myComprDataHdl);
#if TARGET_CPU_68K
    myComprDataPtr = StripAddress(*myComprDataHdl);
#else
    myComprDataPtr = *myComprDataHdl;
#endif
 
    myImageDesc = (ImageDescriptionHandle)NewHandle(4);
    if (myImageDesc == NULL)
        goto bail;
 
    GetGWorld(&mySavedPort, &mySavedDevice);
    SetGWorld(myGWorld, NULL);
 
    for (myNumSample = 1; myNumSample <= kNumVideoFrames; myNumSample++) {
        EraseRect(&myRect);
        
        QTMM_DrawFrame(theTrackWidth, theTrackHeight, myNumSample, myGWorld);
 
        myErr = CompressImage(  myPixMap, 
                                &myRect, 
                                codecNormalQuality,
                                myCodecType,
                                myImageDesc, 
                                myComprDataPtr);
        if (myErr != noErr)
            goto bail;
 
        myErr = AddMediaSample( theMedia, 
                                myComprDataHdl,
                                0,                              // no offset in data
                                (**myImageDesc).dataSize, 
                                kVideoFrameDuration,            // frame duration
                                (SampleDescriptionHandle)myImageDesc, 
                                1,                              // one sample
                                0,                              // self-contained samples
                                NULL);
        if (myErr != noErr)
            goto bail;
    }
 
bail:
    SetGWorld(mySavedPort, mySavedDevice);
 
    if (myImageDesc != NULL)
        DisposeHandle((Handle)myImageDesc);
 
    if (myComprDataHdl != NULL)
        DisposeHandle(myComprDataHdl);
 
    if (myGWorld != NULL)
        DisposeGWorld(myGWorld);
        
    return(myErr);
}
 
 
//////////
//
// QTMM_DrawFrame
// Draw a frame of video.
//
//////////
 
static void QTMM_DrawFrame (short theTrackWidth, short theTrackHeight, long theNumSample, GWorldPtr theGWorld)
{
    Handle                              myHandle = NULL;
    char                                myData[kPICTFileHeaderSize];
    static PicHandle                    myPicture = NULL;
    static GWorldPtr                    myGWorld = NULL;
    static GraphicsImportComponent      myImporter = NULL;
    Rect                                myRect;
    RGBColor                            myColor;
    ComponentResult                     myErr = noErr;
 
    MacSetRect(&myRect, 0, 0, theTrackWidth, theTrackHeight);
 
    if (myPicture == NULL) {
        myErr = NewGWorld(&myGWorld, kPixelDepth, &myRect, NULL, NULL, (GWorldFlags)0);
        if (myErr != noErr)
            goto bail;
 
        // read a picture from our resource file
        myPicture = GetPicture(kPictureID);
        if (myPicture == NULL)
            goto bail;
 
        // use Munger to prepend a 512-byte header onto the picture data; this converts the PICT
        // resource data into in-memory PICT file data (see Ice Floe 14 for an explanation of this)
        myHandle = (Handle)myPicture;
        Munger(myHandle, 0, NULL, 0, myData, kPICTFileHeaderSize);
 
        // get a graphics importer for the picture
        myErr = OpenADefaultComponent(GraphicsImporterComponentType, kQTFileTypePicture, &myImporter); 
        if (myErr != noErr)
            goto bail;
                
        // configure the graphics importer
        myErr = GraphicsImportSetGWorld(myImporter, myGWorld, NULL);
        if (myErr != noErr)
            goto bail;
        
        myErr = GraphicsImportSetDataHandle(myImporter, myHandle);
        if (myErr != noErr)
            goto bail;
            
        myErr = GraphicsImportSetBoundsRect(myImporter, &myRect);
        if (myErr != noErr)
            goto bail;
            
        // draw the picture into the source GWorld
        myErr = GraphicsImportDraw(myImporter);
        if (myErr != noErr)
            goto bail;
    }
    
    // set the blend amount (0 = fully transparent; 0xffff = fully opaque)
    myColor.red = (theNumSample - 1) * (0xffff / kNumVideoFrames - 1);
    myColor.green = (theNumSample - 1) * (0xffff / kNumVideoFrames - 1);
    myColor.blue = (theNumSample - 1) * (0xffff / kNumVideoFrames - 1);
    OpColor(&myColor);
    
    // blend the picture (in the source GWorld) into the empty rectangle (in the destination GWorld)
    CopyBits((BitMapPtr)*GetGWorldPixMap(myGWorld),
             (BitMapPtr)*GetGWorldPixMap(theGWorld),
             &myRect,
             &myRect,
             blend,
             NULL);
 
    if (theNumSample == kNumVideoFrames)
        goto bail;
        
    return;
    
bail:
    if (myHandle != NULL)
        DisposeHandle(myHandle);
        
    if (myPicture != NULL)
        ReleaseResource((Handle)myPicture);
        
    if (myImporter != NULL)
        CloseComponent(myImporter);
}