TweetFollow Us on Twitter

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.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.