Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
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 movie files, 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); |
} |
// ask the user to select an effect |
myErr = QTNewAtomContainer(&gEffectSample); |
BailError(myErr); |
myErr = QTGetEffectsList(&gEffectList, theSpecCount, theSpecCount, 0); |
BailError(myErr); |
myErr = QTCreateStandardParameterDialog(gEffectList, gEffectSample, 0, &gEffectsDialog); |
BailError(myErr); |
// 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; |
// add atoms naming the sources to gEffectSample |
{ |
long myLong; |
if (gSpecCount >= 1) { |
myLong = EndianU32_NtoB(kSourceOneName); |
QTInsertChild(gEffectSample, kParentAtomIsContainer, kEffectSourceName, 1, 0, sizeof(myLong), &myLong, NULL); |
} |
if (gSpecCount >= 2) { |
myLong = EndianU32_NtoB(kSourceTwoName); |
QTInsertChild(gEffectSample, kParentAtomIsContainer, kEffectSourceName, 2, 0, sizeof(myLong), &myLong, NULL); |
} |
} |
// extract the 'what' atom to find out what kind of effect it is |
{ |
QTAtom myEffectAtom; |
QTAtomID myEffectAtomID; |
long myEffectCodeSize; |
Ptr myEffectCodePtr; |
myEffectAtom = QTFindChildByIndex(gEffectSample, kParentAtomIsContainer, kParameterWhatName, kParameterWhatID, &myEffectAtomID); |
myErr = QTLockContainer(gEffectSample); |
BailError(myErr); |
myErr = QTGetAtomDataPtr(gEffectSample, myEffectAtom, &myEffectCodeSize, &myEffectCodePtr); |
BailError(myErr); |
if (myEffectCodeSize != sizeof(OSType)) { |
myErr = paramErr; |
goto bail; |
} |
myEffectCode = *(OSType *)myEffectCodePtr; // "tsk" |
myEffectCode = EndianU32_BtoN(myEffectCode); // because the data is read from an atom container |
myErr = QTUnlockContainer(gEffectSample); |
BailError(myErr); |
} |
// 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); |
} |
} |
{ |
// videoTrackFX is the special track that implements the effect |
videoTrackFX = NewMovieTrack(myDestMovie, videoTrackFXWidth, videoTrackFXHeight, 0); |
BailNil(videoTrackFX); |
videoMediaFX = NewTrackMedia(videoTrackFX, VideoMediaType, myMovieTimeScale, NULL, 0); |
BailNil(videoMediaFX); |
} |
{ |
ImageDescriptionHandle myDesc = NULL; |
#if USES_MAKE_IMAGE_DESC_FOR_EFFECT |
OSErr myErr = noErr; |
// create a new sample description |
myErr = MakeImageDescriptionForEffect(myEffectCode, &myDesc); |
if (myErr != noErr) |
BailError(myErr); |
#else |
// create a new sample description |
myDesc = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription)); |
BailNil(myDesc); |
(**myDesc).idSize = sizeof(ImageDescription); |
(**myDesc).cType = myEffectCode; |
(**myDesc).hRes = 72L << 16; |
(**myDesc).vRes = 72L << 16; |
(**myDesc).dataSize = 0L; |
(**myDesc).frameCount = 1; |
(**myDesc).depth = 0; |
(**myDesc).clutID = -1; |
#endif |
// fill in the fields of the sample description |
(**myDesc).vendor = kAppleManufacturer; |
(**myDesc).temporalQuality = codecNormalQuality; |
(**myDesc).spatialQuality = codecNormalQuality; |
(**myDesc).width = videoTrackFXWidth >> 16; |
(**myDesc).height = videoTrackFXHeight >> 16; |
// add effects sample to movie |
myErr = BeginMediaEdits(videoMediaFX); |
BailError(myErr); |
myErr = AddMediaSample(videoMediaFX, gEffectSample, 0, GetHandleSize(gEffectSample), myEffectDuration, (SampleDescriptionHandle)myDesc, 1, 0, &mySampleTime); |
BailError(myErr); |
myErr = EndMediaEdits(videoMediaFX); |
BailError(myErr); |
QTDisposeAtomContainer(gEffectSample); |
DisposeHandle((Handle)myDesc); |
} |
myErr = InsertMediaIntoTrack(videoTrackFX, 0, mySampleTime, myEffectDuration, fixed1); |
BailError(myErr); |
// create and add the input map |
{ |
long myRefIndex1, myRefIndex2; |
QTAtomContainer myInputMap; |
QTAtom myInputAtom; |
OSType myInputType; |
long myLong; |
QTNewAtomContainer(&myInputMap); |
// first input |
if (videoTracks[0]) { |
AddTrackReference(videoTrackFX, videoTracks[0], kTrackModifierReference, &myRefIndex1); |
QTInsertChild(myInputMap, kParentAtomIsContainer, kTrackModifierInput, myRefIndex1, 0, 0, NULL, &myInputAtom); |
myInputType = EndianU32_NtoB(kTrackModifierTypeImage); |
QTInsertChild(myInputMap, myInputAtom, kTrackModifierType, 1, 0, sizeof(myInputType), &myInputType, NULL); |
myLong = EndianU32_NtoB(kSourceOneName); |
QTInsertChild(myInputMap, myInputAtom, kEffectDataSourceType, 1, 0, sizeof(myLong), &myLong, NULL); |
} |
// second input |
if (videoTracks[1]) { |
AddTrackReference(videoTrackFX, videoTracks[1], kTrackModifierReference, &myRefIndex2); |
QTInsertChild(myInputMap, kParentAtomIsContainer, kTrackModifierInput, myRefIndex2, 0, 0, NULL, &myInputAtom); |
myInputType = EndianU32_NtoB(kTrackModifierTypeImage); |
QTInsertChild(myInputMap, myInputAtom, kTrackModifierType, 1, 0, sizeof(myInputType), &myInputType, NULL); |
myLong = EndianU32_NtoB(kSourceTwoName); |
QTInsertChild(myInputMap, myInputAtom, kEffectDataSourceType, 1, 0, sizeof(myLong), &myLong, NULL); |
} |
// set that map |
if (gSpecCount > 0) |
SetMediaInputMap(GetTrackMedia(videoTrackFX), myInputMap); |
QTDisposeAtomContainer(myInputMap); |
} |
myErr = AddMovieResource(myDestMovie, myMovieRefNum, &myResID, "\pMovie 1"); |
BailError(myErr); |
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); |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-02-25