CocoaDev

Edit AllPages

Quartz Composer app creates files that can be opened by QuickTime Player. QTMovie can also open these files to play in a QTMovieView. One thing you should understand before jumping into creating “qtz” files is the concept of a “Rendering Destination”. Quartz Composer files define image compositions that are a function of time, input parameters and environment variables. These compositions are rendered dynamically into a “destination”. The destination’s exact dimensions are not a known constant. Instead it is up to you to create a Quartz Composer file that can adapt to variable dimensions (e.g. preserving an aspect ratio or centering an image). When you open a “qtz” file with QTMovie, you can define the “Rendering Destination” and the duration of the movie. This is done by changing the Quartz Composer environment. There are three user defaults that define the destination’s size and movie duration. If you would like to define the state that a Quartz composition uses when rendering you must define this environment before you initialize a QTMovie with a “qtz” file. After the movie has been initialized you can change this environment for another movie.

static void SetQuartzComposerState(int width, int height, float duration) { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setObject:[NSNumber numberWithInt:width] forKey:@”QuartzComposerDefaultMovieWidth”]; [defaults setObject:[NSNumber numberWithInt:height] forKey:@”QuartzComposerDefaultMovieHeight”]; [defaults setObject:[NSNumber numberWithFloat:time] forKey:@”QuartzComposerDefaultMovieDuration”]; }

The default destination size is 640x480 and the default duration is 30 seconds (the minimum duration is 3 seconds).

Below is sample code and the basic steps to open and export a “qtz” file using QTKit.

*Open XCode *Create a new “Cocoa Document-based Application” project (File->New Project->Cocoa Document-based Application) *Edit the “MyDocument.m” file so it looks like the code below. *Add the QTKit framework to the doc-app target. *Build/Run.

There are four movies involved in this example:

*Movie A - quicktime movie with 30 fps video (this can be any movie) *Movie B - QTMovie initialized with a “qtz” file that uses “Movie A” as source material *Movie C - CustomMovie object used to create and export a new movie from frames rendered by “Movie B” *Movie D - QTMovie initialized with a reference to “Movie A” to get unique frame times since Quartz Composer movies (e.g. Movie B) do not have discrete frame times.

The macro definition “OpenNewFilesWithSharedWorkspace” can be used to view the two resource files generated by this code. Just set “OpenNewFilesWithSharedWorkspace” to 1 if you would like “Quartz Composer” and “QuickTime Player” to automatically open these files for you. The “qtz” file and source movie are located in the newly created app’s resource directory (hopefully you already know how to view an app’s package contents with the Finder).

A “qtz” file is just a property list. You can edit it in “Property List Editor” or change input parameters directly by editing an NSMutableDictionary initialized with the contents of a “qtz” file and then writing this file back to disk.

This sample code generates a generic “qtz” file for you, but once you become familiar with QuartzComposer and “qtz” files you can create movies with more advanced filters and effects. This code also shows you how to render specific frames by setting the Quartz movie’s current time and creating NSImage objects using QTMovie’s instance method currentFrameImage. Once you have access to individual frame images, the possibilities are endless.

This is only a starting point and there are many performance optimizations that can be pursued. For example, you could set the context, that the Quartz movie draws into, to a pixel buffer so you can pull frames faster. Or, you could figure out the lower level QuickTime calls needed to add raw image sample data directly to the export movie to avoid the performance hit caused by using addImage:forDuration:withAttributes:.

–zootbobbalu

#import “MyDocument.h” #import <QTKit/QTKit.h>

#ifndef PostError #define PostError(n, …) {error = [NSString stringWithFormat:n, ## VA_ARGS]; goto ERROR;} #endif

#define OpenNewFilesWithSharedWorkspace 0

@interface CustomMovie : QTMovie { DataHandler outputHandler; Handle dataHandle; NSString *tempPath; QTVisualContextRef context; unsigned cntxWidth, cntxHeight; }

@interface MyDocument (Private)

@end

////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// //// //// MyDocument //// ////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////

@implementation MyDocument

}

}

#if OpenNewFilesWithSharedWorkspace == 1 NSWorkspace *sws = [NSWorkspace sharedWorkspace]; [sws openFile:moviePath withApplication:@”QuickTime Player”]; #endif

		[manager removeFileAtPath:qtzPath handler:nil];
		[self createQuartzCompositionAtPath:qtzPath moviePath:moviePath];
	}
	
	[movieView setMovie:[QTMovie movieWithFile:qtzPath error:nil]];
	
	[self performSelector:@selector(export) withObject:nil afterDelay:1.0f];
	
}

}

@end

////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// //// //// MyDocument (Private) //// ////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////

@implementation MyDocument (Private)

<plist version="1.0">\n\

\n\ class\n\ QCPatch\n\ state\n\ \n\ connections\n\ \n\ connection_1\n\ \n\ destinationNode\n\ Billboard_1\n\ destinationPort\n\ inputImage\n\ sourceNode\n\ QuickTimePlayer_1\n\ sourcePort\n\ outputImage\n\ \n\ \n\ nodes\n\ \n\ \n\ class\n\ QCQuickTimePlayer\n\ key\n\ QuickTimePlayer_1\n\ state\n\ \n\ deinterlaceFields\n\ \n\ highQuality\n\ \n\ maskCompatibility\n\ \n\ timebase\n\ parent\n\ userInfo\n\ \n\ BAt0eXBlZHN0cmVhbYED6IQBQISE\n\ hBNOU011dGFibGVEaWN0aW9uYXJ5\n\ AISEDE5TRGljdGlvbmFyeQCEhAhO\n\ U09iamVjdACFhAFpAZKEhIQITlNT\n\ dHJpbmcBlYQBKwhwb3NpdGlvboaS\n\ hISEB05TVmFsdWUAlYQBKoSEDXtf\n\ TlNQb2ludD1mZn2bgQMggQOXhoY=\n\ \n\ version\n\ 1\n\ \n\ \n\ \n\ class\n\ QCBillboard\n\ key\n\ Billboard_1\n\ state\n\ \n\ CIRendering\n\ \n\ ivarInputPortStates\n\ \n\ inputBlending\n\ \n\ value\n\ 0\n\ \n\ inputColor\n\ \n\ value\n\ \n\ alpha\n\ 1\n\ blue\n\ 1\n\ green\n\ 1\n\ red\n\ 1\n\ \n\ \n\ inputRotation\n\ \n\ value\n\ 0.0\n\ \n\ inputScale\n\ \n\ value\n\ 1\n\ \n\ inputX\n\ \n\ value\n\ 0.0\n\ \n\ inputY\n\ \n\ value\n\ 0.0\n\ \n\ \n\ pixelAligned\n\ \n\ squarePixels\n\ \n\ systemInputPortStates\n\ \n\ _enable\n\ \n\ value\n\ \n\ \n\ \n\ userInfo\n\ \n\ BAt0eXBlZHN0cmVhbYED6IQBQISE\n\ hBNOU011dGFibGVEaWN0aW9uYXJ5\n\ AISEDE5TRGljdGlvbmFyeQCEhAhO\n\ U09iamVjdACFhAFpAZKEhIQITlNT\n\ dHJpbmcBlYQBKwhwb3NpdGlvboaS\n\ hISEB05TVmFsdWUAlYQBKoSEDXtf\n\ TlNQb2ludD1mZn2bgQP+gQOKhoY=\n\ \n\ version\n\ 1\n\ \n\ \n\ \n\ publishedInputPorts\n\ \n\ \n\ key\n\ Movie_Path\n\ node\n\ QuickTimePlayer_1\n\ port\n\ inputPath\n\ \n\ \n\ timebase\n\ parent\n\ userInfo\n\ \n\ BAt0eXBlZHN0cmVhbYED6IQBQISEhBNOU011dGFibGVEaWN0aW9u\n\ YXJ5AISEDE5TRGljdGlvbmFyeQCEhAhOU09iamVjdACFhAFpAZKE\n\ hIQITlNTdHJpbmcBlYQBKwlzY3JvbGxpbmeGkoSEhAdOU1ZhbHVl\n\ AJWEASqEhA17X05TUG9pbnQ9ZmZ9m4ED5oEEDIaG\n\ \n\ \n\

\n
</plist>”;

char *xmlUTF8 = (char *)[xmlString UTF8String];
NSPropertyListFormat format = NSPropertyListXMLFormat_v1_0;
return [NSPropertyListSerialization propertyListFromData:[NSData dataWithBytes:xmlUTF8 length:strlen(xmlUTF8)]
										mutabilityOption:NSPropertyListMutableContainersAndLeaves
												  format:&format
										errorDescription:(void *)NULL];

}

}

#if OpenNewFilesWithSharedWorkspace == 1 NSWorkspace *sws = [NSWorkspace sharedWorkspace]; [sws openFile:path withApplication:@”Quartz Composer”]; #endif

}

#if OpenNewFilesWithSharedWorkspace == 1 NSWorkspace *sws = [NSWorkspace sharedWorkspace]; [sws openFile:exportPath withApplication:@”QuickTime Player”]; #endif

} else {
	float percentDone = (float)time.timeValue / (float)sourceDuration.timeValue * 100.0f;
	[window setTitle:[NSString stringWithFormat:@"%.2f Percent Done", percentDone]];
	[info setValue:[NSNumber numberWithInt:duplicateCount] forKey:@"duplicateCount"];
	[info setValue:[NSValue valueWithQTTime:time] forKey:@"lastTime"];
	[self performSelector:@selector(nextFrame:) withObject:info afterDelay:0.0f];
}

}

}

@end

////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// //// //// CustomMovie //// ////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////

static OSStatus CreatePixelBufferContext(SInt32 format, SInt32 width, SInt32 height, QTVisualContextRef *contextPtr, NSString **errorStringPtr) { QTVisualContextRef context = NULL; CFDictionaryRef pixelBufferOptions = NULL; CFDictionaryRef visualContextOptions = NULL; OSStatus err = noErr; NSString *error = nil; SInt32 alignment = 16; CFTypeRef vals[] = {NULL, NULL, NULL, NULL}; CFTypeRef keys[] = { kCVPixelBufferPixelFormatTypeKey, kCVPixelBufferWidthKey, kCVPixelBufferHeightKey, kCVPixelBufferBytesPerRowAlignmentKey };

if (!contextPtr || !format || (width < 1) || (height < 1)) 
	PostError(@"invalid input parameters: %i", err = paramErr);

if (vals[0] = CFNumberCreate(NULL, kCFNumberSInt32Type, (void *)&format)) {
	if (vals[1] = CFNumberCreate(NULL, kCFNumberSInt32Type, (void *)&width)) {
		if (vals[2] = CFNumberCreate(NULL, kCFNumberSInt32Type, (void *)&height)) {
			if (vals[3] = CFNumberCreate(NULL, kCFNumberSInt32Type, (void *)&alignment)) {
				pixelBufferOptions = CFDictionaryCreate(NULL, 
														(const void **)keys, 
														(const void **)vals, 
														(CFIndex)4, 
														&kCFTypeDictionaryKeyCallBacks, 
														&kCFTypeDictionaryValueCallBacks);
				CFRelease((CFTypeRef)vals[3]);
			}
			CFRelease((CFTypeRef)vals[2]);
		}
		CFRelease((CFTypeRef)vals[1]);
	}
	CFRelease((CFTypeRef)vals[0]);
}

if (!pixelBufferOptions) 
	PostError(@"unable to create pixel buffer options: %i", err = coreFoundationUnknownErr);

visualContextOptions = CFDictionaryCreate(NULL, 
										  (const void **)&kQTVisualContextPixelBufferAttributesKey, 
										  (const void **)&pixelBufferOptions, 
										  (CFIndex)1, 
										  &kCFTypeDictionaryKeyCallBacks, 
										  &kCFTypeDictionaryValueCallBacks);

if (!visualContextOptions) 
	PostError(@"unable to create visual context options: %i", err = coreFoundationUnknownErr);

if (err = QTPixelBufferContextCreate(kCFAllocatorDefault, visualContextOptions, &context))
	PostError(@"unable to create pixel buffer context: %i", err);

*contextPtr = context;
context = NULL;

ERROR:

if (visualContextOptions) CFRelease(visualContextOptions);
if (pixelBufferOptions) CFRelease(pixelBufferOptions);
if (context) QTVisualContextRelease(context);

if (errorStringPtr) 
	*errorStringPtr = error;

return err; }

@implementation CustomMovie

ERROR:;

NSLog(@"<%p>%s: ERROR!! (%i) %@", self, __PRETTY_FUNCTION__, err, error);
[self release];
return nil;

}

}

ERROR:;

NSLog(@"<%p>%s: ERROR: %@", self, __PRETTY_FUNCTION__, error);

BOOL isDir;
if (atomicPath &&
	([manager fileExistsAtPath:atomicPath isDirectory:&isDir] && !isDir))
{
	[manager removeFileAtPath:atomicPath handler:nil];
}
return NO; }

@end