source/QTUtilities.c

//////////
//
//  File:       QTUtilities.c
//
//  Contains:   Some utilities for working with QuickTime movies.
//              All utilities start with the prefix "QTUtils_".
//
//  Copyright:  © 1996-1999 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <23>     03/22/99    rtm     updated connection speed code to use constants/data types now in Movies.h
//     <22>     03/11/99    rtm     moved _GetControllerType and _SetControllerType from QTVRUtilities to here;
//                                  added QTUtils_ChangeControllerType
//     <21>     02/03/99    rtm     moved non-QTVR-specific utilities from QTVRUtilities to here
//     <20>     01/26/99    rtm     added QTUtils_ConvertCToPascalString; removed "\p" from any constant strings
//     <19>     01/25/99    rtm     #define'd away QTUtils_GetUsersContentRating and siblings, since content rating
//                                  capability is not in latest feature set
//     <18>     12/09/98    rtm     added QTUtils_GetUsersContentRating
//     <17>     11/18/98    rtm     added QTUtils_GetFrameCount
//     <16>     10/27/98    rtm     added QTUtils_MakeMovieLoop
//     <15>     09/14/98    rtm     added QTUtils_GetUsersConnectionSpeed and QTUtils_SetUsersConnectionSpeed
//     <14>     06/24/98    rtm     added QTUtils_GetMaxWindowDepth and QTUtils_GetMaxScreenDepth
//     <13>     06/04/98    rtm     added QTUtils_DeleteAllReferencesToTrack
//     <12>     05/28/98    rtm     added some typecasting to minimize MSDev compiler warnings
//     <11>     05/19/98    rtm     added QTUtils_MovieHasTimeCodeTrack
//     <10>     05/04/98    rtm     added QTUtils_GetTrackName, QTUtils_SetTrackName, QTUtils_MakeTrackNameByType,
//                                  QTUtils_IsImageFile, and QTUtils_IsMovieFile
//     <9>      02/28/98    rtm     fixed QTUtils_GetMovieFileLoopingInfo and the like
//     <8>      01/14/98    rtm     added QTUtils_ConvertFloatToBigEndian
//     <7>      12/19/97    rtm     added QTUtils_AddUserDataTextToMovie and associated routines;
//                                  added QTUtils_GetMovieFileLoopingInfo and the like
//     <6>      11/06/97    rtm     added QTUtils_MakeSampleDescription
//     <5>      10/29/97    rtm     modified QTUtils_IsMediaTypeInMovie and similar routines to use GetMovieIndTrackType
//     <4>      10/27/97    rtm     added QTUtils_HasQuickTimeVideoEffects
//     <3>      10/17/97    rtm     added QTUtils_MovieHasSoundTrack
//     <2>      09/23/97    rtm     added endian adjustment to QTUtils_PrintMoviePICT
//     <1>      09/10/97    rtm     first file
//     
//////////
/*
    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.
*/
 
//////////
//
// header files
//
//////////
 
#ifndef __QTUtilities__
#include "QTUtilities.h"
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// General utilities.
//
// Use these functions to get information about the availability/features of QuickTime or other services.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QTUtils_TrapAvailable
// Check to see whether a given trap is implemented. This is based on IM: Operating System Utilities (p. 8-22).
//
//////////
 
#if TARGET_OS_MAC
Boolean QTUtils_TrapAvailable (short theTrapWord)
{
    TrapType        myTrapType;
    short           myNumToolboxTraps;
    
    // determine whether this is a Toolbox or an Operating System trap
    if ((theTrapWord & 0x0800) > 0)
        myTrapType = ToolTrap;
    else
        myTrapType = OSTrap;
 
    if (myTrapType == ToolTrap) {
        theTrapWord = theTrapWord & 0x07FF;
        
        if (NGetTrapAddress(_InitGraf, ToolTrap) == NGetTrapAddress(0xAA6E, ToolTrap))
            myNumToolboxTraps = 0x0200;
        else
            myNumToolboxTraps = 0x0400;
            
        if (theTrapWord >= myNumToolboxTraps)
            theTrapWord = _Unimplemented;
    }
 
    return(NGetTrapAddress(theTrapWord, myTrapType) != NGetTrapAddress(_Unimplemented, ToolTrap));
}
#endif
 
 
//////////
//
// QTUtils_IsQuickTimeInstalled
// Is QuickTime installed?
//
//////////
 
Boolean QTUtils_IsQuickTimeInstalled (void) 
{
    Boolean     myQTAvail = false;
    long        myAttrs;
    OSErr       myErr = noErr;
 
    myErr = Gestalt(gestaltQuickTime, &myAttrs);
    if (myErr == noErr)
        myQTAvail = true;
 
    return(myQTAvail);
}
 
 
//////////
//
// QTUtils_IsQuickTimeCFMInstalled
// Are the QuickTime CFM libraries installed?
//
//////////
 
#if TARGET_OS_MAC
#ifdef powerc
Boolean QTUtils_IsQuickTimeCFMInstalled (void) 
{
    Boolean     myQTCFMAvail = false;
    long        myAttrs;
    OSErr       myErr = noErr;
 
    // test whether the library is registered.
    myErr = Gestalt(gestaltQuickTimeFeatures, &myAttrs);
    if (myErr == noErr)
        if (myAttrs & (1L << gestaltPPCQuickTimeLibPresent))
            myQTCFMAvail = true;
 
    // test whether a function is available (the library is not moved from the Extension folder);
    // this is the trick to be used when testing if a function is available via CFM
    if (!CompressImage)
        myQTCFMAvail = false;     
 
    return(myQTCFMAvail);
}
#endif  // powerc
#endif
 
 
//////////
//
// QTUtils_GetQTVersion
// Get the version of QuickTime installed.
// The high-order word of the returned long integer contains the version number,
// so you can test a version like this:
//
//      if (((QTUtils_GetQTVersion() >> 16) & 0xffff) >= 0x0210)        // we require QT 2.1 or greater
//          return;
//
//////////
 
long QTUtils_GetQTVersion (void) 
{
    long        myVersion = 0L;
    OSErr       myErr = noErr;
 
    myErr = Gestalt(gestaltQuickTime, &myVersion);
    if (myErr == noErr)
        return(myVersion);
    else
        return(0L);
}
 
 
//////////
//
// QTUtils_HasQuickTimeVideoEffects
// Does the installed version of QuickTime support video effects?
//
// Note: this is a pretty cheesy way of determining whether video effects are available.
//
//////////
 
Boolean QTUtils_HasQuickTimeVideoEffects (void) 
{
    return(((QTUtils_GetQTVersion() >> 16) & 0xffff) >= kQTVideoEffectsMinVers);
}
 
 
//////////
//
// QTUtils_HasFullScreenSupport
// Does the installed version of QuickTime support the full-screen routines?
//
// Note: this is a pretty cheesy way of determining whether full-screen routines are available.
//
//////////
 
Boolean QTUtils_HasFullScreenSupport (void) 
{
    return(((QTUtils_GetQTVersion() >> 16) & 0xffff) >= kQTFullScreenMinVers);
}
 
 
//////////
//
// QTUtils_HasWiredSprites
// Does the installed version of QuickTime support wired sprites?
//
// Note: this is a pretty cheesy way of determining whether wired sprites are available.
//
//////////
 
Boolean QTUtils_HasWiredSprites (void) 
{
    return(((QTUtils_GetQTVersion() >> 16) & 0xffff) >= kQTWiredSpritesMinVers);
}
 
 
//////////
//
// QTUtils_IsStreamedMovie
// Is the specified movie a streamed movie?
//
//////////
 
Boolean QTUtils_IsStreamedMovie (Movie theMovie) 
{
    return(GetMovieIndTrackType(theMovie, 1, kQTSStreamMediaType, movieTrackMediaType | movieTrackEnabledOnly) != NULL);
}
 
 
//////////
//
// QTUtils_GetMovie
// Open the specified movie file; if none is specified, query the user to select a file.
//
//////////
 
Movie QTUtils_GetMovie (FSSpec *theFSSpec, short *theRefNum, short *theResID)
{
    SFTypeList              myTypeList = {MovieFileType, 0, 0, 0};
    StandardFileReply       myReply;
    Movie                   myMovie = NULL;
    OSErr                   myErr = noErr;
 
    // if we are provided with an FSSpec then use it; otherwise elicit a file from the user
    if (theFSSpec == NULL || theFSSpec->vRefNum == 0) { 
        StandardGetFilePreview(NULL, 1, myTypeList, &myReply);
        if (!myReply.sfGood)
            return(NULL);
        
        *theFSSpec = myReply.sfFile;
    }
 
    // we should have now a usable FSSpec; just double check this before continuing
    if (theFSSpec == NULL)
        return NULL;
    
    // open the movie file
    myErr = OpenMovieFile(theFSSpec, theRefNum, fsRdPerm);
    if (myErr == noErr) {
        Str255      myMovieName;
        Boolean     wasChanged;
        
        *theResID = 0;                  // we want the first movie
        
        myErr = NewMovieFromFile(&myMovie, *theRefNum, theResID, myMovieName, newMovieActive, &wasChanged);
        CloseMovieFile(*theRefNum);
    }
    
    if (myErr != noErr)
        return(NULL);
    else
        return(myMovie);
}
 
 
//////////
//
// QTUtils_SaveMovie
// Save and flatten a movie resource into a file.
//
//////////
 
OSErr QTUtils_SaveMovie (Movie theMovie)
{
    StandardFileReply   mySFReply;
    StringPtr           myPrompt = QTUtils_ConvertCToPascalString(kSavePrompt);
    StringPtr           myFileName = QTUtils_ConvertCToPascalString(kSaveMovieFileName);
    OSErr               myErr = noErr;
    
    if (theMovie == NULL)
        return(invalidMovie);
    
    StandardPutFile(myPrompt, myFileName, &mySFReply); 
    if (mySFReply.sfGood) {
        FlattenMovieData(theMovie, flattenAddMovieToDataFork, &mySFReply.sfFile, FOUR_CHAR_CODE('TVOD'), smSystemScript, createMovieFileDeleteCurFile);
        myErr = GetMoviesError();
    }
 
    free(myPrompt);
    free(myFileName);
    
    return(myErr);
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Controller bar utilities.
//
// Use these functions to manipulate the controller bar, its buttons, and the help text displayed in it.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QTUtils_IsControllerBarVisible
// Is the controller bar currently visible?
//
//////////
 
Boolean QTUtils_IsControllerBarVisible (MovieController theMC) 
{
    return((Boolean)MCGetVisible(theMC));
}
 
 
//////////
//
// QTUtils_GetControllerBarHeight
// Return the height of the controller bar displayed by the movie controller.
//
// Note that MCGetControllerBoundsRect returns rectangle of bar and movie, if attached;
// so we need to unattach the controller bar first.
//
//////////
 
short QTUtils_GetControllerBarHeight (MovieController theMC) 
{
    Boolean     wasAttached = false;
    Rect        myRect;
    short       myHeight = 0;
    
    // if the controller bar is attached, detach it (and remember we did so)
    if (MCIsControllerAttached(theMC) == 1) {
        wasAttached = true;
        MCSetControllerAttached(theMC, false);
    }
    
    // get the rectangle of the controller
    MCGetControllerBoundsRect(theMC, &myRect);
    myHeight = myRect.bottom - myRect.top;
    
    // now reattach the controller bar, if it was originally attached
    if (wasAttached)
        MCSetControllerAttached(theMC, true);
    
    return(myHeight);
}
 
 
//////////
//
// QTUtils_HideControllerBar
// Hide the controller bar provided by the movie controller.
//
//////////
 
void QTUtils_HideControllerBar (MovieController theMC) 
{
    MCSetVisible(theMC, false);
}
 
 
//////////
//
// QTUtils_ShowControllerBar
// Show the controller bar provided by the movie controller.
//
//////////
 
void QTUtils_ShowControllerBar (MovieController theMC) 
{
    MCSetVisible(theMC, true);
}
 
 
//////////
//
// QTUtils_ToggleControllerBar
// Toggle the state of the controller bar provided by the movie controller.
//
//////////
 
void QTUtils_ToggleControllerBar (MovieController theMC) 
{
    if (QTUtils_IsControllerBarVisible(theMC))
        QTUtils_HideControllerBar(theMC);
    else
        QTUtils_ShowControllerBar(theMC);
}
 
 
//////////
//
// QTUtils_HideControllerButton
// Hide the specified button in the controller bar.
//
// Some explanation is probably useful here: the first thing to understand is that every VR movie has 
// TWO sets of movie controller flags: (1) a set of "control flags" and (2) a set of "explicit flags".
//
// The control flags work as documented in IM: QuickTime Components (pp. 2-20f) and in VRPWQTVR2.0 (pp. 2-23f):
// if a bit in the set of control flags is set (that is, equal to 1), then the associated action or property is
// enabled. For instance, bit 17 (mcFlagQTVRSuppressZoomBtns) means to suppress the zoom buttons. So, if that
// bit is set in a VR movie's control flags, the zoom buttons are NOT displayed. If that bit is clear, the zoom
// buttons are displayed.
//
// However, the QuickTime VR movie controller sometimes suppresses buttons even when those buttons 
// have not been explicitly suppressed in the control flags. For example, if a particular VR movie does not
// contain a sound track, then the movie controller automatically suppresses the speaker/volume button. Likewise,
// if a movie does contain a sound track, then the speaker/volume button is automatically displayed, again without
// regard to the actual value of bit 17 in the control flags.
//
// This might not be what you'd like to happen. For instance, if your application is playing a sound that it
// loaded from a sound resource, you might want the user to be able to adjust the sound's volume using the volume
// control. To do that, you need a way to *force* the speaker/volume button to appear. For this reason, the
// explicit flags were introduced.
//
// The explicit flags indicate which bits in the control flags are to be used explicitly (that is, taken at
// face value). If a certain bit is set in a movie's explicit flags, then the corresponding bit in the control
// flags is interpreted as the desired setting for the feature (and the movie controller will not attempt to
// do anything "clever"). In other words, if bit 17 is set in a movie's explicit flags and bit 17 is clear in
// that movie's control flags, then the zoom buttons are displayed. Similarly, if bit 2 is set in a movie's 
// explicit flags and bit 2 is clear in that movie's control flags, then the speaker/volume button is displayed,
// whether or not the movie contains a sound track.
//
// The final thing to understand: to get or set a bit in a movie's explicit flags, you must set the flag 
// mcFlagQTVRExplicitFlagSet in your call to mcActionGetFlags or mcActionSetFlags. To get or set a bit in a 
// movie's control flags, you must clear the flag mcFlagQTVRExplicitFlagSet in your call to mcActionGetFlags 
// or mcActionSetFlags. Note that when you use the defined constants to set values in the explicit flags, the 
// constant names might be confusing. For instance, setting the bit mcFlagSuppressSpeakerButton in a movie's
// explicit flags doesn't cause the speaker to be suppressed; it just means: "use the actual value of the
// mcFlagSuppressSpeakerButton bit in the control flags".
//
// Whew! Any questions? Okay, then now you'll understand how to hide or show a button in the controller bar:
// set the appropriate explicit flag to 1 and set the corresponding control flag to the desired value. And
// you'll understand how to let the movie controller do its "clever" work: clear the appropriate explicit flag.
//
//////////
 
void QTUtils_HideControllerButton (MovieController theMC, long theButton) 
{
    long    myControllerFlags;
    
    // get the current explicit flags and set the explicit flag for the specified button
    myControllerFlags = mcFlagQTVRExplicitFlagSet;
    MCDoAction(theMC, mcActionGetFlags, &myControllerFlags);
    MCDoAction(theMC, mcActionSetFlags, (void *)((myControllerFlags | theButton) | mcFlagQTVRExplicitFlagSet));
    
    // get the current control flags and set the suppress flag for the specified button
    myControllerFlags = 0;
    MCDoAction(theMC, mcActionGetFlags, &myControllerFlags);
    MCDoAction(theMC, mcActionSetFlags, (void *)((myControllerFlags | theButton) & ~mcFlagQTVRExplicitFlagSet));
}
 
 
//////////
//
// QTUtils_ShowControllerButton
// Show the specified button in the controller bar.
//
//////////
 
void QTUtils_ShowControllerButton (MovieController theMC, long theButton) 
{
    long    myControllerFlags;
    
    // get the current explicit flags and set the explicit flag for the specified button
    myControllerFlags = mcFlagQTVRExplicitFlagSet;
    MCDoAction(theMC, mcActionGetFlags, &myControllerFlags);
    MCDoAction(theMC, mcActionSetFlags, (void *)((myControllerFlags | theButton) | mcFlagQTVRExplicitFlagSet));
    
    // get the current control flags and clear the suppress flag for the specified button
    myControllerFlags = 0;
    MCDoAction(theMC, mcActionGetFlags, &myControllerFlags);
    MCDoAction(theMC, mcActionSetFlags, (void *)(myControllerFlags & ~theButton & ~mcFlagQTVRExplicitFlagSet));
}
 
 
//////////
//
// QTUtils_ToggleControllerButton
// Toggle the state of the specified button in the controller bar.
//
//////////
 
void QTUtils_ToggleControllerButton (MovieController theMC, long theButton) 
{
    long    myControllerFlags;
    
    // get the current control flags and toggle the suppress flag for the specified button
    myControllerFlags = 0;
    MCDoAction(theMC, mcActionGetFlags, &myControllerFlags);
    
    if (myControllerFlags & theButton)              // if the button is currently suppressed...
        QTUtils_ShowControllerButton(theMC, theButton);
    else
        QTUtils_HideControllerButton(theMC, theButton);
}
 
 
//////////
//
// QTUtils_ResetControllerButton
// Remove any explicit setting of the specified button in the controller bar.
// (This allows the QuickTime VR movie controller to be as clever as it knows how to be.)
//
//////////
 
void QTUtils_ResetControllerButton (MovieController theMC, long theButton) 
{
    long    myControllerFlags = mcFlagQTVRExplicitFlagSet;
    
    // get the current explicit flags and clear the explicit flag for the specified button
    MCDoAction(theMC, mcActionGetFlags, &myControllerFlags);
    MCDoAction(theMC, mcActionSetFlags, (void *)((myControllerFlags | theButton) & ~mcFlagQTVRExplicitFlagSet));
}
 
 
//////////
//
// QTUtils_IsControllerButtonVisible
// Is the specified button in the controller bar currently visible?
//
//////////
 
Boolean QTUtils_IsControllerButtonVisible (MovieController theMC, long theButton) 
{
    long        myControllerFlags;
 
    // get the current control flags
    myControllerFlags = 0;
    MCDoAction(theMC, mcActionGetFlags, &myControllerFlags);
 
    // the speaker button requires some additional logic, because the QTVR movie controller treats it special;
    // be advised that that controller's special behavior could change in the future,
    // so you might need to tweak this code
    if (theButton == mcFlagSuppressSpeakerButton) {
        long    myExplicitFlags;
        
        // get the current explicit flags
        myExplicitFlags = mcFlagQTVRExplicitFlagSet;
        MCDoAction(theMC, mcActionGetFlags, &myExplicitFlags);
    
        // the speaker button is not showing if the movie has no sound track and the explicit flag is not set
        if (!QTUtils_MovieHasSoundTrack(MCGetMovie(theMC)) && !(myExplicitFlags & theButton))
            return(false);
    }
    
    // examine the suppress flag for the specified button
    if (myControllerFlags & theButton)              // if the button is currently suppressed...
        return(false);
    else
        return(true);
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Media utilities.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QTUtils_IsMediaTypeInMovie
// Determine whether a specific media type is in a movie.
// 
//////////
 
Boolean QTUtils_IsMediaTypeInMovie (Movie theMovie, OSType theMediaType)
{
    return(GetMovieIndTrackType(theMovie, 1, theMediaType, movieTrackMediaType | movieTrackEnabledOnly) != NULL);
}
 
 
//////////
//
// QTUtils_MovieHasTimeCodeTrack
// Determine whether a movie contains a timecode track.
// 
//////////
 
Boolean QTUtils_MovieHasTimeCodeTrack (Movie theMovie)
{
    return(GetMovieIndTrackType(theMovie, 1, TimeCodeMediaType, movieTrackMediaType) != NULL);
}
 
 
//////////
//
// QTUtils_MovieHasSoundTrack
// Determine whether a movie contains a sound track.
// 
//////////
 
Boolean QTUtils_MovieHasSoundTrack (Movie theMovie)
{
    return(GetMovieIndTrackType(theMovie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly) != NULL);
}
 
 
//////////
//
// QTUtils_GetSoundMediaHandler
// Return the sound media handler for a movie.
// 
//////////
 
MediaHandler QTUtils_GetSoundMediaHandler (Movie theMovie)
{
    Track       myTrack;
    Media       myMedia;
 
    myTrack = GetMovieIndTrackType(theMovie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly);
    if (myTrack != NULL) {
        myMedia = GetTrackMedia(myTrack);
        return(GetMediaHandler(myMedia));
    } 
        
    return(NULL);
}
 
 
//////////
//
// QTUtils_PrintMoviePICT
// Print the existing movie frame pict.
// 
// Note that in a real application we should put the PrStlDialog code into the Print SetupÉ menu
// function. The reason it's inside this function is that we use this code for quick testing of printing.
//
//////////
 
OSErr QTUtils_PrintMoviePICT (Movie theMovie, short x, short y, long PICTUsed)
{
    PicHandle       myPictHandle = NULL;
    THPrint         myTHPrint = NULL;
    GrafPtr         mySavedPort;
    TPPrPort        myPrintPort;
    Boolean         myResult;
    Boolean         isPrinting = false;
    Rect            myPictRect;
    OSErr           myErr = noErr;
    
    if (theMovie == NULL)
        return(invalidMovie);
    
    GetPort(&mySavedPort);
 
    // get the PICT to be printed, either the poster pict or the current frame pict.
    switch (PICTUsed) {
        case kPrintFrame:
            myPictHandle = GetMoviePict(theMovie, GetMovieTime(theMovie, 0L));
            break;
            
        case kPrintPoster:
            myPictHandle = GetMoviePosterPict(theMovie); 
            break;
 
        default:
            goto Closure;
    }
 
    if (myPictHandle == NULL)
        goto Closure;
 
#if TARGET_RT_LITTLE_ENDIAN
    // change the fields of the Picture structure,
    // if the target runtime environment uses little-endian format for integers
    (**myPictHandle).picSize = EndianS16_BtoL((**myPictHandle).picSize);
    
    (**myPictHandle).picFrame.top = EndianS16_BtoL((**myPictHandle).picFrame.top);
    (**myPictHandle).picFrame.left = EndianS16_BtoL((**myPictHandle).picFrame.left);
    (**myPictHandle).picFrame.bottom = EndianS16_BtoL((**myPictHandle).picFrame.bottom);
    (**myPictHandle).picFrame.right = EndianS16_BtoL((**myPictHandle).picFrame.right);
#endif      
 
    // get the Print record
    myTHPrint = (THPrint)NewHandleClear(sizeof(TPrint));
    if (myTHPrint == NULL)
        goto Closure;
 
    PrOpen();
    isPrinting = true;
    myErr = PrError();
    if (myErr != noErr)
        goto Closure;
 
    PrintDefault(myTHPrint);
 
    // move this to Print SetupÉ if you want to make this look really cool
    myResult = PrStlDialog(myTHPrint);
    if (!myResult)
        goto Closure;
    
    myResult = PrJobDialog(myTHPrint);
    if (!myResult)
        goto Closure;
    
    myPrintPort = PrOpenDoc(myTHPrint, NULL, NULL);
    PrOpenPage(myPrintPort, NULL);
    myErr = PrError();
    if (myErr != noErr)
        goto Closure;
    
    // print at x,y position
    myPictRect = (*myPictHandle)->picFrame;
    MacOffsetRect(&myPictRect, (short)(x - myPictRect.left),  (short)(y - myPictRect.top));
    
    DrawPicture(myPictHandle, &myPictRect);
 
    // if you want to do additional drawing, do it here.
    
    PrClosePage(myPrintPort);
    PrCloseDoc(myPrintPort);
    myErr = PrError();
    if (myErr != noErr)
        goto Closure;
    
    if ((*myTHPrint)->prJob.bJDocLoop == bSpoolLoop)
        PrPicFile(myTHPrint, NULL, NULL, NULL, NULL);
    
    // our closure handling
Closure:
    MacSetPort(mySavedPort);
    
    if (isPrinting)
        PrClose();
    if (myPictHandle)
        KillPicture(myPictHandle);
    if (myTHPrint)
        DisposeHandle((Handle)myTHPrint);
 
    return(myErr);
}
 
 
//////////
//
// QTUtils_SelectAllMovie
// Select the entire movie associated with the specified movie controller.
// 
//////////
 
OSErr QTUtils_SelectAllMovie (MovieController theMC)
{
    TimeRecord          myTimeRecord;
    Movie               myMovie = NULL;
    ComponentResult     myErr = noErr;
    
    if (theMC == NULL)
        return(paramErr);
    
    myMovie = MCGetMovie(theMC);
    if (myMovie == NULL)
        return(paramErr);
    
    myTimeRecord.value.hi = 0;
    myTimeRecord.value.lo = 0;
    myTimeRecord.base = 0;
    myTimeRecord.scale = GetMovieTimeScale(myMovie);    
    myErr = MCDoAction(theMC, mcActionSetSelectionBegin, &myTimeRecord);
    if (myErr != noErr)
        return((OSErr)myErr);
    
    myTimeRecord.value.hi = 0;
    myTimeRecord.value.lo = GetMovieDuration(myMovie);  
    myTimeRecord.base = 0;
    myTimeRecord.scale = GetMovieTimeScale(myMovie);    
    myErr = MCDoAction(theMC, mcActionSetSelectionDuration, &myTimeRecord);
    
    return((OSErr)myErr);
}
 
 
//////////
//
// QTUtils_MakeSampleDescription
// Return a new image description with default and specified values.
// 
//////////
 
ImageDescriptionHandle QTUtils_MakeSampleDescription (long theEffectType, short theWidth, short theHeight)
{
    ImageDescriptionHandle      mySampleDesc = NULL;
    OSErr                       myErr = noErr;
 
    // create a new sample description
    mySampleDesc = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
    if (mySampleDesc == NULL)
        return(NULL);
    
    // fill in the fields of the sample description
    (**mySampleDesc).idSize = sizeof(ImageDescription);
    (**mySampleDesc).cType = theEffectType;
    (**mySampleDesc).vendor = kAppleManufacturer;
    (**mySampleDesc).temporalQuality = codecNormalQuality;
    (**mySampleDesc).spatialQuality = codecNormalQuality;
    (**mySampleDesc).width = theWidth;
    (**mySampleDesc).height = theHeight;
    (**mySampleDesc).hRes = 72L << 16;
    (**mySampleDesc).vRes = 72L << 16;
    (**mySampleDesc).dataSize = 0L;
    (**mySampleDesc).frameCount = 1;
    (**mySampleDesc).depth = 24;
    (**mySampleDesc).clutID = -1;
    
    return(mySampleDesc);
}
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// User data utilities.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
#if CONTENT_RATING_AVAIL
//////////
//
// QTUtils_GetContentRatingFromMovie
// Get the content rating from a movie; return an error if it has none. In any case, return a meaningful
// content rating.
//
//////////
 
OSErr QTUtils_GetContentRatingFromMovie (Movie theMovie, UInt16 *theRating, UInt32 *theReasons)
{
    UserData                    myUserData;
    QTAltContentRatingRecord    myContentRec;
    OSErr                       myErr = paramErr;
 
    *theRating = kQTContentTVYRating;
    *theReasons = 0L;
 
    // make sure we've got a movie
    if (theMovie == NULL)
        return(myErr);
        
    // get the movie's user data list
    myUserData = GetMovieUserData(theMovie);
    if (myUserData != NULL) {
        myErr = GetUserDataItem(myUserData, &myContentRec, sizeof(myContentRec), FOUR_CHAR_CODE('crat'), 0);
        if (myErr == noErr) {
            *theRating = EndianU16_BtoN(myContentRec.ratings);
            *theReasons = EndianU32_BtoN(myContentRec.contentType);
        }
    }
 
    return(myErr);
}
 
 
//////////
//
// QTUtils_AddContentRatingToMovie
// Add a content rating to a movie.
//
// A content rating is stored as a user data item that indicates both the general rating
// (for example, TV-MA [mature audiences only]) and any additional information about the
// content (for example, coarse language). Let's call this additional information the
// "reasons" for the rating. Both the rating and the reasons are specified using constants
// in the file MoviesFormat.h.
//
// This function adds the specified content rating to the movie's user data;
// the updated user data is written to the movie file when the movie is next updated
// (by calling UpdateMovieResource).
// 
//////////
 
OSErr QTUtils_AddContentRatingToMovie (Movie theMovie, UInt16 theRating, UInt32 theReasons)
{
    UserData                    myUserData;
    QTAltContentRatingRecord    myContentRec;
    OSErr                       myErr = noErr;
 
    // get the movie's user data list
    myUserData = GetMovieUserData(theMovie);
    if (myUserData == NULL)
        return(paramErr);
    
    myContentRec.flags = 0;
    myContentRec.contentType = EndianU32_NtoB(theReasons);
    myContentRec.ratings = EndianU16_NtoB(theRating);
    
    // for simplicity, we assume that we want only one content rating in the movie;
    // as a result, we won't worry about overwriting any existing item of that type
 
    // add the data to the movie's user data
    myErr = SetUserDataItem(myUserData, &myContentRec, sizeof(myContentRec), FOUR_CHAR_CODE('crat'), 0);
 
    return(myErr);
}
#endif  // #if CONTENT_RATING_AVAIL
 
 
//////////
//
// QTUtils_AddUserDataTextToMovie
// Add a user data item, of the specified type, containing the specified text to a movie.
//
// This function adds the specified text to the movie's user data;
// the updated user data is written to the movie file when the movie is next updated
// (by calling UpdateMovieResource).
// 
//////////
 
OSErr QTUtils_AddUserDataTextToMovie (Movie theMovie, char *theText, OSType theType)
{
    UserData                    myUserData;
    Handle                      myHandle = NULL;
    short                       myIndex = 0;
    long                        myLength = strlen(theText);
    OSErr                       myErr = noErr;
 
    // get the movie's user data list
    myUserData = GetMovieUserData(theMovie);
    if (myUserData == NULL)
        return(paramErr);
    
    // copy the specified text into a new handle
    myHandle = NewHandleClear(myLength);
    if (myHandle == NULL)
        return(MemError());
 
    BlockMoveData(theText, *myHandle, myLength);
 
    // for simplicity, we assume that we want only one user data item of the specified type in the movie;
    // as a result, we won't worry about overwriting any existing item of that type....
    //
    // if you need multiple user data items of a given type (for example, a copyright notice
    // in several different languages), you would need to modify this code; this is left as an exercise
    // for the reader....
 
    // add the data to the movie's user data
    myErr = AddUserDataText(myUserData, myHandle, theType, myIndex + 1, smSystemScript);
 
    // clean up
    DisposeHandle(myHandle);
    return(myErr);
}
 
 
//////////
//
// QTUtils_AddCopyrightToMovie
// Add a user data item containing the specified copyright text to a movie.
//
//////////
 
OSErr QTUtils_AddCopyrightToMovie (Movie theMovie, char *theText)
{
    return(QTUtils_AddUserDataTextToMovie(theMovie, theText, kUserDataTextCopyright));
}
 
 
//////////
//
// QTUtils_AddMovieNameToMovie
// Add a user data item containing the specified name to a movie.
//
//////////
 
OSErr QTUtils_AddMovieNameToMovie (Movie theMovie, char *theText)
{
    return(QTUtils_AddUserDataTextToMovie(theMovie, theText, kUserDataTextFullName));
}
 
 
//////////
//
// QTUtils_AddMovieInfoToMovie
// Add a user data item containing the specified information to a movie.
//
//////////
 
OSErr QTUtils_AddMovieInfoToMovie (Movie theMovie, char *theText)
{
    return(QTUtils_AddUserDataTextToMovie(theMovie, theText, kUserDataTextInformation));
}
 
 
//////////
//
// QTUtils_GetMovieFileLoopingInfo
// Get the looping state of a movie file.
//
// A movie file can have information about its looping state in a user data item of type 'LOOP'.
// If the movie doesn't contain an item of this type, then we'll assume that it isn't looping.
// If it does contain an item of this type, then the item data (a long integer) is 0 for normal
// looping and 1 for palindrome looping. Accordingly, this function returns the following values
// in the theLoopInfo parameter:
//
//      0 == normal looping
//      1 == palindrome looping
//      2 == no looping
//
//////////
 
OSErr QTUtils_GetMovieFileLoopingInfo (Movie theMovie, long *theLoopInfo)
{
    UserData        myUserData;
    long            myInfo = kNoLooping;
    OSErr           myErr = paramErr;
 
    // make sure we've got a movie
    if (theMovie == NULL)
        goto bail;
        
    // get the movie's user data list
    myUserData = GetMovieUserData(theMovie);
    if (myUserData != NULL)
        myErr = GetUserDataItem(myUserData, &myInfo, sizeof(myInfo), FOUR_CHAR_CODE('LOOP'), 0);
 
bail:
    *theLoopInfo = myInfo;
 
    return(myErr);
}
 
 
//////////
//
// QTUtils_SetMovieFileLoopingInfo
// Set the looping state for a movie file.
//
//////////
 
OSErr QTUtils_SetMovieFileLoopingInfo (Movie theMovie, long theLoopInfo)
{
    UserData        myUserData;
    short           myCount = 0;
    OSErr           myErr = paramErr;
 
    // get the movie's user data
    myUserData = GetMovieUserData(theMovie);
    if (myUserData == NULL)
        goto bail;
 
    // we want to end up with at most one user data item of type 'LOOP',
    // so let's remove any existing ones
    myCount = CountUserDataType(myUserData, FOUR_CHAR_CODE('LOOP'));
    while (myCount--)
        RemoveUserData(myUserData, FOUR_CHAR_CODE('LOOP'), 1);
 
    switch (theLoopInfo) {
        case kNormalLooping:
        case kPalindromeLooping:
            myErr = SetUserDataItem(myUserData, &theLoopInfo, sizeof(long), FOUR_CHAR_CODE('LOOP'), 0);
            break;
 
        case kNoLooping:
        default:
            myErr = noErr;
            break;
    }
 
bail:
    return(myErr);
}
 
 
//////////
//
// QTUtils_SetLoopingStateFromFile
// Set the looping state for a movie based on the looping information in the movie file.
//
//////////
 
OSErr QTUtils_SetLoopingStateFromFile (Movie theMovie, MovieController theMC)
{
    long            myLoopInfo;
    OSErr           myErr = noErr;
 
    myErr = QTUtils_GetMovieFileLoopingInfo(theMovie, &myLoopInfo);
    switch (myLoopInfo) {
 
        case kNormalLooping:
            MCDoAction(theMC, mcActionSetLooping, (void *)true);
            MCDoAction(theMC, mcActionSetLoopIsPalindrome, (void *)false);
            break;
 
        case kPalindromeLooping:
            MCDoAction(theMC, mcActionSetLooping, (void *)true);
            MCDoAction(theMC, mcActionSetLoopIsPalindrome, (void *)true);
            break;
 
        case kNoLooping:
        default:
            MCDoAction(theMC, mcActionSetLooping, (void *)false);
            MCDoAction(theMC, mcActionSetLoopIsPalindrome, (void *)false);
            break;
    }
 
    return(myErr);
}
 
 
//////////
//
// QTUtils_MakeMovieLoop
// Set the specified movie to loop.
//
// Based on the function MakeMovieLoop by Kevin Marks.
//
//////////
 
OSErr QTUtils_MakeMovieLoop (Movie theMovie, Boolean isPalindrome)
{
    TimeBase        myTimeBase = NULL;
    long            myFlags = 0L;
    OSErr           myErr = paramErr;
 
    // make sure we've got a movie
    if (theMovie == NULL)
        goto bail;
    
    myErr = noErr;
        
    // set the movie's play hints to enhance looping performance
    SetMoviePlayHints(theMovie, hintsLoop, hintsLoop);
    
    // set the looping flag of the movie's time base
    myTimeBase = GetMovieTimeBase(theMovie);
    myFlags = GetTimeBaseFlags(myTimeBase);
    myFlags |= loopTimeBase;
    
    // set or clear the palindrome flag, depending on the specified setting
    if (isPalindrome)
        myFlags |= palindromeLoopTimeBase;
    else
        myFlags &= ~palindromeLoopTimeBase;
        
    SetTimeBaseFlags(myTimeBase, myFlags);
 
bail:
    return(myErr);
}
 
 
//////////
//
// QTUtils_GetTrackName
// Get the name of the specified movie track.
//
// This routine is modelled on the one contained in Dispatch 2 from the Ice Floe;
// I've modified it to return a C string instead of a Pascal string.
//
// The caller is responsible for disposing of the pointer returned by this function (by calling free).
//
//////////
 
char *QTUtils_GetTrackName (Track theTrack)
{
    UserData            myUserData;
    char                *myString = NULL;
    OSErr               myErr = noErr;
 
    // make sure we've got a track
    if (theTrack == NULL)
        return(NULL);
        
    // a track's name (if it has one) is stored in the track's user data
    myUserData = GetTrackUserData(theTrack);
    if (myUserData != NULL) {
        Handle          myHandle = NewHandle(0);
 
        // get the user data item of type kUserDataName;
        // the handle we pass to GetUserData is resized to contain the track name
        myErr = GetUserData(myUserData, myHandle, kUserDataName, 1);
        if (myErr == noErr) {
            long        myLength = GetHandleSize(myHandle);
 
            if (myLength > 0) {
                myString = malloc(myLength + 1);
                if (myString != NULL) {
                    memcpy(myString, *myHandle, myLength);
                    myString[myLength] = '\0';
                }
            }           
        }   
 
        DisposeHandle(myHandle);
    }
 
    return(myString);
}
 
 
//////////
//
// QTUtils_SetTrackName
// Set the name of the specified movie track.
//
// This function adds the specified text to the track's user data;
// the updated user data is written to the movie file when the movie is next updated
// (by calling UpdateMovieResource).
//
//////////
 
OSErr QTUtils_SetTrackName (Track theTrack, char *theText)
{
    UserData            myUserData;
    OSErr               myErr = noErr;
 
    // make sure we've got a track and a name
    if ((theTrack == NULL) || (theText == NULL))
        return(paramErr);
        
    // get the track's user data list
    myUserData = GetTrackUserData(theTrack);
    if (myUserData == NULL)
        return(paramErr);
    
    // remove any existing track name
    while (RemoveUserData(myUserData, kUserDataName, 1) == noErr)
        ;
 
    myErr = SetUserDataItem(myUserData, theText, strlen(theText), kUserDataName, 0);
 
    return(myErr);
}
 
 
//////////
//
// QTUtils_MakeTrackNameByType
// Create a (unique) name for the specified track, based on the track's type.
//
// Given a movie track, this routine constructs a name for that track based on
// the media type of that track. For instance, if the track is a sound track,
// this routine returns the name "Sound". However, if there is more than one
// track of that media type, then this routine numbers the track names. So, if
// there are two sound tracks, this routine names them "Sound 1" and "Sound 2".
//
// This routine is modelled on the one contained in Dispatch 2 from the Ice Floe;
// I've modified it to return a C string instead of a Pascal string.
//
// The caller is responsible for disposing of the pointer returned by this function (by calling free).
//
//////////
 
char *QTUtils_MakeTrackNameByType (Track theTrack)
{
    Media               myMedia;
    char                *myString = NULL;
    OSErr               myErr = noErr;
 
    // make sure we've got a track
    if (theTrack == NULL)
        return(NULL);
    
    myMedia = GetTrackMedia(theTrack);
    if (myMedia != NULL) {
        MediaHandler    myMediaHandler = GetMediaHandler(myMedia);
        OSType          myMediaType;
        Str255          myName;
 
        // get the media type of the track
        GetMediaHandlerDescription(myMedia, &myMediaType, NULL, NULL);
        
        // get the text of the media type
        myErr = MediaGetName(myMediaHandler, myName, 0, NULL);
        if (myErr == noErr) {
            
            // determine whether there's more than one track with this media type
            if (GetMovieIndTrackType(GetTrackMovie(theTrack), 2, myMediaType, movieTrackMediaType) != NULL) {
 
                // add an index number to the track type string we constructed above
                long    myIndex = 1;
                Str255  myNumString;
 
                while (GetMovieIndTrackType(GetTrackMovie(theTrack), myIndex, myMediaType, movieTrackMediaType) != theTrack)
                   myIndex++;
 
                NumToString(myIndex, myNumString);
                myName[++myName[0]] = ' ';
                BlockMoveData(&myNumString[1], &myName[myName[0] + 1], myNumString[0]);
                myName[0] += myNumString[0];
            }
 
            // now copy the string data from the Pascal string to a C string
            if (myName[0] > 0) {
                myString = malloc(myName[0] + 1);
                if (myString != NULL) {
                    memcpy(myString, &myName[1], myName[0]);
                    myString[myName[0]] = '\0';
                }
            }           
        }
    }
 
    return(myString);
}
 
 
//////////
//
// QTUtils_IsImageFile
// Is the specified file an image file?
//
//////////
 
Boolean QTUtils_IsImageFile (FSSpec *theFSSpec)
{   
    Boolean                     isImageFile = false;
    GraphicsImportComponent     myImporter = NULL;
 
    GetGraphicsImporterForFile(theFSSpec, &myImporter);
    if (myImporter != NULL) {
        // this file is a still image
        isImageFile = true;
        CloseComponent(myImporter);
    }
 
    return(isImageFile);
}
 
 
//////////
//
// QTUtils_IsMovieFile
// Is the specified file a file that can be opened by QuickTime as a movie?
//
// This apparently isn't working correctly: GMIFDR always returns -2003 (cantFindHandler)
//
//////////
 
Boolean QTUtils_IsMovieFile (FSSpec *theFSSpec)
{   
    Boolean                     isMovieFile = false;
    AliasHandle                 myAlias = NULL;
    Component                   myImporter = NULL;
    OSErr                       myErr = noErr;
 
    NewAliasMinimal(theFSSpec, &myAlias);
    GetMovieImporterForDataRef(rAliasType, (Handle)myAlias, kGetMovieImporterDontConsiderGraphicsImporters, &myImporter);
 
    DisposeHandle((Handle)myAlias);
    if (myImporter != NULL)     // this file is a movie file
        isMovieFile = true;
 
    return(isMovieFile);
}
 
 
//////////
//
// QTUtils_ConvertFloatToBigEndian
// Convert the specified floating-point number to big-endian format.
//
//////////
 
void QTUtils_ConvertFloatToBigEndian (float *theFloat)
{
    unsigned long       *myLongPtr;
    
    myLongPtr = (unsigned long *)theFloat;
    *myLongPtr = EndianU32_NtoB(*myLongPtr);
}
 
 
//////////
//
// QTUtils_ConvertCToPascalString
// Convert a C string into a Pascal string.
//
// The caller is responsible for disposing of the pointer returned by this function (by calling free).
//
//////////
 
StringPtr QTUtils_ConvertCToPascalString (char *theString)
{
    StringPtr   myString = malloc(strlen(theString) + 1);
    short       myIndex = 0;
 
    while (theString[myIndex] != '\0') {
        myString[myIndex + 1] = theString[myIndex];
        myIndex++;
    }
    
    myString[0] = (unsigned char)myIndex;
    
    return(myString);
}
 
 
//////////
//
// QTUtils_ConvertPascalToCString
// Convert a Pascal string into a C string.
//
// The caller is responsible for disposing of the pointer returned by this function (by calling free).
//
//////////
 
char *QTUtils_ConvertPascalToCString (StringPtr theString)
{
    char        *myString = malloc(theString[0] + 1);
    short       myIndex = 0;
 
    for (myIndex = 0; myIndex < theString[0]; myIndex++)
        myString[myIndex] = theString[myIndex + 1];
    
    myString[theString[0]] = '\0';
    
    return(myString);
}
 
 
//////////
//
// QTUtils_DeleteAllReferencesToTrack
// Delete all existing track references to the specified track.
//
//////////
 
OSErr QTUtils_DeleteAllReferencesToTrack (Track theTrack)
{
    Movie               myMovie = NULL;
    Track               myTrack = NULL;
    long                myTrackCount = 0L;
    long                myTrRefCount = 0L;
    long                myTrackIndex;
    long                myTrRefIndex;
    OSErr               myErr = noErr;
 
    myMovie = GetTrackMovie(theTrack);
    if (myMovie == NULL)
        return(paramErr);
 
    // iterate thru all the tracks in the movie (that are different from the specified track)
    myTrackCount = GetMovieTrackCount(myMovie);
    for (myTrackIndex = 1; myTrackIndex <= myTrackCount; myTrackIndex++) {
        myTrack = GetMovieIndTrack(myMovie, myTrackIndex);
        if ((myTrack != NULL) && (myTrack != theTrack)) {
            OSType      myType = 0L;
    
            // iterate thru all track reference types contained in the current track
            myType = GetNextTrackReferenceType(myTrack, myType);
            while (myType != 0L) {
 
                // iterate thru all track references of the current type;
                // note that we count down to 1, since DeleteTrackReference will cause
                // any higher-indexed track references to be renumbered
                myTrRefCount = GetTrackReferenceCount(myTrack, myType);
                for (myTrRefIndex = myTrRefCount; myTrRefIndex >= 1; myTrRefIndex--) {
                    Track   myRefTrack = NULL;
 
                    myRefTrack = GetTrackReference(myTrack, myType, myTrRefIndex);
                    if (myRefTrack == theTrack)
                        myErr = DeleteTrackReference(myTrack, myType, myTrRefIndex);
                }
 
                myType = GetNextTrackReferenceType(myTrack, myType);
            }
        }
    }
 
    return(myErr);
}
 
 
//////////
//
// QTUtils_GetFrameDuration
// Get the duration of the first sample frame in the specified movie track.
//
//////////
 
TimeValue QTUtils_GetFrameDuration (Track theTrack)
{   
    TimeValue   mySampleDuration;
    OSErr       myErr = noErr;
 
    myErr = GetMediaSample( GetTrackMedia(theTrack),
                            NULL,       // don't return sample data
                            0,
                            NULL,       // don't return number of bytes of sample data returned
                            0,
                            NULL,
                            &mySampleDuration,
                            NULL,       // don't return sample description
                            NULL,       // don't return sample description index
                            0,          // no max number of samples
                            NULL,       // don't return number of samples returned
                            NULL);      // don't return sample flags
 
    // make sure we return a legitimate value even if GetMediaSample encounters an error
    if (myErr != noErr)
        mySampleDuration = 0;
 
    return(mySampleDuration);
}
 
 
//////////
//
// QTUtils_GetFrameCount
// Get the number of frames in the specified movie track. We return the value -1 if
// an error occurs and we cannot determine the number of frames in the track.
//
// Based (loosely) on frame-counting code in ConvertToMovie Jr.c.
// 
// We count the frames in the track by stepping through all of its interesting times
// (the places where the track displays a new sample).
//
//////////
 
long QTUtils_GetFrameCount (Track theTrack)
{   
    long        myCount = -1;
    short       myFlags;
    TimeValue   myTime = 0;
    
    if (theTrack == NULL)
        goto bail;
        
    // we want to begin with the first frame (sample) in the track
    myFlags = nextTimeMediaSample + nextTimeEdgeOK;
 
    while (myTime >= 0) {
        myCount++;
        
        // look for the next frame in the track; when there are no more frames,
        // myTime is set to -1, so we'll exit the while loop
        GetTrackNextInterestingTime(theTrack, myFlags, myTime, fixed1, &myTime, NULL);
        
        // after the first interesting time, don't include the time we're currently at
        myFlags = nextTimeStep;
    }
 
bail:
    return(myCount);
}
 
 
//////////
//
// QTUtils_GetMaxWindowDepth
// Get the deepest pixel type and size of the screen area intersected by a specified window.
//
//////////
 
void QTUtils_GetMaxWindowDepth (CWindowPtr theWindow, short *thePixelType, short *thePixelSize)
{
    Rect            myRect;
    Point           myPoint = {0, 0};
    
    // initialize returned values 
    *thePixelType = k1MonochromePixelFormat;
    *thePixelSize = 0;
 
    if (theWindow == NULL)
        return;
 
    myRect = theWindow->portRect;
    
    // assume the window is the current port
    LocalToGlobal(&myPoint);
 
    // offset the rectangle to global coordinates
    MacOffsetRect(&myRect, myPoint.h, myPoint.v);
    
    // get the max data for the global rectangle
    QTUtils_GetMaxScreenDepth(&myRect, thePixelType, thePixelSize);
}
 
 
//////////
//
// QTUtils_GetMaxScreenDepth
// Get the deepest pixel type and size of the screen area intersected by a specified rectangle.
//
//////////
 
void QTUtils_GetMaxScreenDepth (Rect *theGlobalRect, short *thePixelType, short *thePixelSize)
{
    GDHandle        myGDevice = NULL;
    PixMapHandle    myPixMap = NULL;
 
    myGDevice = GetMaxDevice(theGlobalRect);    // get the max device
    if (myGDevice != NULL) {
        // get the pixmap for the max device
        myPixMap = (**myGDevice).gdPMap;
        if (myPixMap != NULL) {
            // extract the interesting info from the pixmap of the device
            *thePixelType = (**myPixMap).pixelType;
            *thePixelSize = (**myPixMap).pixelSize;
        }
    }
}
 
 
//////////
//
// QTUtils_GetUsersConnectionSpeed
// Return the connection speed selected by the user in the QuickTime Settings control panel;
// return 0 if the user's QuickTime preferences cannot be read.
//
// Based on code in Ice Floe Dispatch 17 by Mike Dodd.
//
//////////
 
long QTUtils_GetUsersConnectionSpeed (void)
{
    QTAtomContainer                 myPrefsContainer = NULL;
    QTAtom                          myPrefsAtom = 0;
    ConnectionSpeedPrefsRecord      myPrefsRec;
    long                            myDataSize = 0L;
    long                            mySpeed = 0L;
    Ptr                             myAtomData = NULL;
    OSErr                           myErr = noErr;
 
    // you can retrieve the user's QuickTime preferences by calling GetQuickTimePreference;
    // the first parameter indicates the type of preference you want information about, and
    // the second parameter is an atom container that contains the returned preference data 
    myErr = GetQuickTimePreference(ConnectionSpeedPrefsType, &myPrefsContainer);
    if (myErr == noErr) {
            
        // find the atom of the desired type
        myPrefsAtom = QTFindChildByID(myPrefsContainer, kParentAtomIsContainer, ConnectionSpeedPrefsType, 1, NULL);
        if (myPrefsAtom == 0) {
            // we did not find any such atom in the returned atom container, so we'll
            // return a default connection speed setting of 28.8K bytes per second
            mySpeed = kDataRate288ModemRate;
        } else {
            // we found the desired atom in the returned atom container;
            // read the data contained in that atom and verify that the data is of the
            // size we are expecting
            QTGetAtomDataPtr(myPrefsContainer, myPrefsAtom, &myDataSize, &myAtomData);
            
            if (myDataSize != sizeof(ConnectionSpeedPrefsRecord)) {
                // the data in the atom isn't the right size, so it must be corrupt;
                // return a default connection speed setting of 28.8K bytes per second
                mySpeed = kDataRate288ModemRate;
            } else {
                // everything is fine: read the connection speed
                
                // NOTE: the data in this atom is native-endian, so we do not need to
                // perform any endian-swapping when extracting the speed from the atom.
                // (This is an exception to the rule that data in atom containers is
                // always big-endian.)
                myPrefsRec = *(ConnectionSpeedPrefsRecord *)myAtomData;
                mySpeed = myPrefsRec.connectionSpeed;
            }
        }
 
        QTDisposeAtomContainer(myPrefsContainer);
    }
    
    return(mySpeed);
}
 
 
//////////
//
// QTUtils_SetUsersConnectionSpeed
// Set the connection speed in the QuickTime Settings control panel to the specified value.
//
// NOTE: In general, you should let the user decide the connection speed (using the QuickTime
// Settings control panel). In some cases, however, you might need to do this programmatically.
// Also, you should in general use values for theSpeed that are enumerated in the header file
// MoviesFormat.h.
//
// Based on code in Ice Floe Dispatch 17 by Mike Dodd.
//
//////////
 
OSErr QTUtils_SetUsersConnectionSpeed (long theSpeed)
{
    QTAtomContainer                 myPrefsContainer = NULL;
    QTAtom                          myPrefsAtom = 0;
    ConnectionSpeedPrefsRecord      myPrefsRec;
    OSErr                           myErr = noErr;
 
    myErr = QTNewAtomContainer(&myPrefsContainer);
    if (myErr == noErr) {
        // NOTE: the data in this atom is native-endian, so we do not need to
        // perform any endian-swapping when inserting the speed into the atom.
        // (This is an exception to the rule that data in atom containers is
        // always big-endian.)
        myPrefsRec.connectionSpeed = theSpeed;
        
        myErr = QTInsertChild(myPrefsContainer, kParentAtomIsContainer, ConnectionSpeedPrefsType, 1, 0, sizeof(ConnectionSpeedPrefsRecord), &myPrefsRec, &myPrefsAtom);
        if (myErr == noErr)
            myErr = SetQuickTimePreference(ConnectionSpeedPrefsType, myPrefsContainer);
            
        QTDisposeAtomContainer(myPrefsContainer);
    }
 
    return(myErr);
}
 
 
#if CONTENT_RATING_AVAIL
//////////
//
// QTUtils_GetUsersContentRating
// Return, through the function's parameters, the content rating and acceptable content types
// selected by the user in the QuickTime Settings control panel; return an error if the user's
// QuickTime preferences cannot be read.
//
// Based on QTUtils_GetUsersConnectionSpeed.
//
//////////
 
OSErr QTUtils_GetUsersContentRating (UInt32 *theType, UInt16 *theRating)
{
    QTAtomContainer                 myPrefsContainer = NULL;
    QTAtom                          myPrefsAtom = 0;
    ContentRatingPrefsRecord        myContentRec;
    long                            myDataSize = 0L;
    Ptr                             myAtomData = NULL;
    OSErr                           myErr = noErr;
 
    // you can retrieve the user's QuickTime preferences by calling GetQuickTimePreference;
    // the first parameter indicates the type of preference you want information about, and
    // the second parameter is an atom container that contains the returned preference data 
    myErr = GetQuickTimePreference(kContentRatingPrefsType, &myPrefsContainer);
    if (myErr == noErr) {
            
        // find the atom of the desired type
        myPrefsAtom = QTFindChildByID(myPrefsContainer, kParentAtomIsContainer, kContentRatingPrefsType, 1, NULL);
        if (myPrefsAtom == 0) {
            // we did not find any such atom in the returned atom container, so we'll
            // return default settings
            *theType = 0L;
            *theRating = kQTContentTVYRating;
        } else {
            // we found the desired atom in the returned atom container;
            // read the data contained in that atom and verify that the data is of the
            // size we are expecting
            myErr = QTGetAtomDataPtr(myPrefsContainer, myPrefsAtom, &myDataSize, &myAtomData);
            
            if (myDataSize != sizeof(ContentRatingPrefsRecord)) {
                // the data in the atom isn't the right size, so it must be corrupt;
                // return default settings
                *theType = 0L;
                *theRating = kQTContentTVYRating;
            } else {
                // everything is fine: read the content information
                
                // NOTE: the data in this atom is native-endian, so we do not need to
                // perform any endian-swapping when extracting the data from the atom.
                // (This is an exception to the rule that data in atom containers is
                // always big-endian.)
                
                // WARNING: the format of the data in a content rating atom is, to my
                // knowledge, currently undocumented; the following method of extracting
                // that info is based on empirical investigation.
                myContentRec = *(ContentRatingPrefsRecord *)myAtomData;
                *theType = (UInt32)(~(myContentRec.fContentTypes) & 0x00ff);
                *theRating = myContentRec.fContentRating;
            }
        }
 
        QTDisposeAtomContainer(myPrefsContainer);
    }
    
    return(myErr);
}
#endif  // #if CONTENT_RATING_AVAIL
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Controller utilities.
//
// Use these functions to manipulate movie controllers.
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////
 
//////////
//
// QTUtils_GetControllerType
// Get the controller type of the specified movie.
//
//////////
 
OSType QTUtils_GetControllerType (Movie theMovie) 
{
    UserData        myUserData;
    OSType          myType = kQTVRUnknownType;
    
    // make sure we've got a movie
    if (theMovie == NULL)
        return(myType);
        
    myUserData = GetMovieUserData(theMovie);
    if (myUserData != NULL)
        GetUserDataItem(myUserData, &myType, sizeof(myType), kUserDataMovieControllerType, 0);
    
    return(EndianU32_BtoN(myType));
}
 
 
//////////
//
// QTUtils_SetControllerType
// Set the controller type of the specified movie.
//
// This function adds an item to the movie's user data;
// the updated user data is written to the movie file when the movie is next updated
// (by calling AddMovieResource or UpdateMovieResource).
//
// NOTE: This function is intended to set the controller type of a movie you're building;
// to change the controller of an open movie, use QTUtils_ChangeControllerType.
//
//////////
 
OSErr QTUtils_SetControllerType (Movie theMovie, OSType theType)
{
    UserData        myUserData;
    OSErr           myErr = noErr;
 
    // make sure we've got a movie
    if (theMovie == NULL)
        return(paramErr);
        
    // get the movie's user data list
    myUserData = GetMovieUserData(theMovie);
    if (myUserData == NULL)
        return(paramErr);
    
    theType = EndianU32_NtoB(theType);
    myErr = SetUserDataItem(myUserData, &theType, sizeof(theType), kUserDataMovieControllerType, 0);
 
    return(myErr);
}
 
 
//////////
//
// QTUtils_ChangeControllerType
// Change the controller type of the movie that uses the specified controller, "on the fly",
// and return the new movie controller to the caller; if for some reason we cannot create a
// new movie controller, return NULL.
//
//////////
 
MovieController QTUtils_ChangeControllerType (MovieController theMC, OSType theType, long theFlags)
{
    MovieController     myMC = NULL;
    Movie               myMovie = NULL;
    Rect                myRect;
    OSErr               myErr = noErr;
 
    // make sure we've got a movie controller
    if (theMC == NULL)
        return(NULL);
    
    // get the movie associated with that controller
    myMovie = MCGetMovie(theMC);
    if (myMovie == NULL)
        return(NULL);
        
    GetMovieBox(myMovie, &myRect);
 
    // set the new controller type in the movie's user data list
    myErr = QTUtils_SetControllerType(myMovie, theType);
    if (myErr != noErr)
        return(NULL);
 
    // dispose of the existing controller
    DisposeMovieController(theMC);
    
    // create a new controller of the specified type
    myMC = NewMovieController(myMovie, &myRect, theFlags);
 
    return(myMC);
}
 
 
#endif  // ifndef __QTUtilities__