QTInfo.c

//////////
//
//  File:       QTInfo.c
//
//  Contains:   Sample code for working with QuickTime movie information.
//
//  Written by: Tim Monroe
//
//  Copyright:  © 2000 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//     
//     <1>      04/23/00    rtm     first file
//
//////////
 
 
//////////
//
// header files
//
//////////
 
#include "QTInfo.h"
 
 
//////////
//
// global variables
//
//////////
 
extern short                gAppResFile;                        // file reference number for this application's resource file
extern ModalFilterUPP       gModalFilterUPP;                    // UPP to our custom dialog event filter
 
 
//////////
//
// QTInfo_MovieHasPreview
// Does the specified movie have a movie preview?
//
//////////
 
Boolean QTInfo_MovieHasPreview (Movie theMovie)
{
    TimeValue           myStart;
    TimeValue           myDuration;
    long                myCount = 0L;
    long                myIndex = 0L;
    Track               myTrack = NULL;
    long                myUsage = 0L;
    Boolean             myHasPreview = false;
 
    // see if the movie has a positive preview duration
    GetMoviePreviewTime(theMovie, &myStart, &myDuration);
    if (myDuration > 0)
        myHasPreview = true;
 
    // make sure that some track is used in the movie preview
    myCount = GetMovieTrackCount(theMovie);
    for (myIndex = 1; myIndex <= myCount; myIndex++) {
        myTrack = GetMovieIndTrack(theMovie, myIndex);
        if (myTrack == NULL)
            continue;
            
        myUsage = GetTrackUsage(myTrack);
        if (myUsage & trackUsageInPreview)
            break;              // we found a track with the trackUsageInPreview flag set; break out of the loop
    }
    
    if (myIndex > myCount)
        myHasPreview = false;   // we went thru all tracks without finding one with a preview usage
 
    return(myHasPreview);
}
 
 
//////////
//
// QTInfo_MovieHasPoster
// Does the specified movie have a movie poster?
//
//////////
 
Boolean QTInfo_MovieHasPoster (Movie theMovie)
{
    long                myCount = 0L;
    long                myIndex = 0L;
    Track               myTrack = NULL;
    long                myUsage = 0L;
    Boolean             myHasPoster = true;
 
    // make sure that some track is used in the movie poster
    myCount = GetMovieTrackCount(theMovie);
    for (myIndex = 1; myIndex <= myCount; myIndex++) {
        myTrack = GetMovieIndTrack(theMovie, myIndex);
        if (myTrack == NULL)
            continue;
            
        myUsage = GetTrackUsage(myTrack);
        if (myUsage & trackUsageInPoster)
            break;              // we found a track with the trackUsageInPoster flag set; break out of the loop
    }
    
    if (myIndex > myCount)
        myHasPoster = false;    // we went thru all tracks without finding one with a poster usage
 
    return(myHasPoster);
}
 
 
//////////
//
// QTInfo_SetPreviewToSelection
// Set the movie preview to be the current movie selection.
//
//////////
 
OSErr QTInfo_SetPreviewToSelection (Movie theMovie, MovieController theMC)
{
    TimeValue           myStart;
    TimeValue           myDuration;
    ComponentResult     myErr = noErr;
    
    GetMovieSelection(theMovie, &myStart, &myDuration);
    SetMoviePreviewTime(theMovie, myStart, myDuration);
 
    myErr = MCMovieChanged(theMC, theMovie);
    
    return((OSErr)myErr);
}
 
 
//////////
//
// QTInfo_SetSelectionToPreview
// Set the movie selection to the current movie preview.
//
//////////
 
OSErr QTInfo_SetSelectionToPreview (Movie theMovie, MovieController theMC)
{
    TimeValue           myStart;
    TimeValue           myDuration;
    ComponentResult     myErr = noErr;
 
    GetMoviePreviewTime(theMovie, &myStart, &myDuration);
    SetMovieSelection(theMovie, myStart, myDuration);
 
    myErr = MCMovieChanged(theMC, theMovie);
    
    return((OSErr)myErr);
}
 
 
//////////
//
// QTInfo_ClearPreview
// Clear the movie preview.
//
//////////
 
OSErr QTInfo_ClearPreview (Movie theMovie, MovieController theMC)
{
    long                myCount = 0L;
    long                myIndex = 0L;
    Track               myTrack = NULL;
    long                myUsage = 0L;
    ComponentResult     myErr = noErr;
 
    // set the movie preview start time and duration to 0
    SetMoviePreviewTime(theMovie, 0, 0);
 
    // remove all tracks that are used *only* in the movie preview
    myCount = GetMovieTrackCount(theMovie);
    for (myIndex = myCount; myIndex >= 1; myIndex--) {
        myTrack = GetMovieIndTrack(theMovie, myIndex);
        if (myTrack == NULL)
            continue;
        
        myUsage = GetTrackUsage(myTrack);
        myUsage &= trackUsageInMovie | trackUsageInPreview | trackUsageInPoster;
        if (myUsage == trackUsageInPreview)
            DisposeMovieTrack(myTrack);
    }
 
    // add trackUsageInPreview to any remaining tracks that are in the movie
    // (so that subsequently setting the preview to a selection will include
    // these tracks)
    myCount = GetMovieTrackCount(theMovie);
    for (myIndex = 1; myIndex <= myCount; myIndex++) {
        myTrack = GetMovieIndTrack(theMovie, myIndex);
        if (myTrack == NULL)
            continue;
            
        myUsage = GetTrackUsage(myTrack);
        if (myUsage & trackUsageInMovie)
            SetTrackUsage(myTrack, myUsage | trackUsageInPreview);
    }
 
    myErr = MCMovieChanged(theMC, theMovie);
    
    return((OSErr)myErr);
}
 
 
//////////
//
// QTInfo_GoToPosterFrame
// Set the current movie time to the movie poster time.
//
//////////
 
OSErr QTInfo_GoToPosterFrame (Movie theMovie, MovieController theMC)
{
    TimeRecord          myTimeRecord;
    ComponentResult     myErr = noErr;
 
    // stop the movie from playing
    myErr = MCDoAction(theMC, mcActionPlay, (void *)0L);
    if (myErr != noErr)
        goto bail;
        
    // set up a time record with the desired movie time, scale, and base
    myTimeRecord.value.hi = 0;
    myTimeRecord.value.lo = GetMoviePosterTime(theMovie);
    myTimeRecord.base = GetMovieTimeBase(theMovie);
    myTimeRecord.scale = GetMovieTimeScale(theMovie);   
 
    myErr = MCDoAction(theMC, mcActionGoToTime, &myTimeRecord);
 
bail:
    return((OSErr)myErr);
}
 
 
//////////
//
// QTInfo_SetPosterToFrame
// Set the movie poster time to the current movie time.
//
//////////
 
OSErr QTInfo_SetPosterToFrame (Movie theMovie, MovieController theMC)
{
    TimeValue           myTime;
    ComponentResult     myErr = noErr;
 
    // stop the movie from playing
    myErr = MCDoAction(theMC, mcActionPlay, (void *)0L);
    if (myErr != noErr)
        goto bail;
    
    myTime = GetMovieTime(theMovie, NULL);
    SetMoviePosterTime(theMovie, myTime);
 
    myErr = MCMovieChanged(theMC, theMovie);
    
bail:
    return((OSErr)myErr);
}
 
 
//////////
//
// QTInfo_MakeFilePreview
// Add a file preview to the specified file.
//
// If theRefNum picks out a resource fork, we simply call the ICM function MakeFilePreview.
// If theRefNum picks out a data fork, we need to add a 'pnot' atom to that file; we do this
// by appending the appropriate atom data to the end of the file. This strategy will fail if
// the atom that was previously the last one in the file does not have a correct length value
// in its atom header.
//
// For poster previews, we also need to add an atom of type 'PICT' to the end of the file.
//
// NOTE: The current version of this function does not replace any existing 'pnot' atom in the
// data fork. A better algorithm would be: determine which type of file preview is being created,
// and count any existing preview data atoms of that type; then look for an existing 'pnot' atom;
// if one is found, update it to use the (possibly new) file preview type and set the index to
// one more than the number of existing preview data atoms of that type; than add the new preview
// data atom. These and other enhancements are left as an exercise for the reader.
//
//////////
 
OSErr QTInfo_MakeFilePreview (Movie theMovie, short theRefNum, ICMProgressProcRecordPtr theProgressProc)
{
    unsigned long           myModDate;
    PreviewResourceRecord   myPNOTRecord;
    long                    myEOF;
    long                    mySize;
    unsigned long           myAtomHeader[2];    // an atom header: size and type
    OSType                  myPreviewType;
    OSErr                   myErr = noErr;
 
    //////////
    //
    // determine whether theRefNum is a file reference number of a data fork or a resource fork;
    // if it's a resource fork, then we'll just call the existing ICM function MakeFilePreview
    //
    //////////
 
    if (QTInfo_IsRefNumOfResourceFork(theRefNum)) {
        myErr = MakeFilePreview(theRefNum, theProgressProc);
        goto bail;
    }
    
    //////////
    //
    // determine the preview type
    //
    //////////
    
    // if the movie has a movie preview, use that as the file preview; otherwise use a thumbnail
    // of the movie poster frame as the file preview
    if (QTInfo_MovieHasPreview(theMovie))
        myPreviewType = MovieAID;
    else
        myPreviewType = kQTFileTypePicture;
 
    //////////
    //
    // construct the 'pnot' atom
    //
    //////////
 
    // fill in the 'pnot' atom header
    myAtomHeader[0] = EndianU32_NtoB(sizeof(myAtomHeader) + sizeof(myPNOTRecord));
    myAtomHeader[1] = EndianU32_NtoB(ShowFilePreviewComponentType);
    
    // fill in the 'pnot' atom data
    GetDateTime(&myModDate);
    
    myPNOTRecord.modDate = EndianU32_NtoB(myModDate);       // the modification time of the preview
    myPNOTRecord.version = EndianS16_NtoB(0);               // version number; must be 0
    myPNOTRecord.resType = EndianU32_NtoB(myPreviewType);   // atom type containing preview
    myPNOTRecord.resID = EndianS16_NtoB(1);                 // the 1-based index of the atom of the specified type to use
    
    //////////
    //
    // write the 'pnot' atom at the end of the data fork
    //
    //////////
 
    // get the current logical end-of-file and extend it by the desired amount
    myErr = GetEOF(theRefNum, &myEOF);
    if (myErr != noErr)
        goto bail;
    
    myErr = SetEOF(theRefNum, myEOF + sizeof(myAtomHeader) + sizeof(myPNOTRecord));
    if (myErr != noErr)
        goto bail;
 
    // set the file mark
    myErr = SetFPos(theRefNum, fsFromStart, myEOF);
    if (myErr != noErr)
        goto bail;
    
    // write the atom header into the file
    mySize = sizeof(myAtomHeader);
    myErr = FSWrite(theRefNum, &mySize, myAtomHeader);
    if (myErr != noErr)
        goto bail;
    
    // write the atom data into the file
    mySize = sizeof(myPNOTRecord);
    myErr = FSWrite(theRefNum, &mySize, &myPNOTRecord);
    if (myErr != noErr)
        goto bail;
    
    //////////
    //
    // write the preview data atom at the end of the data fork
    //
    //////////
 
    if (myPreviewType == MovieAID) {
        // in theory, we don't need to do anything here, since our 'pnot' atom points
        // to the 'moov' atom; in practice, this doesn't work correctly (it seems like
        // a bug in StandardGetFilePreview)
    }
    
    if (myPreviewType == kQTFileTypePicture) {
        PicHandle       myPicture = NULL;
        PicHandle       myThumbnail = NULL;
 
        // get the poster frame picture
        myPicture = GetMoviePosterPict(theMovie);
        if (myPicture != NULL) {
        
            // create a thumbnail
            myThumbnail = (PicHandle)NewHandleClear(4);
            if (myThumbnail != NULL) {
                myErr = MakeThumbnailFromPicture(myPicture, 0, myThumbnail, theProgressProc);
                if (myErr == noErr) {
                
                    myAtomHeader[0] = EndianU32_NtoB(sizeof(myAtomHeader) + GetHandleSize((Handle)myThumbnail));
                    myAtomHeader[1] = EndianU32_NtoB(myPreviewType);
 
                    // write the atom header into the file
                    mySize = sizeof(myAtomHeader);
                    myErr = FSWrite(theRefNum, &mySize, myAtomHeader);
                    if (myErr == noErr) {
                        // write the atom data into the file
                        mySize = GetHandleSize((Handle)myThumbnail);
                        myErr = FSWrite(theRefNum, &mySize, *myThumbnail);
                    }
                }
                
                KillPicture(myThumbnail);
            }
        
            KillPicture(myPicture);
        }
    }
    
#if 0
    // here's how you'd add a text preview; note that the text is hard-coded here....
    if (myPreviewType == kQTFileTypeText) {
        char    myText[] = "The penguin gradually appears from the mist of the ice floe.";
        
        myAtomHeader[0] = EndianU32_NtoB(sizeof(myAtomHeader) + strlen(myText));
        myAtomHeader[1] = EndianU32_NtoB(myPreviewType);
 
        // write the atom header into the file
        mySize = sizeof(myAtomHeader);
        myErr = FSWrite(theRefNum, &mySize, myAtomHeader);
        if (myErr != noErr)
            goto bail;
        
        // write the atom data into the file
        mySize = strlen(myText);
        myErr = FSWrite(theRefNum, &mySize, myText);
        if (myErr != noErr)
            goto bail;
    }
#endif
    
#if TARGET_OS_MAC
    if (myErr == noErr) {
        short               myVolNum;
        
        // flush the volume
        myErr = GetVRefNum(theRefNum, &myVolNum);
        if (myErr != noErr)
            goto bail;
        
        myErr = FlushVol(NULL, myVolNum);
    }   
#endif  
 
bail:
    return(myErr);
}
 
 
//////////
//
// QTInfo_IsRefNumOfResourceFork
// Does the specified file reference number refer to an open resource fork?
//
//////////
 
Boolean QTInfo_IsRefNumOfResourceFork (short theRefNum)
{
    FCBPBRec        myFCBPBRec;
    Boolean         myIsResFork = false;
    OSErr           myErr = noErr;
    
    myFCBPBRec.ioCompletion = NULL;
    myFCBPBRec.ioNamePtr = NULL;
    myFCBPBRec.ioVRefNum = 0;
    myFCBPBRec.ioRefNum = theRefNum;
    myFCBPBRec.ioFCBIndx = 0;
    
    myErr = PBGetFCBInfoSync(&myFCBPBRec);
    if (myErr == noErr)
        if (myFCBPBRec.ioFCBFlags & kioFCBResourceMask)
            myIsResFork = true;
    
    return(myIsResFork);
}
 
 
//////////
//
// QTInfo_EditAnnotation
// Allow the user to enter or edit the annotation of the specified sort. Return a Boolean value
// that indicates whether the user clicked the OK button in the displayed dialog box and the text was
// successfully added to the movie (it may of course have been the same text already in the movie).
//
//////////
 
Boolean QTInfo_EditAnnotation (Movie theMovie, OSType theType)
{
    DialogPtr       myDialog = NULL;
    short           myItem;
    short           mySavedResFile;
    GrafPtr         mySavedPort;
    Handle          myHandle = NULL;
    short           myItemKind;
    Handle          myItemHandle;
    UserData        myUserData = NULL;
    Rect            myItemRect;
    Str255          myString;
    Boolean         myIsChanged = false;
    OSErr           myErr = noErr;
    
    //////////
    //
    // save the current resource file and graphics port
    //
    //////////
 
    mySavedResFile = CurResFile();
    GetPort(&mySavedPort);
 
    // set the application's resource file
    UseResFile(gAppResFile);
 
    // get the movie user data
    myUserData = GetMovieUserData(theMovie);
    if (myUserData == NULL)
        goto bail;
 
    //////////
    //
    // create the dialog box in which the user will add or edit the annotation
    //
    //////////
 
    myDialog = GetNewDialog(kEditTextResourceID, NULL, (WindowPtr)-1L);
    if (myDialog == NULL)
        goto bail;
 
    MacSetPort(GetDialogPort(myDialog));
    
    SetDialogDefaultItem(myDialog, kEditTextItemOK);
    SetDialogCancelItem(myDialog, kEditTextItemCancel);
    
    // get a string for the specified annotation type
    switch (theType) {
        case kUserDataTextFullName:
            GetIndString(myString, kTextKindsResourceID, kTextKindsFullName);
            break;
    
        case kUserDataTextCopyright:
            GetIndString(myString, kTextKindsResourceID, kTextKindsCopyright);
            break;
    
        case kUserDataTextInformation:
            GetIndString(myString, kTextKindsResourceID, kTextKindsInformation);
            break;
    }
    
    GetDialogItem(myDialog, kEditTextItemEditLabel, &myItemKind, &myItemHandle, &myItemRect);
    SetDialogItemText(myItemHandle, myString);
 
    //////////
    //
    // set the current annotation of the specified type, if it exists
    //
    //////////
 
    myHandle = NewHandleClear(4);
    if (myHandle != NULL) {
        myErr = GetUserDataText(myUserData, myHandle, theType, 1, GetScriptManagerVariable(smRegionCode));
        if (myErr == noErr) {
            QTInfo_TextHandleToPString(myHandle, myString);
            GetDialogItem(myDialog, kEditTextItemEditBox, &myItemKind, &myItemHandle, &myItemRect);
            SetDialogItemText(myItemHandle, myString);
            SelectDialogItemText(myDialog, kEditTextItemEditBox, 0, myString[0]);
        }
        
        DisposeHandle(myHandle);
    }
 
    MacShowWindow(GetDialogWindow(myDialog));
    
    //////////
    //
    // display and handle events in the dialog box until the user clicks OK or Cancel
    //
    //////////
    
    do {
        ModalDialog(gModalFilterUPP, &myItem);
    } while ((myItem != kEditTextItemOK) && (myItem != kEditTextItemCancel));
    
    //////////
    //
    // handle the selected button
    //
    //////////
    
    if (myItem != kEditTextItemOK)
        goto bail;
    
    // retrieve the edited text
    myHandle = NewHandleClear(4);
    if (myHandle != NULL) {
        GetDialogItem(myDialog, kEditTextItemEditBox, &myItemKind, &myItemHandle, &myItemRect);
        GetDialogItemText(myItemHandle, myString);
        QTInfo_PStringToTextHandle(myString, myHandle);
        myErr = AddUserDataText(myUserData, myHandle, theType, 1, GetScriptManagerVariable(smRegionCode));
        myIsChanged = (myErr == noErr);
        DisposeHandle(myHandle);
    }
 
bail:   
    // restore the previous resource file and graphics port
    MacSetPort(mySavedPort);
    UseResFile(mySavedResFile);
    
    if (myDialog != NULL)
        DisposeDialog(myDialog);
 
    return(myIsChanged);
}
 
 
//////////
//
// QTInfo_TextHandleToPString
// Copy the characters in the specified handle into the specified Pascal string.
//
//////////
 
void QTInfo_TextHandleToPString (Handle theHandle, Str255 theString)
{
    short       myCount;
    
    myCount = GetHandleSize(theHandle);
    if (myCount > 255)
        myCount = 255;
 
    theString[0] = myCount;
    BlockMoveData(*theHandle, &(theString[1]), myCount);
}
 
 
//////////
//
// QTInfo_PStringToTextHandle
// Copy the characters in the specified Pascal string into the specified handle.
//
//////////
 
void QTInfo_PStringToTextHandle (Str255 theString, Handle theHandle)
{
    SetHandleSize(theHandle, theString[0]);
    if (GetHandleSize(theHandle) != theString[0])
        return;
 
    BlockMoveData(&(theString[1]), *theHandle, theString[0]);
}