AddEffectSegment.c

//////////
//
//  File:       AddEffectSegment.c
//
//  Contains:   Sample code for adding a visual effect to a segment of a movie.
//
//  Written by: Tim Monroe
//
//  Copyright:  © 1999 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <2>      05/06/99    rtm     tested on Mac and Windows; works fine
//     <1>      05/05/99    rtm     first file; some functions based on some existing code
//                                  in QTShowEffect
//     
//  This file contains some sample code that adds a QuickTime effect to a part of a movie;
//  we assume that the movie already exists and that it contains one or more video tracks.
//  Our job here is to add a specified effect at a specified time. For one- or two-source
//  effects, the principal value in this code consists in illustrating how to duplicate the
//  portion(s) of the existing video track(s) for use as the effects source track(s).
//
//////////
 
#include "AddEffectSegment.h"
 
 
//////////
//
// QTEffSeg_AddEffectToMovieSegment
// Add the specified effect to occur at the specified time and duration.
//
//////////
 
OSErr QTEffSeg_AddEffectToMovieSegment (Movie theMovie, OSType theEffectType, long theNumSources, TimeValue theStartTime, TimeValue theDuration)
{
    Track                   myVidTrack1 = NULL;
    Track                   myVidTrack2 = NULL;
    Track                   mySrcTrack1 = NULL;
    Track                   mySrcTrack2 = NULL;
    Media                   mySrcMedia1 = NULL;
    Media                   mySrcMedia2 = NULL;
    Track                   myEffectTrack = NULL;
    Media                   myEffectMedia = NULL;
    Fixed                   myWidth, myHeight;
    TimeScale               myTimeScale;
    TimeValue               mySampleTime;
    Rect                    myRect;
    QTAtomContainer         myInputMap = NULL;
    QTAtomContainer         myEffectDesc = NULL;
    ImageDescriptionHandle  mySampleDesc = NULL;
    OSType                  myEffectName1 = kSourceNoneName;
    OSType                  myEffectName2 = kSourceNoneName;
    short                   myLayer;
    OSErr                   myErr = noErr;
    
    //////////
    //
    // get some information about the movie
    //
    //////////
    
    // make sure we were passed a valid movie
    if (theMovie == NULL)
        return(paramErr);
        
    myTimeScale = GetMovieTimeScale(theMovie);
    GetMovieBox(theMovie, &myRect);
    myLayer = QTEffSeg_GetFrontmostTrackLayer(theMovie, VideoMediaType);
    
    myWidth = (myRect.right - myRect.left) << 16;
    myHeight = (myRect.bottom - myRect.top) << 16;
 
    //////////
    //
    // retrieve the original video track(s), create the effect's source track(s) and media,
    // then set the new source track(s) to reference the data in the original video track(s)
    //
    //////////
    
    switch (theNumSources) {
        case 2:
            myVidTrack2 = GetMovieIndTrackType(theMovie, 2, VideoMediaType, movieTrackMediaType | movieTrackEnabledOnly);
            if (myVidTrack2 == NULL)
                return(paramErr);
            
            mySrcTrack2 = NewMovieTrack(theMovie, myWidth, myHeight, kNoVolume);
            if (mySrcTrack2 == NULL)
                return(paramErr);
                
            mySrcMedia2 = NewTrackMedia(mySrcTrack2, VideoMediaType, myTimeScale, NULL, 0);
            if (mySrcMedia2 == NULL)
                return(paramErr);
 
#if COPY_MOVIE_MEDIA
            myErr = BeginMediaEdits(mySrcMedia2);
            if (myErr != noErr)
                return(myErr);
#endif          
            myErr = CopyTrackSettings(myVidTrack2, mySrcTrack2);
            myErr = InsertTrackSegment(myVidTrack2, mySrcTrack2, theStartTime, theDuration, theStartTime);
            if (myErr != noErr)
                return(myErr);
 
#if COPY_MOVIE_MEDIA
            EndMediaEdits(mySrcMedia2);
#endif          
            myEffectName2 = kSourceTwoName;
            
            // note that we fall through here!
                
        case 1:
            myVidTrack1 = GetMovieIndTrackType(theMovie, 1, VideoMediaType, movieTrackMediaType | movieTrackEnabledOnly);
            if (myVidTrack1 == NULL)
                return(paramErr);
                
            mySrcTrack1 = NewMovieTrack(theMovie, myWidth, myHeight, kNoVolume);
            if (mySrcTrack1 == NULL)
                return(paramErr);
                
            mySrcMedia1 = NewTrackMedia(mySrcTrack1, VideoMediaType, myTimeScale, NULL, 0);
            if (mySrcMedia1 == NULL)
                return(paramErr);
 
#if COPY_MOVIE_MEDIA
            myErr = BeginMediaEdits(mySrcMedia1);
            if (myErr != noErr)
                return(myErr);
#endif          
            myErr = CopyTrackSettings(myVidTrack1, mySrcTrack1);
            myErr = InsertTrackSegment(myVidTrack1, mySrcTrack1, theStartTime, theDuration, theStartTime);
            if (myErr != noErr)
                return(myErr);
            
#if COPY_MOVIE_MEDIA
            EndMediaEdits(mySrcMedia1);
#endif          
            myEffectName1 = kSourceOneName;
            
            break;
            
        case 0:
            // for 0-source effects, we don't need to create a new source track
            break;
    
        default:
            return(paramErr);
    }
    
    //////////
    //
    // create the effects track and media
    //
    //////////
 
    myEffectTrack = NewMovieTrack(theMovie, myWidth, myHeight, kNoVolume);
    if (myEffectTrack == NULL)
        return(GetMoviesError());
        
    myEffectMedia = NewTrackMedia(myEffectTrack, VideoMediaType, myTimeScale, NULL, 0);
    if (myEffectMedia == NULL)
        return(GetMoviesError());
    
    // create an effect sample description
    mySampleDesc = QTEffSeg_MakeSampleDescription(theEffectType, myWidth >> 16, myHeight >> 16);
    if (mySampleDesc == NULL)
        goto bail;
 
    // create an effect description
    myEffectDesc = QTEffSeg_CreateEffectDescription(theEffectType, myEffectName1, myEffectName2);
 
    // add the effect description as a sample to the effect track media
    BeginMediaEdits(myEffectMedia);
 
    myErr = AddMediaSample(myEffectMedia, (Handle)myEffectDesc, 0, GetHandleSize((Handle)myEffectDesc), theDuration, (SampleDescriptionHandle)mySampleDesc, 1, 0, &mySampleTime);
    if (myErr != noErr)
        goto bail;
 
    EndMediaEdits(myEffectMedia);
    
    // add the media sample to the effects track
    myErr = InsertMediaIntoTrack(myEffectTrack, theStartTime, mySampleTime, theDuration, fixed1);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // create the input map and add references for the source track(s)
    //
    //////////
 
    myErr = QTNewAtomContainer(&myInputMap);
    if (myErr != noErr)
        goto bail;
    
    if (mySrcTrack1 != NULL) {  
        myErr = QTEffSeg_AddTrackReferenceToInputMap(myInputMap, myEffectTrack, mySrcTrack1, kSourceOneName);
        if (myErr != noErr)
            goto bail;
    }
        
    if (mySrcTrack2 != NULL) {  
        myErr = QTEffSeg_AddTrackReferenceToInputMap(myInputMap, myEffectTrack, mySrcTrack2, kSourceTwoName);
        if (myErr != noErr)
            goto bail;
    }
        
    // add the input map to the effects track
    myErr = SetMediaInputMap(myEffectMedia, myInputMap);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // do any required positioning and graphics mode manipulation
    //
    //////////
 
    SetTrackLayer(myEffectTrack, myLayer - 1);  // in front of any existing video track
    
    switch (theNumSources) {
        case 2:
            break;
            
        case 1:
            break;
            
        case 0: {
            RGBColor    myColor;
            
            myColor.red = 0;        // (good for fire, not so good for clouds)
            myColor.green = 0;
            myColor.blue = 0;
            
            MediaSetGraphicsMode(GetMediaHandler(myEffectMedia), transparent, &myColor);
            break;
        }
    }
    
bail:
    if (mySampleDesc != NULL)
        DisposeHandle((Handle)mySampleDesc);
    
    if (myInputMap != NULL)
        QTDisposeAtomContainer(myInputMap);
    
    return(myErr);
}
 
 
//////////
//
// QTEffSeg_CreateEffectDescription
// Create an effect description for zero, one, or two sources.
// 
// The effect description specifies which video effect is desired and the parameters for that effect.
// It also describes the source(s) for the effect. An effect description is simply an atom container
// that holds atoms with the appropriate information.
//
// Note that because we are creating an atom container, we must pass big-endian data (hence the calls
// to EndianU32_NtoB).
//
// The caller is responsible for disposing of the returned atom container, by calling QTDisposeAtomContainer.
//
//////////
 
QTAtomContainer QTEffSeg_CreateEffectDescription (OSType theEffectName, OSType theSourceName1, OSType theSourceName2)
{
    QTAtomContainer     myEffectDesc = NULL;
    OSType              myType;
    OSErr               myErr = noErr;
 
    // create a new, empty effect description
    myErr = QTNewAtomContainer(&myEffectDesc);
    if (myErr != noErr)
        goto bail;
 
    // create the effect ID atom: the atom type is kParameterWhatName, and the atom ID is kParameterWhatID
    myType = EndianU32_NtoB(theEffectName);
    myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, kParameterWhatName, kParameterWhatID, 0, sizeof(myType), &myType, NULL);
    if (myErr != noErr)
        goto bail;
        
    // add the first source, if it's not kSourceNoneName
    if (theSourceName1 != kSourceNoneName) {
        myType = EndianU32_NtoB(theSourceName1);
        myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, kEffectSourceName, 1, 0, sizeof(myType), &myType, NULL);
        if (myErr != noErr)
            goto bail;
    }
                            
    // add the second source, if it's not kSourceNoneName
    if (theSourceName2 != kSourceNoneName) {
        myType = EndianU32_NtoB(theSourceName2);
        myErr = QTInsertChild(myEffectDesc, kParentAtomIsContainer, kEffectSourceName, 2, 0, sizeof(myType), &myType, NULL);
    }
 
bail:
    return(myEffectDesc);
}
 
 
//////////
//
// QTEffSeg_AddTrackReferenceToInputMap
// Add a track reference to the specified input map.
// 
//////////
 
OSErr QTEffSeg_AddTrackReferenceToInputMap (QTAtomContainer theInputMap, Track theTrack, Track theSrcTrack, OSType theSrcName)
{
    OSErr               myErr = noErr;
    QTAtom              myInputAtom;
    long                myRefIndex;
    OSType              myType;
 
    myErr = AddTrackReference(theTrack, theSrcTrack, kTrackModifierReference, &myRefIndex);
    if (myErr != noErr)
        goto bail;
            
    // add a reference atom to the input map
    myErr = QTInsertChild(theInputMap, kParentAtomIsContainer, kTrackModifierInput, myRefIndex, 0, 0, NULL, &myInputAtom);
    if (myErr != noErr)
        goto bail;
    
    // add two child atoms to the parent reference atom
    myType = EndianU32_NtoB(kTrackModifierTypeImage);
    myErr = QTInsertChild(theInputMap, myInputAtom, kTrackModifierType, 1, 0, sizeof(myType), &myType, NULL);
    if (myErr != noErr)
        goto bail;
    
    myType = EndianU32_NtoB(theSrcName);
    myErr = QTInsertChild(theInputMap, myInputAtom, kEffectDataSourceType, 1, 0, sizeof(myType), &myType, NULL);
        
bail:
    return(myErr);
}
 
 
//////////
//
// QTEffSeg_MakeSampleDescription
// Return a new image description with default and specified values.
// 
//////////
 
ImageDescriptionHandle QTEffSeg_MakeSampleDescription (OSType theEffectType, short theWidth, short theHeight)
{
    ImageDescriptionHandle      mySampleDesc = NULL;
 
#if USES_MAKE_IMAGE_DESC_FOR_EFFECT
    OSErr                       myErr = noErr;
    
    // create a new sample description
    myErr = MakeImageDescriptionForEffect(theEffectType, &mySampleDesc);
    if (myErr != noErr)
        return(NULL);
#else
    // create a new sample description
    mySampleDesc = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
    if (mySampleDesc == NULL)
        return(NULL);
        
    // fill in the fields of the sample description
    (**mySampleDesc).cType = theEffectType;
    (**mySampleDesc).idSize = sizeof(ImageDescription);
    (**mySampleDesc).hRes = 72L << 16;
    (**mySampleDesc).vRes = 72L << 16;
    (**mySampleDesc).frameCount = 1;
    (**mySampleDesc).depth = 0;
    (**mySampleDesc).clutID = -1;
#endif
    
    (**mySampleDesc).vendor = kAppleManufacturer;
    (**mySampleDesc).temporalQuality = codecNormalQuality;
    (**mySampleDesc).spatialQuality = codecNormalQuality;
    (**mySampleDesc).width = theWidth;
    (**mySampleDesc).height = theHeight;
    
    return(mySampleDesc);
}
 
 
//////////
//
// QTEffSeg_GetFrontmostTrackLayer
// Return the layer number of the frontmost track of the specified kind in a movie.
// 
//////////
 
short QTEffSeg_GetFrontmostTrackLayer (Movie theMovie, OSType theTrackType)
{
    short       myLayer = 0;
    short       myIndex = 1;
    Track       myTrack = NULL;
    
    // get the layer number of the first track of the specified kind;
    // if no track of that kind exists in the movie, return 0
    myTrack = GetMovieIndTrackType(theMovie, 1, theTrackType, movieTrackMediaType | movieTrackEnabledOnly);
    if (myTrack == NULL)
        return(myLayer);
        
    myLayer = GetTrackLayer(myTrack);
    
    // see if any of the remaining tracks have lower layer numbers
    while (myTrack != NULL) {
        if (myLayer > GetTrackLayer(myTrack))
            myLayer = GetTrackLayer(myTrack);
        myIndex++;
        myTrack = GetMovieIndTrackType(theMovie, myIndex, theTrackType, movieTrackMediaType | movieTrackEnabledOnly);
    }
    
    return(myLayer);
}