May 00 QTToolkit
Volume Number: 16 (2000)
Issue Number: 5
Column Tag: Programming
In and Out
by Tim Monroe
Using QuickTime's Movie Importers and Exporters
Introduction
In the previous QuickTime Toolkit article, we looked at graphics importers and exporters, which allow us to read still images from files, draw those images, and save the images in new image formats. In this article, we're going to take a look at movie importers and exporters, which allow us to convert various kinds of data to and from the QuickTime movie format. A movie importer reads data and converts it into a QuickTime movie, and a movie exporter writes QuickTime movie data in some other format. For example, we can use a movie importer to read an AVI file and convert it into the QuickTime movie format. At that point, we can display the movie in a window and attach a movie controller to it, exactly as if the original data had been stored in the QuickTime file format. Conversely, we can use a movie exporter to save QuickTime movie data as an AVI file. Because movie importers and exporters allow us to change the format of movie data, they are also called movie data exchange components.
Originally, movie importers and exporters were mainly intended to read and write data stored in files that are not QuickTime files. We can, however, use movie data exchange components for other purposes also. In particular, we can use movie exporters to change the compression format or compression settings of a QuickTime file. We can also use movie exporters to add hint tracks to a movie so that the movie can be streamed over a network. In this case, the existing movie data isn't actually being converted to some other format; rather, new tracks are being added to the movie to enable the movie data to be efficiently broken up into packets that can be sent out over a network.
In this article, we'll begin by considering how to export a movie under a new format. We'll see how to do this in two ways, first allowing the user to select any available export format and then restricting the export format to a particular format. Next we'll investigate how to import non-movie files as movies. Finally, we'll learn how to use movie progress functions, which the Movie Toolbox calls when an operation (such as importing or exporting a movie) promises to take a significant amount of time.
Exporting Movies
To export a movie is to convert it into a new format or to change the movie data in some other way. QuickTime provides a number of functions that we can use to export a movie. By far the easiest to use is the ConvertMovieToFile function, whose declaration (in Movies.h) looks essentially like this:
OSErr ConvertMovieToFile (
Movie theMovie,
Track onlyTrack,
FSSpec * outputFile,
OSType fileType,
OSType creator,
ScriptCode scriptTag,
short * resID,
long flags,
ComponentInstance userComp);
Most of these parameters have pretty obvious uses: theMovie, of course, is the movie we want to export, and outputFile points to a file system specification for the file we want the converted movie data to be put into. The fileType and creator parameters are the desired file type and file creator code of the destination file, and scriptTag is the script into which the movie should be converted.
A few of these parameters are less obvious. The onlyTrack parameter specifies which track in the source movie is to be converted; we'll always specify NULL for this parameter to have ConvertMovieToFile convert all the tracks in the movie. The resID parameter points to some storage that will receive the resource ID of the source movie; we won't need this information, so once again we'll always pass NULL for this parameter.
The userComp parameter specifies which movie export component should be used to perform the data conversion. If we want the movie to be exported into a specific format, we can open the appropriate movie export component and pass the component instance in this parameter. On the other hand, if we want to display a dialog box that allows the user to select the desired output format, then we can pass NULL in this parameter. In that case, we should also specify 0 in the fileType parameter (since of course we don't yet know what the file type of the output file should be).
Finally, the flags parameter specifies a 32-bit value whose bits encode information governing how the movie conversion should proceed. Our first example of using ConvertMovieToFile will use this line of code to configure the flags parameter:
myFlags = createMovieFileDeleteCurFile |
showUserSettingsDialog |
movieFileSpecValid |
movieToFileOnlyExport;
The createMovieFileDeleteCurFile flag tells ConvertMovieToFile to delete any existing file specified by the outputFile parameter. The showUserSettingsDialog flag indicates that ConvertMovieToFile should display a movie export dialog box, shown in Figure 1.
Figure 1. The movie export dialog box.
If the showUserSettingsDialog flag is set, then the movieFileSpecValid flag indicates that the name specified in the name field of the file system specification pointed to by the outputFile parameter should be the name initially displayed in the export dialog box. Also, if showUserSettingsDialog is set, then the movieToFileOnlyExport flag indicates that the user should be allowed to select only from among the output formats supported by any available movie export components. These available formats are listed in the pop-up menu shown in Figure 2. On the other hand, if showUserSettingsDialog is set but the movieToFileOnlyExport flag is clear, and if fileType is either MovieFileType or 0, then the pop-up menu also includes items that allow the user to save the movie as a movie file or as a self-contained movie file.
Figure 2. The pop-up menu listing the available export formats.
Figure 2 shows the export formats built into QuickTime 4.1. Of course, since movie exporters are components, additional exporters can be added by third parties. So the pop-up menu on your machine might contain a few more exporters than you see in Figure 2.
Converting to Any Available Export Format
Listing 1 shows a function QTDX_ExportMovieAsAnyTypeFile that configures the movie export flags and then calls ConvertMovieToFile to allow the user to export a movie into any available export format.
Listing 1: Exporting a movie as a user-selected type
QTDX_ExportMovieAsAnyTypeFile
OSErr QTDX_ExportMovieAsAnyTypeFile (Movie theMovie, FSSpec *theFSSpec)
{
FSSpec myFSSpec = *theFSSpec;
long myFlags = 0L;
OSErr myErr = noErr;
myFlags = createMovieFileDeleteCurFile |
showUserSettingsDialog |
movieFileSpecValid |
movieToFileOnlyExport;
// export the movie into a file
myErr = ConvertMovieToFile(
theMovie, // the movie to convert
NULL, // all tracks in the movie
&myFSSpec, // the output file
0L, // the output file type
FOUR_CHAR_CODE('TVOD'), // the output file creator
smSystemScript, // the script
NULL, // no resource ID to be returned
myFlags, // export flags
NULL); // no specific export component
return(myErr);
}
Because the showUserSettingsDialog flag is set, ConvertMovieToFile displays the movie export dialog box shown in Figure 1. When the user selects an output file and export format, ConvertMovieToFile creates that file (first deleting it if it already exists) and performs the desired conversion. Note that when ConvertMovieToFile completes, it returns information about the newly-created file in the myFSSpec parameter. (This behavior appears to be undocumented.) In our sample application, the theFFSpec parameter passed to QTDX_ExportMovieAsAnyTypeFile points to the file system specification of the open movie file. So we make a local copy of that specification in myFSSpec to avoid overwriting the movie's file information.
Converting to a Specific Export Format
As I mentioned earlier, we can specify a particular movie export component when calling ConvertMovieToFile if we want to export a movie into a specific format. Let's suppose that we want to allow the user to add a hint track to a movie, so that it can be efficiently streamed across a network. The first thing we need to do is find the movie hinter export component. We can use the Component Manager functions FindNextComponent and OpenComponent to find that component and open an instance of it, like this:
ComponentDescription myCompDesc;
myCompDesc.componentType = MovieExportType;
myCompDesc.componentSubType = MovieFileType;
myCompDesc.componentManufacturer = FOUR_CHAR_CODE('hint');
myCompDesc.componentFlags = 0;
myCompDesc.componentFlagsMask = 0;
myExporter = OpenComponent(FindNextComponent(NULL, &myCompDesc));
As you can see, we're asking for a movie export component that can create movie files with tracks of type 'hint'. We'll pass myExporter as the last parameter when we call ConvertMovieToFile.
Now, what flags should we pass to ConvertMovieToFile? We certainly want to pass createMovieFileDeleteCurFile and movieFileSpecValid. And we certainly don't want to pass movieToFileOnlyExport, because we already know what kind of output file we want to create. But what about showUserSettingsDialog? Do we want to display the movie export dialog box shown in Figure 1? For some purposes we might, but for the moment let's suppose that we don't want the user to change the output file name or location (which he or she could do if we displayed the movie export dialog box). So we won't include showUserSettingsDialog in the flags we pass to ConvertMovieToFile.
Now we've got a slight problem. While we don't want the user to change the output file name or location, we might want the user to change the settings used by the movie hinter export component. If the movie export dialog box were displayed, the user could click on the "Options..." button to display the settings dialog box shown in Figure 3.
Figure 3. The movie hinter settings dialog box.
Luckily, we can use the movie hinter export component directly and ask it to display its settings dialog box, by calling the MovieExportDoUserDialog function like this:
MovieExportDoUserDialog(myExporter, theMovie, NULL,
0, 0, &myCancelled);
So at this point, we've found the appropriate movie hinter export component and displayed its settings dialog box to allow the user to select the desired hinter settings. We can finish up by calling ConvertMovieToFile, specifying the movie hinter export component:
myErr = ConvertMovieToFile(
theMovie, // the movie to convert
NULL, // all tracks in the movie
&myHintedFile, // the output file
MovieFileType, // the output file type
FOUR_CHAR_CODE('TVOD'), // the output file creator
smSystemScript, // the script
NULL, // no resource ID to be returned
myFlags, // conversion flags
myExporter); // hinter export component
If this line of code completes successfully, the specified movie will have been exported into a hinted movie file.
Using Movie Export Settings
Imagine now that you've got a large number of movie files to which you want to add hint tracks. Imagine further that you want to use some specific non-default settings for the hinting. It would be tedious to have to open each movie file and configure its export settings in the dialog box displayed by MovieExportDoUserDialog. Instead, it would be better to configure those settings once, save them somewhere, and then instruct the movie hinter export component to use those saved settings.
The processing required to make this happen is actually rather simple. Let's suppose that theFSSpecPtr is a pointer to a file system specification for a file that contains a saved copy of the desired exporter settings. Then we can use the function QTDX_GetExporterSettingsFromFile defined in Listing 2 to read those settings and install them as the exporter's active settings.
Listing 2: Reading stored exporter settings
QTDX_GetExporterSettingsFromFile
OSErr QTDX_GetExporterSettingsFromFile
(MovieExportComponent theExporter, FSSpecPtr theFSSpecPtr)
{
Handle myHandle = NULL;
ComponentResult myErr = fnfErr;
myHandle = QTDX_ReadHandleFromFile(theFSSpecPtr);
if (myHandle == NULL)
goto bail;
myErr =
MovieExportSetSettingsFromAtomContainer(theExporter,
(QTAtomContainer)myHandle);
bail:
if (myHandle != NULL)
DisposeHandle(myHandle);
return((OSErr)myErr);
}
The function QTDX_ReadHandleFromFile reads the data in the specified file into a handle; there is nothing especially interesting about it so we won't discuss it further. The important function here is MovieExportSetSettingsFromAtomContainer, which takes that handle of data, interprets it as an atom container, and uses the settings in that atom container as the current settings of the specified movie exporter.
An atom container is a handle to a block of memory that is structured in a hierarchical arrangement of container atoms (which contain other atoms) and leaf atoms (which contain data). In a future article, we'll take a closer look at the structure of atom containers and see how to create and parse them. But for the moment, we can remain blissfully ignorant of those details, because we can use the function MovieExportGetSettingsAsAtomContainer to create the settings atom container. Listing 3 shows our function that saves the current settings of the specified movie exporter into a file.
Listing 3: Creating stored exporter settings
QTDX_SaveExporterSettingsInFile
OSErr QTDX_SaveExporterSettingsInFile
(MovieExportComponent theExporter, FSSpecPtr theFSSpecPtr)
{
QTAtomContainer myContainer = NULL;
ComponentResult myErr = noErr;
myErr = MovieExportGetSettingsAsAtomContainer (theExporter, &myContainer);
if (myErr != noErr)
goto bail;
myErr = QTDX_WriteHandleToFile((Handle) myContainer, theFSSpecPtr);
bail:
if (myContainer != NULL)
QTDisposeAtomContainer(myContainer);
return((OSErr)myErr);
}
So what we need to do is display the movie hinter export component's settings dialog box once, allow the user to configure the hinter settings as desired, and then save those settings into a file by calling QTDX_SaveExporterSettingsInFile. Then, we can use those saved settings for any subsequent export operations. Listing 4 shows the complete function QTDX_ExportMovieAsHintedMovie that exports a movie as a hinted movie. Notice that this function takes a Boolean parameter that indicates whether it should display the settings dialog box.
Listing 4: Exporting a movie as a hinted movie
QTDX_ExportMovieAsHintedMovie
OSErr QTDX_ExportMovieAsHintedMovie (Movie theMovie, Boolean thePromptUser)
{
ComponentDescription myCompDesc;
MovieExportComponent myExporter = NULL;
long myFlags;
FSSpec myHintedFile;
FSSpec myPrefsFile;
Boolean myIsSelected = false;
Boolean myIsReplacing = false;
StringPtr myPrompt;
StringPtr myFileName;
ComponentResult myErr = badComponentType;
myFlags =
createMovieFileDeleteCurFile | movieFileSpecValid;
myPrompt =
QTUtils_ConvertCToPascalString(kHintedMovieSavePrompt);
myFileName =
QTUtils_ConvertCToPascalString(kHintedMovieFileName);
// get an output file for the hinted movie
QTFrame_PutFile(myPrompt, myFileName, &myHintedFile,
&myIsSelected, &myIsReplacing);
if (!myIsSelected) {
myErr = userCanceledErr;
goto bail;
}
if (myIsReplacing) {
myErr = FSpDelete(&myHintedFile);
if (myErr != noErr)
goto bail;
}
// find and open a movie export component that can hint a movie file
myCompDesc.componentType = MovieExportType;
myCompDesc.componentSubType = MovieFileType;
myCompDesc.componentManufacturer = FOUR_CHAR_CODE('hint');
myCompDesc.componentFlags = 0;
myCompDesc.componentFlagsMask = 0;
myExporter = OpenComponent(FindNextComponent(NULL, &myCompDesc));
if (myExporter == NULL)
goto bail;
// get the preferences file for this application
QTDX_GetPrefsFileSpec(&myPrefsFile, (void *)&myHintedFile);
// read existing movie exporter settings from a file; if we aren't going to prompt
// the user for exporter settings, these stored settings will be used; otherwise,
// these stored settings will be used as initial values in the settings dialog box
QTDX_GetExporterSettingsFromFile(myExporter, &myPrefsFile);
if (thePromptUser && QTDX_ComponentHasUI(MovieExportType, myExporter)) {
Boolean myCancelled = false;
// display a dialog box to prompt the user for desired movie exporter settings
myErr = MovieExportDoUserDialog(myExporter,
theMovie, NULL, 0, 0, &myCancelled);
if (myCancelled)
goto bail;
// save the existing settings into our preferences file
QTDX_SaveExporterSettingsInFile(myExporter, &myPrefsFile);
}
// export the movie into a file
myErr = ConvertMovieToFile(
theMovie, // the movie to convert
NULL, // all tracks in the movie
&myHintedFile, // the output file
MovieFileType, // the output file type
FOUR_CHAR_CODE('TVOD'), // the output file creator
smSystemScript, // the script
NULL, // no resource ID to be returned
myFlags, // conversion flags
myExporter); // hinter export component
bail:
// close the movie export component
if (myExporter != NULL)
CloseComponent(myExporter);
free(myPrompt);
free(myFileName);
return((OSErr)myErr);
}
Importing Files
As we saw in the previous article, QuickTime occasionally imports files for us, without our having to explicitly find a movie importer or call any file-conversion function. When we call NewMovieFromFile and specify a file that isn't a QuickTime movie file, NewMovieFromFile looks for a movie importer that can handle the data in that file. If it finds one, it automatically uses that importer to convert the file data into the QuickTime movie format and returns the converted movie to us. So we can import files that are not QuickTime movie files simply by handing them to NewMovieFromFile and letting it works its magic.
But there are a few details we need to take care of before we pass a file to NewMovieFromFile. First, if we want to mimic the behavior of QuickTime Player's "Import..." menu item, then we need to make sure that the list of files displayed to the user in the file-selection dialog box does not include QuickTime movie files. We also need to make sure that that list does include files of type 'TEXT' and 'PICT' (which by default are not listed in the file list displayed when "Open" is selected but which are displayed when "Import..." is selected). Then, if we are not using the StandardGetFilePreview function to display that list of files, we need to determine whether a file selected by the user needs to be converted into another file before it can be imported. Let's take these two tasks in order.
Filtering Out Movies
The list of files displayed in response to selecting the "Import..." menu item should not include any files that are QuickTime movies. If we are using the Navigation Services function NavGetFile, we can accomplish this by passing it a file filter function that accepts all files that QuickTime can open, except files of type kQTFileTypeMovie. Our application framework maintains a list of all files that QuickTime can open, either directly or using a movie importer or a graphics importer. So all our import file filter function needs to do check to see whether a candidate file has a type that's in that list but that isn't kQTFileTypeMovie. The function QTDX_FilterFiles defined in Listing 5 does just that.
Listing 5: Filtering out movies
QTDX_FilterFiles
PASCAL_RTN Boolean QTDX_FilterFiles (AEDesc *theItem, void *theInfo, void *theCallBackUD, NavFilterModes theFilterMode)
{
#pragma unused(theCallBackUD, theFilterMode)
NavFileOrFolderInfo *myInfo =
(NavFileOrFolderInfo *)theInfo;
if (gValidFileTypes == NULL)
QTFrame_BuildFileTypeList();
if (theItem->descriptorType == typeFSS) {
if (!myInfo->isFolder) {
OSType myType =
myInfo->fileAndFolder.fileInfo.finderInfo.fdType;
long myCount;
long myIndex;
// see whether the file type is in the list of file types that our application can
// open, but do not allow movie files
myCount = GetPtrSize((Ptr)gValidFileTypes) /
(long)sizeof(OSType);
for (myIndex = 0; myIndex < myCount; myIndex++)
if ((myType == gValidFileTypes[myIndex]) &&
(myType != kQTFileTypeMovie))
return(true);
// if we got to here, it's a file we cannot open
return(false);
}
}
// if we got to here, it's a folder or non-HFS object
return(true);
}
If we are running on Windows, where we are using the Standard File Package, it's even simpler. We can just pass StandardGetFilePreview a list of file types that includes all types that QuickTime can open, minus kQTFileTypeMovie. When we constructed the list of file types gValidFileTypes, we put kQTFileTypeMovie in the first position. So we can just pass a pointer that begins at the second file type, like this:
myTypeListPtr = (QTFrameTypeListPtr)&gValidFileTypes[1];
myNumTypes = (short)(GetPtrSize((Ptr)gValidFileTypes) / sizeof(OSType)) - 1;
You might be wondering why we didn't also use a custom file filter function with StandardGetFilePreview. On the Macintosh, this would work fine; but on Windows, only the list of file types is used to determine which files to show in the file-opening dialog box.
Importing In Place
QuickTime can import some types of files without first having to make a copy of the file data. These kinds of files can be imported in place - meaning that the associated movie importer can construct a movie that directly references that data. Other kinds of files cannot be imported in place. For instance, when we select QuickTime Player's "Import..." menu item and choose a file of type 'PICT', we are presented with the dialog box shown in Figure 4, which asks us to specify a new file to hold the converted picture data. When we specify a new file, the selected 'PICT' file is converted into that file and then the converted file is opened in a movie window.
Figure 4. The file conversion dialog box.
If we are using StandardGetFilePreview to present a list of openable files to the user, we don't need to know whether a file can be imported in place. StandardGetFilePreview determines this by itself and presents the file conversion dialog box whenever the selected file cannot be imported in place. But if we are using the Navigation Services programming interfaces, we do need to figure this out. Listing 6 defines a function that determines whether a given file can be imported in place. As you can see, we first find a movie importer that can open files whose type is that of the specified file (on the Macintosh) or whose file extension is that of the specified file (on Windows). Then we call GetComponentInfo to get a set of flags that specify the capabilities of that importer. For present purposes, we need to see whether the bit canMovieImportInPlace is set.
Listing 6: Determining whether a file can be imported in place
QTDX_FileCanBeImportedInPlace
Boolean QTDX_FileCanBeImportedInPlace (FSSpec *theFSSpec)
{
ComponentDescription myCompDesc;
Component myComponent = NULL;
Boolean myCanImportInPlace = false;
OSType mySubType;
unsigned long myFlags = 0;
OSErr myErr = noErr;
#if TARGET_OS_MAC
FInfo myFileInfo;
// get the file type of the specified file
myErr = FSpGetFInfo(theFSSpec, &myFileInfo);
if (myErr != noErr)
goto bail;
mySubType = myFileInfo.fdType;
#endif
#if TARGET_OS_WIN32
// get the filename extension of the specified file
myErr = QTGetFileNameExtension(theFSSpec->name, 0L, &mySubType);
if (myErr != noErr)
goto bail;
myFlags = movieImportSubTypeIsFileExtension;
#endif
myCompDesc.componentType = MovieImportType;
myCompDesc.componentSubType = mySubType;
myCompDesc.componentManufacturer = 0;
myCompDesc.componentFlags = myFlags;
myCompDesc.componentFlagsMask = myFlags;
myComponent = FindNextComponent(NULL, &myCompDesc);
if (myComponent != NULL) {
GetComponentInfo(myComponent, &myCompDesc,
NULL, NULL, NULL);
if (myCompDesc.componentFlags & canMovieImportInPlace)
myCanImportInPlace = true;
}
bail:
return(myCanImportInPlace);
}
Importing Files
Now we have all the pieces we need to handle the "Import..." menu item. First we do the necessary work to limit the files displayed in the file-opening dialog box to any files that QuickTime can import but which are not QuickTime movie files. Then we check to see whether the file selected by the user can be imported in place. If it cannot, we need to display a file-saving dialog box to elicit a new file from the user; we also need to convert the selected file into a movie file, which we can do using the ConvertFileToMovieFile function. Finally, we pass the converted file (or the originally selected file, if it can be imported in place) to our function QTFrame_OpenMovieInWindow, which in turn calls NewMovieFromFile. Listing 7 puts this all together.
Listing 7: Importing a file
QTDX_ImportAnyNonMovie
OSErr QTDX_ImportAnyNonMovie (void)
{
QTFrameFileFilterUPP myFileFilterUPP = NULL;
QTFrameTypeListPtr myTypeListPtr = NULL;
short myNumTypes = 0;
Movie myMovie = NULL;
FSSpec myFileToConvert;
FSSpec myConvertedFile;
StringPtr myPrompt = QTUtils_ConvertCToPascalString(kImportSavePrompt);
OSErr myErr = noErr;
#if TARGET_OS_WIN32
myTypeListPtr = (QTFrameTypeListPtr)&gValidFileTypes[1];
myNumTypes = (short)(GetPtrSize((Ptr)gValidFileTypes) / sizeof(OSType)) - 1;
#endif
// let the user select an openable file from any files that aren't movie files
myFileFilterUPP =
QTFrame_GetFileFilterUPP((ProcPtr)QTDX_FilterFiles);
myErr = QTFrame_GetOneFileWithPreview(myNumTypes,
myTypeListPtr, &myFileToConvert, (void *)myFileFilterUPP);
if (myErr != noErr)
goto bail;
myConvertedFile = myFileToConvert;
// determine whether the selected file needs to be converted into another file before
// QuickTime can open it; if so, do the conversion
#if TARGET_OS_MAC
if (!QTDX_FileCanBeImportedInPlace(&myFileToConvert)) {
Boolean myIsSelected = false;
Boolean myIsReplacing = false;
// display the put-file dialog to save the converted file
QTFrame_PutFile(myPrompt, myFileToConvert.name,
&myConvertedFile, &myIsSelected, &myIsReplacing);
if (!myIsSelected)
goto bail;
// delete any existing file of that name
if (myIsReplacing) {
myErr = DeleteMovieFile(&myConvertedFile);
if (myErr != noErr)
goto bail;
}
// import the file into a movie
myErr = ConvertFileToMovieFile(
&myFileToConvert, // the file to convert
&myConvertedFile, // the file to convert it into
FOUR_CHAR_CODE('TVOD'), // the output file creator
smSystemScript, // the script
NULL,
0L,
NULL,
gMovieProgressProcUPP,
0L);
}
#endif
// now open the (possibly) converted file in a window
if (myErr == noErr)
QTFrame_OpenMovieInWindow(NULL, &myConvertedFile);
bail:
if (myFileFilterUPP != NULL)
DisposeRoutineDescriptor(myFileFilterUPP);
free(myPrompt);
return(myErr);
}
It's important to understand that our Macintosh implementation of the "Import..." menu item lacks a key feature provided automatically by StandardGetFilePreview. To wit: the file-saving dialog box displayed by the call to QTFrame_PutFile in Listing 7 does not contain an Options button that allows the user to modify any settings supported by the movie importer capable of opening the selected file. (Look again at Figure 4 to see the Options button provided by StandardGetFilePreview.) To add a custom button to the standard Navigation Services file-saving dialog box would take us too far afield right now. Let's put this item on our list of features to add at some time in the future.
Default Progress Functions
Unless a movie is very short, the calculations and disk accesses involved in (for instance) exporting it to a new format can take several minutes, if not considerably longer, even on today's relatively fast machines. From its very inception, QuickTime has provided a way to inform the user that a lengthy operation is in progress and to provide some indication of how much of the operation has completed. These tasks are accomplished using a movie progress function, which typically displays a movie progress dialog box. In this section and the following two sections, we'll see how to display and manage a movie progress dialog box.
By far the easiest way to display a progress dialog box is to use QuickTime's default progress function, which displays and manages a dialog box like the one shown in Figure 5 (on Macintosh computers) or Figure 6 (on Windows computers).
Figure 5. The default progress dialog box (Macintosh).
Figure 6. The default progress dialog box (Windows).
As you can see, these dialog boxes contain a progress bar control that shows the relative amount of completion of the operation, a text string indicating the operation that's in progress, and a button to cancel the operation. There are a dozen or so operations that can trigger the display of a movie progress dialog box, including exporting and importing movies, cutting or pasting movie segments, loading a movie into memory, and saving a movie as a self-contained movie.
You can instruct QuickTime to display this default progress dialog box during lengthy operations on a particular movie by calling the SetMovieProgressProc function and passing -1 as the progress function universal procedure pointer. Our basic application framework includes this line of code to set the default function for each movie we open:
SetMovieProgressProc(myMovie, (MovieProgressUPP)-1, 0);
You need to take the word "default" here with a grain of salt, however. You'll get this default progress dialog box only if you call SetMovieProgressProc with -1 as the second parameter. If you open a movie, do not call SetMovieProgressProc, and then initiate a lengthy operation, QuickTime does not display any progress dialog box at all. This might lead the user to think that your application has frozen, so it's almost always a good idea to use a progress function that provides some visual feedback.
Custom Progress Functions
The default progress function in fact does a fair amount of work for us. It displays the default progress dialog box, continually updates the progress bar control in the dialog box, and responds to user clicks on the Stop button. In addition, it looks for user presses on the Escape key and Command-period key combination and interprets those presses as equivalent to a click on the Stop button. Finally, it displays a message indicating which operation is in progress. Not bad for a single line of code.
Still, it's tempting to want to jazz things up a bit, and QuickTime provides a way for us to install a custom progress function that is called periodically during a lengthy operation. We're free to do just about anything in our custom progress function. We can display a different dialog box or message, replace the progress bar control by some other progress indicator, play sounds, and the like. For the moment, we'll restrain our urges to bloat our progress dialog box with features. Instead, we'll retain the basic appearance and operation of the default progress dialog box, while adding two features: we'll add a text message that indicates the approximate amount of time remaining in the operation, and we'll add a picture that is gradually erased (from bottom to top) as the operation progresses. Figure 7 shows our custom progress dialog box in action.
Figure 7. The custom progress dialog box of QTDataEx.
As you've already seen, we install a custom progress function by passing its universal procedure pointer to the SetMovieProgressProc. So, if our custom progress function is QTDX_MovieProgressProc, we can install it by executing these lines of code:
gMovieProgressProcUPP = NewMovieProgressProc(QTDX_MovieProgressProc);
if ((**theWindowObject).fMovie != NULL)
SetMovieProgressProc((**theWindowObject).fMovie, gMovieProgressProcUPP, (long)theWindowObject);
The third parameter to SetMovieProgressProc is an arbitrary 32-bit reference constant that's passed to the movie progress function each time it's called. Here we are passing the window object associated with the movie. We don't actually use that value in our custom progress function, but it's not hard to imagine things we could do with that information.
Our custom movie progress function has this declaration:
PASCAL_RTN OSErr QTDX_MovieProgressProc
(Movie theMovie, short theMessage, short theOperation,
Fixed thePercentDone, long theRefcon);
As you can see, this function has five parameters, two of which are the movie being operated upon and the reference constant that we specified when we called SetMovieProgressProc. The theMessage parameter is a value that indicates why our progress function is being called. The Movie Toolbox defines these constants for this parameter:
enum {
movieProgressOpen = 0,
movieProgressUpdatePercent = 1,
movieProgressClose = 2
};
So our custom progress function gets called when the lengthy operation is started, when it has stopped, and at various percentages of completion. The progress function is called often enough for us to keep our dialog box updated in a fairly smooth manner. If theMessage is movieProgressUpdatePercent, then the thePercentDone parameter indicates the percentage of completion. This percentage is always specified as a Fixed value between 0.0 and 1.0.
The theOperation parameter is a constant that specifies which operation has triggered the custom progress function. The Movie Toolbox defines these twelve constants:
enum {
progressOpFlatten = 1,
progressOpInsertTrackSegment = 2,
progressOpInsertMovieSegment = 3,
progressOpPaste = 4,
progressOpAddMovieSelection = 5,
progressOpCopy = 6,
progressOpCut = 7,
progressOpLoadMovieIntoRam = 8,
progressOpLoadTrackIntoRam = 9,
progressOpLoadMediaIntoRam = 10,
progressOpImportMovie = 11,
progressOpExportMovie = 12
};
We'll use this information to determine which string to display at the top of our custom progress dialog box, like this:
// set the dialog box text that describes the current operation
GetDialogItem(myDialog, kProgressTextItemID, &myItemKind,
&myItemHandle, &myItemRect);
if ((theOperation > 0) &&
(theOperation <= progressOpExportMovie)) {
GetIndString(myString, kOperationsStringsResID, theOperation);
SetDialogItemText(myItemHandle, myString);
}
Our application's resource file contains a 'STR#' resource of ID kOperationsStringsResID that contains strings describing each of the twelve possible operations our movie progress function can be called to handle. We'll use the same strings that are used in the default progress dialog box. (In fact, I simply "borrowed" the 'STR#' resource of ID -19183 from the QuickTime extension.)
Our custom progress function is in fact fairly simple in structure, if a tad long. It simply inspects the theMessage parameter to see what phase of the operation is occurring and then switches to the appropriate code to handle that phase. When our function receives the movieProgressOpen message, it opens our custom progress dialog box and performs any initial configuration of the dialog box. When it receives the movieProgressUpdatePercent message, it updates the progress bar control and erases the appropriate section of the picture in the dialog box. During this phase, our progress function also looks to see if the user wants to cancel the operation. Finally, when it receives the movieProgressClose message, it disposes of the dialog box and cleans up. Let's consider each of these phases in a bit more detail.
Opening the Dialog Box
When our custom progress function receives the movieProgressOpen message, we'll open a dialog box in the standard way, by reading it from our resource file:
myDialog = GetNewDialog(kProgressDialogResID, NULL, (WindowPtr)-1);
If we are successful in opening the dialog box (that is, if myDialog is non-NULL), then we need to display the appropriate operation message (which we just saw how to do) and configure the progress bar control. To configure the control, we want to set its minimum value to 0 and its maximum value to some arbitrary value, defined by the constant kProgressBarMaxValue:
GetDialogItem(myDialog, kProgressBarItemID, &myItemKind, &myItemHandle, &myItemRect);
myBar = (ControlHandle)myItemHandle;
SetControlMinimum(myBar, 0);
SetControlMaximum(myBar, (SInt16)kProgressBarMaxValue);
In fact we could probably dispense with calling SetControlMinimum and SetControlMaximum here, because the minimum and maximum values are set also in the resource file; but it doesn't hurt to make sure that those values are what we expect them to be. When it comes time to update the progress bar control, it will be important that the control maximum be set correctly. Note that we are saving the control handle of the progress bar control in the local static variable myBar.
Next, we need to set a drawing procedure for the user item that contains the progress picture. We can do so like this:
GetDialogItem(myDialog, kProgressPictureItemID, &myItemKind, &myItemHandle, &myItemRect);
SetDialogItem(myDialog, kProgressPictureItemID, myItemKind, (Handle)gProgressUserItemProcUPP, &myItemRect);
The global variable gProgressUserItemProcUPP is a universal procedure pointer to our function QTDX_ProgressBoxUserItemProcedure, which reads the picture from a 'PICT' resource in our application's resource file and draws it in the rectangle enclosing the user item, using the QuickDraw function DrawPicture.
Now we are ready to display our custom progress dialog box to the user. We'll call MacShowWindow and DrawDialog to do so. We shall also call the user-item drawing procedure directly, since that results in a noticeably quicker update of the user item when the box is first drawn.
MacShowWindow(myDialog);
QTDX_ProgressBoxUserItemProcedure(myDialog, kProgressPictureItemID);
DrawDialog(myDialog);
Finally, we'll call the TickCount function to get the current time, so that we can later display an estimate of the remaining time:
myTicks = TickCount();
Handling Progress Messages
When our custom progress function receives a movieProgressUpdatePercent message, the thePercentDone parameter indicates the percentage of completion. As mentioned earlier, the value passed in the thePercentDone parameter is always between 0 and fixed1 (that is, between 0x00000000 and 0x00010000). Just to be safe, however, we'll first verify that the value passed to our custom progress function lies within the expected range:
if ((thePercentDone < 0) || (thePercentDone > fixed1))
break;
The next thing we want to do is update the progress bar control. Remember that the acceptable values for that control lie between 0 and kProgressBarMaxValue, so we need to scale thePercentDone to lie within that range. We can do this with the following lines of code.
if (myBar != NULL) {
SetControlValue(myBar, (SInt16)Fix2Long(FixMul(thePercentDone, Long2Fix(kProgressBarMaxValue))));
}
We also want to erase the appropriate bottom portion of the picture in the custom dialog box. Once again, this mainly involves scaling the height of the picture rectangle by the percentage we are passed, as follows:
GetDialogItem(myDialog, kProgressPictureItemID, &myItemKind, &myItemHandle, &myItemRect);
MacSetRect( &myEraseRect,
myItemRect.left,
myItemRect.bottom - (SInt16)Fix2Long(FixMul(thePercentDone, Long2Fix(myItemRect.bottom - myItemRect.top))),
myItemRect.right,
myItemRect.bottom);
EraseRect(&myEraseRect);
The last visible thing we want to do is print out an estimated time remaining in the lengthy operation. In QTDX_MovieProgressProc, we retrieve the rectangle surrounding the area where we want to draw the remaining time estimate and then call the function QTDX_EstimateRemainingTime, like this:
GetDialogItem(myDialog, kProgressTimeItemID, &myItemKind,
&myItemHandle, &myItemRect);
QTDX_EstimateRemainingTime(&myItemRect, thePercentDone, TickCount() - myTicks);
QTDX_EstimateRemainingTime is defined in Listing 8.
Listing 8: Displaying the estimated time remaining
QTDX_EstimateRemainingTime
void QTDX_EstimateRemainingTime (Rect *theRect, Fixed thePercentDone, UInt32 theTicksElapsed)
{
char myString[32];
Fixed myEstTicks;
UInt32 myRemSeconds;
Rect myEraseRect;
StringPtr myPString = NULL;
myEstTicks = FixMul(FixDiv(fixed1, thePercentDone),
Long2Fix(theTicksElapsed));
myRemSeconds = Fix2Long(FixDiv(myEstTicks -
Long2Fix(theTicksElapsed), Long2Fix(60)));
TextSize(kTimeRemainingLabelSize);
TextFont(1);
myPString =
QTUtils_ConvertCToPascalString(kTimeRemainingLabel);
MoveTo(theRect->left, theRect->bottom);
DrawString(myPString);
MacSetRect(&myEraseRect,
theRect->left + StringWidth(myPString),
theRect->top,
theRect->right,
theRect->bottom);
free(myPString);
EraseRect(&myEraseRect);
MoveTo(myEraseRect.left, myEraseRect.bottom);
// the early percentages give inaccurate estimates, so don't start // displaying the
// time until we've reached a minimum threshold
if (thePercentDone < kMinimumUsefulPercent)
return;
if (myRemSeconds == 1)
sprintf(myString, "%u second", myRemSeconds);
else
sprintf(myString, "%u seconds", myRemSeconds);
myPString = QTUtils_ConvertCToPascalString(myString);
DrawString(myPString);
free(myPString);
}
Handling User Actions
One other thing we need to do in response to the movieProgressUpdatePercent message is check to see whether the user has performed any actions that affect any items in our custom progress dialog box. In our case, that means that we have to check to see whether the user has clicked the Stop button or pressed an equivalent key or key combination. This turns out to be a bit tricky. Normally, when we are displaying a modal dialog box, we can sit in a loop calling the ModalDialog function, waiting for the user to hit the Stop button (or perform some other operation that our modal dialog event filter function will map into a click on the Stop button). But we cannot adopt that strategy here, because our custom progress function is being called periodically by the Movie Toolbox, which is busy performing some lengthy operation. In other words, we're not in charge when we're inside of our progress function; rather, the Movie Toolbox is allotting us some time periodically to update our progress dialog box.
Since we cannot sit around waiting for the user to perform some relevant action, the next best thing we can do is go looking for events that interest us whenever we receive a movieProgressUpdatePercent message. For instance, we can look for clicks on the Stop button by searching the event queue for mouse-down events, like this:
GetDialogItem(myDialog, kProgressStopButtonItemID, &myItemKind, &myItemHandle, &myItemRect);
if (WaitNextEvent(mDownMask, &myEvent, 0, NULL)) {
GlobalToLocal(&myEvent.where);
if (TrackControl((ControlHandle)myItemHandle, myEvent.where, NULL))
myErr = userCanceledErr;
}
And we can look for key presses by searching the event queue for keyDown events, like this:
if (WaitNextEvent(keyDownMask, &myEvent, 0, NULL)) {
myKey = myEvent.message & charCodeMask;
if (myEvent.modifiers & cmdKey)
if (IsCmdChar(&myEvent, kPeriod))
myKey = kEscapeKey;
if (myKey == kEscapeKey) {
unsigned long myTicks;
// simulate a press on the Stop button
HiliteControl((ControlHandle)myItemHandle, kControlButtonPart);
Delay(kMyButtonDelay, &myTicks);
HiliteControl((ControlHandle)myItemHandle, false);
myErr = userCanceledErr;
}
}
Notice that when we find a click on the Stop button or the appropriate key press, we set our function return value to userCanceledErr. Whenever our custom progress function returns any non-zero value, the Movie Toolbox cancels the operation in progress.
Now, this all works fairly well for handling user actions. Our progress function is called often enough that it can pick up these user actions and respond to them just about as quickly as if it were continually calling ModalDialog inside a loop. There is only one remaining complication: if we run our application with this code for our custom progress function on Windows, the user's clicks on the Stop button will not be found when we call WaitNextEvent. (This is due to some fairly low-level details of the QuickTime Media Layer which need not concern us here.) The Escape key presses will be found just fine, and all the item-updating code works just fine. It's just that clicks on the Stop button are not to be found in the event queue.
The solution to this problem is to install a callback procedure that is executed whenever an event occurs for one of the active control items in the custom dialog box. Our callback function can be very simple: all it needs to do is look for clicks on the Stop button and then set a flag to pass that information back to our custom progress function. Listing 9 shows our dialog callback function.
Listing 9: Looking for clicks on the Stop button on Windows
QTDX_ModelessCallback
#if TARGET_OS_WIN32
static void QTDX_ModelessCallback (EventRecord *theEvent,
DialogPtr theDialog, short theItemHit)
{
#pragma unused(theEvent, theDialog)
if (theItemHit == kProgressStopButtonItemID)
gUserCancelled = true;
}
#endif
Now we need to do two other things. In the code handling the movieProgressOpen message, we need to install this callback procedure and initialize the global flag, like this:
#if TARGET_OS_WIN32
SetModelessDialogCallbackProc(myDialog, (QTModelessCallbackUPP)QTDX_ModelessCallback);
gUserCancelled = false;
#endif
And then in our code that handles the movieProgressUpdatePercent message, we need to see whether the global flag has been set by the callback procedure; if so, we set the function return value to some non-zero value and then jump to the end of the routine.
#if TARGET_OS_WIN32
if (gUserCancelled) {
myErr = userCanceledErr;
goto bail;
}
#endif
If you'd prefer to avoid using global variables here, you could instead pass information to and from the callback procedure by setting the window reference constant of the custom progress dialog box, using the SetWRefCon function.
Closing the Dialog Box
When the Movie Toolbox is finished with the lengthy operation that triggered our progress function or when it receives a non-zero return value from our progress function, it sends our progress function a movieProgressClose message. This is our signal to remove our custom progress dialog box from the screen and do any other necessary clean-up. Our custom progress function executes these few lines to dispose of our dialog box and reset the static variables:
case movieProgressClose:
if (myDialog != NULL)
DisposeDialog(myDialog);
myDialog = NULL;
myBar = NULL;
break;
So now we've handled all three messages. Our complete progress function is shown in Listing 10.
Listing 10: Displaying and managing a custom progress dialog box
QTDX_MovieProgressProc
PASCAL_RTN OSErr QTDX_MovieProgressProc (Movie theMovie, short theMessage, short theOperation, Fixed thePercentDone, long theRefcon)
{
#pragma unused(theMovie, theRefcon)
CGrafPtr mySavedPort = NULL;
GDHandle mySavedDevice = NULL;
static DialogPtr myDialog = NULL;
static ControlHandle myBar = NULL;
static UInt32 myTicks = 0;
short myItemKind;
Handle myItemHandle = NULL;
Rect myItemRect;
Rect myEraseRect;
Str255 myString;
EventRecord myEvent;
char myKey;
OSErr myErr = noErr;
GetGWorld(&mySavedPort, &mySavedDevice);
if (myDialog != NULL)
SetGWorld((CGrafPtr)myDialog, GetMainDevice());
switch (theMessage) {
case movieProgressOpen:
// display the progress dialog box
myDialog = GetNewDialog(kProgressDialogResID, NULL, (WindowPtr)-1);
if (myDialog != NULL) {
// set the dialog box as the current graphics port
SetGWorld((CGrafPtr)myDialog, GetMainDevice());;
SetDialogCancelItem(myDialog,
kProgressStopButtonItemID);
// configure the progress bar control
GetDialogItem(myDialog, kProgressBarItemID,
&myItemKind, &myItemHandle, &myItemRect);
myBar = (ControlHandle)myItemHandle;
SetControlMinimum(myBar, 0);
SetControlMaximum(myBar,
(SInt16)kProgressBarMaxValue);
// set the dialog box text that describes the current operation
GetDialogItem(myDialog, kProgressTextItemID,
&myItemKind, &myItemHandle, &myItemRect);
if ((theOperation > 0) &&
(theOperation <= progressOpExportMovie)) {
GetIndString(myString, kOperationsStringsResID, theOperation);
SetDialogItemText(myItemHandle, myString);;
}
// set a user-item drawing procedure for the picture rectangle
GetDialogItem(myDialog, kProgressPictureItemID,
&myItemKind, &myItemHandle, &myItemRect);
SetDialogItem(myDialog,
kProgressPictureItemID,
myItemKind,
(Handle)gProgressUserItemProcUPP,
&myItemRect);
// show the dialog box and draw the picture in the user item rectangle
MacShowWindow(myDialog);
QTDX_ProgressBoxUserItemProcedure(myDialog,
kProgressPictureItemID);
DrawDialog(myDialog);
myTicks = TickCount();
#if TARGET_OS_WIN32
// set a dialog callback procedure, to notify our progress proc
// that the user has cancelled
SetModelessDialogCallbackProc(myDialog,
(QTModelessCallbackUPP)QTDX_ModelessCallback);
// initialize the variable that keeps track of whether the user has cancelled
gUserCancelled = false;
#endif
}
break;
case movieProgressUpdatePercent:
#if TARGET_OS_WIN32
if (gUserCancelled) {
myErr = userCanceledErr; // stop the operation
goto bail;
}
#endif
// check to see whether the user wants to cancel the operation
// get the rectangle surrounding the Stop button
GetDialogItem(myDialog, kProgressStopButtonItemID,
&myItemKind, &myItemHandle, &myItemRect);
// check for user clicks in the Stop button
if (WaitNextEvent(mDownMask, &myEvent, 0, NULL)) {
GlobalToLocal(&myEvent.where);
if (TrackControl((ControlHandle)myItemHandle,
myEvent.where, NULL))
myErr = userCanceledErr; // stop the operation }
// check for user presses on the Escape key or on equivalent key // combinations
if (WaitNextEvent(keyDownMask, &myEvent, 0, NULL)) {
myKey = myEvent.message & charCodeMask;
if (myEvent.modifiers & cmdKey)
if (IsCmdChar(&myEvent, kPeriod))
myKey = kEscapeKey;
if (myKey == kEscapeKey) {
unsigned long myTicks;
// simulate a press on the Stop button
HiliteControl((ControlHandle)myItemHandle,
kControlButtonPart);
Delay(kMyButtonDelay, &myTicks);
HiliteControl((ControlHandle)
myItemHandle, false);
myErr = userCanceledErr; // stop the operation }
}
// update our progress dialog box
if (myBar != NULL) {
// thePercentDone is in the range 0 to fixed1 (0x00000000 to 0x00010000);
// we need to scale it to lie within the range 0 to kProgressBarMaxValue
SetControlValue(myBar,
(SInt16)Fix2Long(FixMul(thePercentDone,
Long2Fix(kProgressBarMaxValue))));
}
// erase the appropriate bottom portion of the picture
GetDialogItem(myDialog, kProgressPictureItemID,
&myItemKind, &myItemHandle, &myItemRect);
MacSetRect( &myEraseRect,
myItemRect.left,
myItemRect.bottom -
(SInt16)Fix2Long(FixMul(thePercentDone,
Long2Fix(myItemRect.bottom - myItemRect.top))),
myItemRect.right,
myItemRect.bottom);
EraseRect(&myEraseRect);
// update the estimated time remaining
GetDialogItem(myDialog, kProgressTimeItemID,
&myItemKind, &myItemHandle, &myItemRect);
QTDX_EstimateRemainingTime(&myItemRect, thePercentDone,
TickCount() - myTicks);
break;
case movieProgressClose:
// remove our progress dialog box
if (myDialog != NULL)
DisposeDialog(myDialog);
myDialog = NULL;
myBar = NULL;
myTicks = 0;
break;
}
bail:
SetGWorld(mySavedPort, mySavedDevice);
return(myErr);
}
Progress Functions For Image Operations
So far, we've focused on implementing a custom progress function for operations involving movies (importing, exporting, cutting, pasting, and so forth). But operations on images can also take a significant amount of time. For instance, compressing or decompressing an image can sometimes take quite a while, especially if the compression or decompression algorithm is processor-intensive and the image is fairly large. So we'd like to be able to provide the same sort of progress notification to the user that we can now provide with lengthy operations on movies. We can do this by defining an image progress function.
In general, everything we've learned so far about movie progress functions will be of use in writing an image progress function. The declaration of an image progress function is slightly different than that of a movie progress function. Here's how we might declare an image progress function:
PASCAL_RTN OSErr QTDX_ImageProgressProc
(short theMessage,
Fixed thePercentDone, long theRefCon);
As you can see, there is no parameter specifying the movie (since of course there is no movie being operated upon here). Also, there is no indication of which operation is triggering our image progress function.
The main difference between an image progress function and a movie progress function is in the way we install the progress function. As we've seen, we can call SetMovieProgressProc to install a movie progress function for all lengthy operations involving the specified movie. When we call SetMovieProgressProc, we need to pass it a universal procedure pointer to the movie progress function. When installing an image progress function, however, we instead need to pass the address of a structure of type ICMProgressProcRecord, which is defined (in ImageCompression.h) like this:
struct ICMProgressProcRecord {
ICMProgressUPP progressProc;
long progressRefCon;
};
Using the ICMProgressProcRecord record, we specify both the image progress function and the reference constant. For example, we could call GraphicsImportSetProgressProc to install an image progress function for any image handled by our application like this:
if ((**theWindowObject).fGraphicsImporter != NULL) {
ICMProgressProcRecord myProcRec;
myProcRec.progressProc = gImageProgressProcUPP;
myProcRec.progressRefCon = (long)theWindowObject;
GraphicsImportSetProgressProc((**theWindowObject).fGraphicsImporter, &myProcRec);
}
At this point, whenever the graphics importer associated with the image undertakes some lengthy operation, it will call our image progress function QTDX_ImageProgressProc. (To get the default image progress function, pass (ICMProgressProcRcordPtr)-1 as the second parameter, in place of &myProcRec.) We could of course display a custom progress dialog box devoted especially to images and manage that dialog box with a new image progress function; for simplicity, however, we'll just call our existing movie progress function, as shown in Listing 11.
Listing 11: Displaying and managing a custom progress function
QTDX_ImageProgressProc
PASCAL_RTN OSErr QTDX_ImageProgressProc
(short theMessage,
Fixed thePercentDone, long theRefCon)
{
return(QTDX_MovieProgressProc(NULL, theMessage, 0,
thePercentDone, theRefCon));
}
Notice that we pass NULL for the movie parameter (which is okay since we never use that parameter in the custom movie progress function). We also pass 0 as the operation parameter. As I mentioned above, our custom image progress function does not get notified about which operation is taking a long time; accordingly, we just pass 0 to our movie progress function as the operation parameter. Since 0 is not a valid index in a 'STR#' resource, we won't be able to display a custom message telling the user which operation is in progress. For images, therefore, the custom progress dialog box will contain whatever message is specified in the dialog item resource (which in our case is "Making Progress").
This Month's Code
The Code folder accompanying this article contains the project files, source code, and resource data of a sample application called QTDataEx, for both Macintosh and Windows. QTDataEx is just like our basic QTShell application, except that the Test menu has been renamed "Movie" and includes three items that illustrate basic uses of movie data exchange components. Figure 8 shows the Movie menu of QTDataEx when a QuickTime movie is in the frontmost window.
Figure 8. The Movie menu in QTDataEx.
Selecting one of these items results in our application calling one of the functions we've defined earlier (that is, either QTDX_ImportAnyNonMovie, QTDX_ExportMovieAsAnyTypeFile, or QTDX_ExportMovieAsHintedMovie). Further, the function QTApp_SetupWindowObject installs our custom movie and image progress functions, so that any lengthy operations involving movies or images opened by our application will result in the display of our custom progress dialog box. For import operations, where there is no movie initially available, we need to specify the custom progress function as a parameter to the ConvertFileToMovieFile function (see Listing 7).
Conclusion
In this article we've taken a preliminary look at using movie importers and exporters. We've used the high-level functions ConvertFileToMovieFile and ConvertMovieToFile to perform some basic importing and exporting. We've also worked directly with movie exporters when displaying an exporter's settings dialog box (with MovieExportDoUserDialog) and managing the settings in that dialog box (with MovieExportGetSettingsAsAtomContainer and MovieExportSetSettingsFromAtomContainer). And we've seen how to display and manage the default and custom progress dialog boxes for both movies and images.
QuickTime provides a wide array of lower-level importing and exporting capabilities. If we wanted to export only part of a movie, for instance, we could use the MovieExportToFile function. Or, if we wanted to export a movie into memory (and not into a file), we could use the MovieExportToHandle function. As usual, we've barely just scratched the surface with movie importers and exporters.
Credits
Thanks are due to Brian Friedkin for patiently explaining to me the secrets for managing a custom movie progress dialog box under Windows. Also, I'm grateful to Edward Agabeg, Chris Flick, Scott Kuechle, and Kevin Marks for reviewing this article and offering a number of helpful comments.
Tim Monroe has worked at Apple for over 10 years, first as a technical writer in the Inside Macintosh group and later as a software engineer in the QuickTime group. Currently he is developing sample code and utilities for the QuickTime software development kit. You can reach him at monroe@apple.com.