Start Code/MakeEffectMovie.c

//////////
//
//  File:       MakeEffectMovie.c
//
//  Contains:   QuickTime video effect support for QuickTime movies.
//              This file is used for BOTH MacOS and Windows.
//
//  Written by: Tim Monroe
//              Based (heavily!) on the previous MakeEffectMovie code written by Sam Bushell,
//              which was based on ConvertToMovie.c from ConvertToMovie Jr. sample code.
//
//  Copyright:  © 1997-1999 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <7>      03/20/00    rtm     made changes to get things running under CarbonLib
//     <6>      03/03/99    rtm     added support for MakeImageDescriptionForEffect (QT 4.0 and later)
//     <5>      03/12/98    rtm     added global flag gDoneWithDialog to fix Windows dialog box problems
//     <4>      02/28/98    rtm     revised event/message handling following QTShowEffect.c
//     <3>      01/08/98    rtm     sync'ed with latest code from Sam: reworked QTEffects_GetFirstVideoTrackInMovie
//                                  to use GetMovieIndTrackType and fixed time scale setting code; added global flag
//                                  gCopyMovieMedia and menu commands to set that flag
//     <2>      11/21/97    rtm     factored out QTEffects_GetFirstVideoTrackInMovie;
//                                  reworked event-loop processing: changed QTEffects_MakeEffectMovieForSources into
//                                  QTEffects_DisplayDialogForSources and QTEffects_RespondToDialogSelection.
//     <1>      11/06/97    rtm     first file; integrated existing code with shell framework;
//                                  added endian macros where appropriate
//     
//  This application takes the video tracks from zero, one, or two movies 
//  and creates a new movie with an effects track for them.
//
//  Try it out: drag two short movies of the same size onto the application icon.
//  The QuickTime effects dialog will appear and you can select an effect to transition
//  from the first movie to the second. Or, drag a single movie onto the application icon
//  and you'll get an effects dialog for one-source effects.
//
//  You can also launch the application and choose the Make Effect Movie menu command in
//  the Test menu. You'll get a file-opening dialog box for each movie you want to apply
//  the effects to. Just hit Cancel in the first dialog box to get zero-source effects (like fire).
//
//////////
 
// TO DO:
// + copy sound tracks from original movie(s); add tweening of sound tracks as effect progresses
 
// header files
#include "MakeEffectMovie.h"
 
// global variables
QTParameterDialog           gEffectsDialog = 0L;            // identifier for the standard parameter dialog box
QTAtomContainer             gEffectSample = NULL;           // effects sample
QTAtomContainer             gEffectList = NULL;
PicHandle                   gPosterA = NULL;
PicHandle                   gPosterB = NULL;
Movie                       gSrcMovies[kMaxNumSources] = {NULL, NULL};
Track                       gSrcTracks[kMaxNumSources] = {NULL, NULL};
UInt16                      gSpecCount = 0;     
Boolean                     gCopyMovieMedia = false;        // should we copy the movie media into the new effects movie?
Boolean                     gDoneWithDialog = false;        // are we done using the effects parameters dialog box?
 
 
//////////
//
// QTEffects_GetFirstVideoTrackInMovie
// Return, through the theTrack parameter, the first video track in the specified movie.
//
// Actually, we look for the first track that has the kCharacteristicCanSendVideo characteristic,
// so we can apply effects to MPEG or QD3D tracks as well.
//
// If no such track is found, return invalidTrack as the function result.
//
//////////
 
OSErr QTEffects_GetFirstVideoTrackInMovie (Movie theMovie, Track *theTrack)
{
    *theTrack = GetMovieIndTrackType(theMovie, 1, kCharacteristicCanSendVideo, movieTrackCharacteristic | movieTrackEnabledOnly);
    
    if (*theTrack == NULL)
        return(invalidTrack);
        
    return(noErr);
}
 
 
//////////
//
// QTEffects_DisplayDialogForSources
// Display the standard effects parameters dialog box for the movies passed in.
//
//////////
 
OSErr QTEffects_DisplayDialogForSources (FSSpec *theSpecList, UInt16 theSpecCount)
{
    OSErr                   myErr = noErr;
    UInt16                  myMovieIter;
    
    // make sure that there aren't too many sources
    if (theSpecCount > kMaxNumSources) {
        myErr = paramErr;
        goto bail;
    }
    
    // assign source count to a global, so QTEffects_RespondToDialogSelection has access to it
    gSpecCount = theSpecCount;      
    
    // open the source movies, get movies from them, and get the first video track from each movie
    for (myMovieIter = 0; myMovieIter < theSpecCount; myMovieIter++) {
        short   myRefNum;
        
        // open a movie file using the FSSpec and create a movie from that file
        myErr = OpenMovieFile(&theSpecList[myMovieIter], &myRefNum, 0);
        BailError(myErr);
        
        myErr = NewMovieFromFile(&gSrcMovies[myMovieIter], myRefNum, NULL, NULL, newMovieActive, NULL);
        BailError(myErr);
        
        SetMoviePlayHints(gSrcMovies[myMovieIter], hintsHighQuality, hintsHighQuality);
 
        // we're done with the movie file, so close it
        CloseMovieFile(myRefNum);
        
        // find the first video track in the source movie
        myErr = QTEffects_GetFirstVideoTrackInMovie(gSrcMovies[myMovieIter], &gSrcTracks[myMovieIter]);
        BailError(myErr);
    }
    
 
// Step 1.
// Insert "SelectAnEffect.clp" here
 
    
    // insert poster frames into dialog
    if (gSrcTracks[0] != NULL) {
        gPosterA = GetTrackPict(gSrcTracks[0], GetMoviePosterTime(gSrcMovies[0]));
        if (gPosterA != NULL) {
            QTParamPreviewRecord            myPreviewRecord;
 
            myPreviewRecord.sourcePicture = gPosterA;
            myPreviewRecord.sourceID = 1;
            myErr = QTStandardParameterDialogDoAction(gEffectsDialog, pdActionSetPreviewPicture, &myPreviewRecord);
        }
    }
 
    if (gSrcTracks[1] != NULL) {
        gPosterB = GetTrackPict(gSrcTracks[1], GetMoviePosterTime(gSrcMovies[1]));
        if (gPosterB != NULL) {
            QTParamPreviewRecord            myPreviewRecord;
 
            myPreviewRecord.sourcePicture = gPosterB;
            myPreviewRecord.sourceID = 2;
            myErr = QTStandardParameterDialogDoAction(gEffectsDialog, pdActionSetPreviewPicture, &myPreviewRecord);
        }
    }
    
    // now, the frontmost window is the standard effects parameter dialog box;
    // on the Mac, we call QTEffects_HandleEffectsDialogEvents in our main event loop
    // to find and process events targeted at the effects parameter dialog box; on Windows,
    // we need to use a different strategy: we install a modeless dialog callback procedure
    // that is called internally by QTML
 
#if TARGET_OS_WIN32
    gDoneWithDialog = false;
    
    // force the dialog box to be drawn
    {
        EventRecord         myEvent = {0};
        
        QTEffects_EffectsDialogCallback(&myEvent, FrontWindow(), 0);
    }
    
    SetModelessDialogCallbackProc(FrontWindow(), (QTModelessCallbackUPP)QTEffects_EffectsDialogCallback);
    QTMLSetWindowWndProc(FrontWindow(), QTEffects_CustomDialogWndProc);
#endif
    
bail:
    return(myErr);
}
 
 
//////////
//
// QTEffects_RespondToDialogSelection
// If theErr is codecParameterDialogConfirm, make an effects movie.
// If theErr is userCanceledErr, do any necessary clean up.
//
//////////
 
void QTEffects_RespondToDialogSelection (OSErr theErr)
{
    short                   myMovieRefNum = 0;
    short                   myResID = movieInDataForkResID;
    Boolean                 myDialogWasCancelled = false;
    OSType                  myEffectCode;
    Fixed                   videoTrackFXWidth, videoTrackFXHeight;
    TimeScale               myMovieTimeScale = 600; 
    TimeValue               myEffectDuration = 0;
    TimeValue               mySampleTime = 0;
    FSSpec                  myFile;
    Boolean                 myIsSelected = false;
    Boolean                 myIsReplacing = false;  
    StringPtr               myPrompt = QTUtils_ConvertCToPascalString(kEffectsSaveMoviePrompt);
    StringPtr               myFileName = QTUtils_ConvertCToPascalString(kEffectsSaveMovieFileName);
    Movie                   myDestMovie = NULL;
    Track                   videoTracks[kMaxNumSources] = {NULL, NULL};
    Track                   videoTrackFX = NULL;
    Media                   videoMediaFX = NULL;
    UInt16                  myMovieIter;
    long                    myFlags = createMovieFileDeleteCurFile | createMovieFileDontCreateResFile;
    OSErr                   myErr = noErr;
    
    // standard parameter box has been dismissed, so remember that fact
    gEffectsDialog = 0L;
 
    myDialogWasCancelled = (theErr == userCanceledErr);
 
    // we're finished with the effect list and movie posters    
    QTDisposeAtomContainer(gEffectList);
    
    if (gPosterA != NULL)
        KillPicture(gPosterA);
        
    if (gPosterB != NULL)
        KillPicture(gPosterB);
    
    if (myDialogWasCancelled)
        goto bail;
    
 
// Step 2.
// Insert "AddSourceName.clp" here
 
    
 
// Step 3.
// Insert "GetWhatKindOfEffect.clp" here
 
 
    // ask the user for the name of the new movie file
    QTFrame_PutFile(myPrompt, myFileName, &myFile, &myIsSelected, &myIsReplacing);
    if (!myIsSelected)
        goto bail;              // deal with user cancelling
 
    // create a movie file for the destination movie
    myErr = CreateMovieFile(&myFile, FOUR_CHAR_CODE('TVOD'), smSystemScript, myFlags, &myMovieRefNum, &myDestMovie);
    BailError(myErr);
    
    // copy the user data and settings from the source to the destination movie;
    // these settings include information like user data
    if (gSpecCount > 0)
        CopyMovieSettings(gSrcMovies[0], myDestMovie);
    
    // convert all the movies to have a common time scale;
    // we pick the largest time scale out of all the source movies, with a minimum of 600
    myMovieTimeScale = 600;
    for (myMovieIter = 0; myMovieIter < gSpecCount; myMovieIter++) {
        if (myMovieTimeScale < GetMovieTimeScale(gSrcMovies[myMovieIter]))
            myMovieTimeScale = GetMovieTimeScale(gSrcMovies[myMovieIter]);
    }
    
    for (myMovieIter = 0; myMovieIter < gSpecCount; myMovieIter++) {
        if (myMovieTimeScale != GetMovieTimeScale(gSrcMovies[myMovieIter]))
            SetMovieTimeScale(gSrcMovies[myMovieIter], myMovieTimeScale);
    }
    
    // the effect duration is the minimum of the length of the tracks
    if (gSpecCount == 0)
        myEffectDuration = myMovieTimeScale * 10;
    else
        myEffectDuration = GetTrackDuration(gSrcTracks[0]);
    
    for (myMovieIter = 1; myMovieIter < gSpecCount; myMovieIter++) {
        if (myEffectDuration > GetTrackDuration(gSrcTracks[myMovieIter]))
            myEffectDuration = GetTrackDuration(gSrcTracks[myMovieIter]);
    }
    
    // default size when there are no video tracks
    videoTrackFXWidth = 160L << 16;
    videoTrackFXHeight = 120L << 16;
 
    for (myMovieIter = 0; myMovieIter < kMaxNumSources; myMovieIter++) {
        videoTracks[myMovieIter] = NULL;
    }
    
    // make the video tracks
    for (myMovieIter = 0; myMovieIter < gSpecCount; myMovieIter++) {
        Fixed   mySrcTrackWidth, mySrcTrackHeight;
        OSType  mySrcMediaType = 0;
        Media   videoMedia;
 
        // videoTracks[n] is a clone of gSrcTracks[n]
        GetTrackDimensions(gSrcTracks[myMovieIter], &mySrcTrackWidth, &mySrcTrackHeight);
        
        if ((myMovieIter == 0) || (videoTrackFXWidth < mySrcTrackWidth))
                videoTrackFXWidth = mySrcTrackWidth;
                
        if ((myMovieIter == 0) || (videoTrackFXHeight < mySrcTrackHeight))
                videoTrackFXHeight = mySrcTrackHeight;
        
        GetMediaHandlerDescription(GetTrackMedia(gSrcTracks[myMovieIter]), &mySrcMediaType, NULL, NULL);
 
        videoTracks[myMovieIter] = NewMovieTrack(myDestMovie, mySrcTrackWidth, mySrcTrackHeight, 0);
        BailNil(videoTracks[myMovieIter]);
        videoMedia = NewTrackMedia(videoTracks[myMovieIter], mySrcMediaType, myMovieTimeScale, NULL, 0);
        BailNil(videoMedia);
        
        if (gCopyMovieMedia) {
            myErr = BeginMediaEdits(videoMedia);
            BailError(myErr);
        }
 
        myErr = CopyTrackSettings(gSrcTracks[myMovieIter], videoTracks[myMovieIter]);
        BailError(myErr);
        myErr = InsertTrackSegment(gSrcTracks[myMovieIter], videoTracks[myMovieIter], 0, myEffectDuration, 0);
        BailError(myErr);
        
        if (gCopyMovieMedia) {
            myErr = EndMediaEdits(videoMedia);
            BailError(myErr);
        }
    }
 
// Step 4.
// Insert "CreateEffectsTrack.clp" here
 
 
    {
    
// Step 5.
// Insert "CreateEffectDescription.clp" here
 
 
// Step 6.
// Insert "AddEffectsDescription.clp" here
        
    }
 
 
    {
// Step 7.
// Insert "CreateInputMap.clp" here
 
 
// Step 8.
// Insert "AddInputMap.clp" here
 
    }
 
// Step 9.
// Insert "AddMovieResource.clp" here
 
    
    CloseMovieFile(myMovieRefNum);
    
    for (myMovieIter = 0; myMovieIter < gSpecCount; myMovieIter++)
        DisposeMovie(gSrcMovies[myMovieIter]);
        
    DisposeMovie(myDestMovie);
    
bail:
    free(myPrompt);
    free(myFileName);
 
    return;
}
 
 
#if TARGET_OS_WIN32
//////////
//
// QTEffects_EffectsDialogCallback
// This function is called by QTML when it processes events for the standard or custom effects parameter dialog box.
// 
//////////
 
static void QTEffects_EffectsDialogCallback (EventRecord *theEvent, DialogRef theDialog, DialogItemIndex theItemHit)
{
    QTParamDialogEventRecord    myRecord;
 
    myRecord.theEvent = theEvent;
    myRecord.whichDialog = theDialog;
    myRecord.itemHit = theItemHit;
 
    if (gEffectsDialog != 0L) {
        QTStandardParameterDialogDoAction(gEffectsDialog, pdActionModelessCallback, &myRecord);
    
        // see if the event is meant for the effects parameter dialog box
        QTEffects_HandleEffectsDialogEvents(theEvent, theItemHit);
    }
}
 
 
//////////
//
// QTEffects_CustomDialogWndProc
// Handle messages for the custom effects parameters dialog box.
// 
//////////
 
LRESULT CALLBACK QTEffects_CustomDialogWndProc (HWND theWnd, UINT theMessage, UINT wParam, LONG lParam)
{
    EventRecord         myEvent = {0};
 
    if (!gDoneWithDialog && (theMessage == 0x7FFF))
        QTEffects_EffectsDialogCallback(&myEvent, GetNativeWindowPort(theWnd), 0);
 
    return(DefWindowProc(theWnd, theMessage, wParam, lParam));
}
#endif
 
 
//////////
//
// QTEffects_HandleEffectsDialogEvents
// Process events that might be targeted at the standard effects parameter dialog box.
// Return true if the event was completely handled.
// 
//////////
 
Boolean QTEffects_HandleEffectsDialogEvents (EventRecord *theEvent, DialogItemIndex theItemHit)
{
    Boolean         isHandled = false;
    OSErr           myErr = noErr;
    
    // pass the event to the standard effects parameter dialog box handler
    myErr = QTIsStandardParameterDialogEvent(theEvent, gEffectsDialog);
    
    // the result from QTIsStandardParameterDialogEvent tells us how to respond next
    switch (myErr) {
        
        case codecParameterDialogConfirm:
        case userCanceledErr:
            // the user clicked the OK or Cancel button; dismiss the dialog box and respond accordingly
            gDoneWithDialog = true;
            QTStandardParameterDialogDoAction(gEffectsDialog, pdActionConfirmDialog, NULL);
            QTDismissStandardParameterDialog(gEffectsDialog);
            gEffectsDialog = 0L;
            QTEffects_RespondToDialogSelection(myErr);
            isHandled = true;
            break;
            
        case noErr:
            // the event was completely handled by QTIsStandardParameterDialogEvent
            isHandled = true;
            break;
            
        case featureUnsupported:
            // the event was not handled by QTIsStandardParameterDialogEvent;
            // let the event be processed normally
            isHandled = false;
            break;
            
        default:
            // the event was not handled by QTIsStandardParameterDialogEvent;
            // do not let the event be processed normally
            isHandled = true;
            break;
    }
 
    return(isHandled);
}
 
 
//////////
//
// QTEffects_PromptUserForFilesAndMakeEffect
// Let the user select some movies, then apply the effect to them.
//
// If the user cancels the first file-open dialog box, there are zero sources.
// If the user cancels the second file-open dialog box, there is one source.
// 
//////////
 
void QTEffects_PromptUserForFilesAndMakeEffect (void)
{
    QTFrameFileFilterUPP    myFileFilterUPP = NULL;
    FSSpec                  mySpecs[kMaxNumSources];
    int                     mySpecCount;
    OSType                  myTypeList[] = {kQTFileTypeMovie};
    short                   myNumTypes = 1;
    OSErr                   myErr = noErr;
    
#if TARGET_OS_MAC
    myNumTypes = 0;
#endif
 
    myFileFilterUPP = QTFrame_GetFileFilterUPP((ProcPtr)QTFrame_FilterFiles);
 
    // ask for up to kMaxNumSources movie files;
    // accept early cancels; they just mean there are fewer input movies
    mySpecCount = 0;
    while (mySpecCount < kMaxNumSources) {
        FSSpec  myFSSpec;
 
        myTypeList[0] = MovieFileType;
 
        myErr = QTFrame_GetOneFileWithPreview(myNumTypes, (QTFrameTypeListPtr)myTypeList, &myFSSpec, myFileFilterUPP);
        if (myErr != noErr)
            break;  // the user doesn't want any more source movies
    
        // save the FSSpec from the reply information
        mySpecs[mySpecCount] = myFSSpec;
        
        mySpecCount++;
    }
    
    QTEffects_DisplayDialogForSources(mySpecs, mySpecCount);
    
    if (myFileFilterUPP != NULL)
        DisposeNavObjectFilterUPP(myFileFilterUPP);
}