Here is the standard Quicktime Component export settings dialog we are going to use:
The dialog is only available in Quicktime framework (not to be confused with QTKit), hence the limitations:
- 32-bit build only
- No iOS integration whatsoever.
First thing you need to do is to link your application with Quiktime framework, make sure you build it in 32-bit Intel architecture and Build Active Architecture Only setting is turned on.
The goal is to open the dialog, set the necessary export settings and use them to export a movie.
I will provide the code pieces in reverse order starting from the actual dialog call going back to the usage of required structures. We have got to use a lot of code, so some patience is necessary.
The dialog requires a Quicktime component. On our example we will use MPEG4 component. We provide the component, the previously stored settings and open the dialog:
#import <QuickTime/QuickTimeComponents.h>
- (void)editExportSetting:(JSMovieExportSetting*)setting
{
MovieExportComponent exporter = OpenComponent(_MPEG4Component.component);
Boolean canceled;
[self updateMovieExportComponent:exporter withExportSetting:setting];
ComponentResult err = MovieExportDoUserDialog(exporter, NULL, NULL, 0, 0, &canceled);
if (!canceled)
{
if (err == 0)
{
[self updateExportSetting:setting withMovieExportComponent:exporter];
}
}
CloseComponent(exporter);
}
When user finished with editing and clicks OK button we store the settings for later usage.
The OpenComponent
function requires Component
data type. I have built a convenience class wrapper for it, see the listing below.
Now we need to get the MPEG4 component:
- (JSQTComponent*)MPEG4Component
{
/*
MPEG-4
*/
ComponentDescription description;
description.componentType = 1936746868;
description.componentSubType = 1836082996;
description.componentManufacturer = 1634758764;
description.componentFlags = 269058096;
description.componentFlagsMask = 66207;
return [self componentWithDescription:description];
}
- (JSQTComponent*)componentWithDescription:(ComponentDescription)description
{
JSQTComponent* result = nil;
for (JSQTComponent* component in [self components])
{
if ([component isEqualToComponentDescription:description])
{
result = component;
break;
}
}
return result;
}
- (NSArray*)components
{
if (_components == nil)
{
_components = [NSMutableArray new];
QTAtomContainer resources = NULL;
OSErr err = GetComponentPublicResourceList(kQTPresetsListResourceType, 1, 0, &_componentDescription, nil, nil, &resources);
if (err != noErr)
{
NSLog(@"JSQTComponentDataModel error: %d", err);
}
else if (resources != NULL)
{
QTAtom currChild = 0;
QTAtom nextChild = 0;
OSErr err;
unsigned atomsCount = QTCountChildrenOfType(resources, kParentAtomIsContainer, 0);
for (UInt16 i=0; i < atomsCount; i++)
{
QTAtom compAtomId = 0;
if (QTFindChildByIndex(resources, kParentAtomIsContainer, kQTMetaDataCommonKeyComposer, i+1, &compAtomId))
{
Component component = (Component)compAtomId;
err = QTNextChildAnyType(resources, kParentAtomIsContainer, currChild, &nextChild);
if (err == noErr && nextChild)
{
[_components addObject:[[[JSQTComponent alloc] initWithComponent:component] autorelease]];
}
else
{
NSLog(@"Error %d getting item %d
", err, i);
}
currChild = nextChild;
}
}
QTDisposeAtomContainer(resources);
}
}
return _components;
}
Basically we look for a component with the description we need in the list. The list was build to get components of a certain type, because Quicktime has tons of different once.
We are interested only in those once created for movie export.
ComponentDescription _componentDescription;
_componentDescription.componentType = MovieExportType;
_componentDescription.componentSubType = kAnyComponentSubType;
_componentDescription.componentManufacturer = kAnyComponentManufacturer;
_componentDescription.componentFlags = canMovieExportFiles;
_componentDescription.componentFlagsMask = canMovieExportFiles;
The list is cached for further usage, do not forget to release it in your dealloc.
We are going pretty good so far. We have got our MPEG4 component object, got Component
structure from it. Now we need to apply the settings we stored last time to the component.
- (void)updateMovieExportComponent:(MovieExportComponent)component withExportSetting:(JSMovieExportSetting*)movieExportSetting
{
NSData* presetSettings = movieExportSetting.exportSettings;
Handle settings = NewHandleClear([presetSettings length]);
if (settings)
{
memcpy(*settings, [presetSettings bytes], GetHandleSize(settings));
// Set movie export settings from the settings QTAtomContainer
MovieExportSetSettingsFromAtomContainer(component, (QTAtomContainer)settings);
DisposeHandle(settings);
}
}
If you use the component first time without any settings, the dialog will display default once.
After user has finished with editing and pressed OK we need to store the settings:
- (void)updateExportSetting:(JSMovieExportSetting*)movieExportSetting withMovieExportComponent:(MovieExportComponent)component
{
QTAtomContainer settings;
ComponentResult err = MovieExportGetSettingsAsAtomContainer(component, &settings);
if (err == 0)
{
NSData* exportSettings = [NSData dataWithBytes:*settings length:GetHandleSize(settings)];
movieExportSetting.exportSettings = exportSettings;
}
}
JSMovieExportSetting
is a wrapper, see the listing below. You can easily make it serialised to a file to store the settings.
The last step is to use the settings we have just got for movie conversion.
The good new is that we do not have to use Quicktime any more, we can use QTKit methods.
QTMovie* movie = [QTMovie movieWithFile:sourceFilePath error:outError];
NSDictionary* settings = [movieExportSetting movieAttributes];
[movie writeToFile:outputFilePath withAttributes:settings error:outError]
Set movie delegate and implement movie: shouldContinueOperation: withPhase: atPercent: withAttributes:
to see the progress if needed.
Below you can find listings of the classes used. I hope this helps.
JSQTComponent
@interface JSQTComponent : NSObject
{
Component _component;
ComponentDescription _componentDescription;
NSString* _name;
NSString* _info;
NSString* _icon;
}
- (id)initWithComponent:(Component)component;
@property (nonatomic, readonly) Component component;
@property (nonatomic, readonly) ComponentDescription componentDescription;
@property (nonatomic, retain) NSString* name;
@property (nonatomic, retain) NSString* info;
@property (nonatomic, retain) NSString* icon;
- (BOOL)isEqualToComponentDescription:(ComponentDescription)anotherComponentDescription;
@end
@implementation JSQTComponent
@synthesize component = _component;
@synthesize componentDescription = _componentDescription;
@synthesize name = _name;
@synthesize info = _info;
@synthesize icon = _icon;
(id)initWithComponent:(Component)component
{
self = [super init];
if (self != nil)
{
_component = component;
[self getDescription];
}
return self;
}
(void)dealloc
{
[_name release];
[_info release];
[_icon release];
[super dealloc];
}
(void)getDescription
{
OSErr err;
Handle aName = NewHandleClear(255);
Handle anInfo = NewHandleClear(255);
Handle anIcon = NewHandleClear(255);
Handle iconSuite = NULL;
err = GetComponentInfo(_component, &_componentDescription, aName, anInfo, anIcon);
if (err == 0)
{
self.name = [NSString stringWithPStringHandle:aName];
self.info = [NSString stringWithPStringHandle:anInfo];
self.icon = [NSString stringWithPStringHandle:anIcon];
//err = GetComponentIconSuite(aComponent, &iconSuite);
}
DisposeHandle(aName);
DisposeHandle(anInfo);
DisposeHandle(anIcon);
DisposeHandle(iconSuite);
}
(BOOL)isEqualToComponentDescription:(ComponentDescription)anotherComponentDescription
{
return (_componentDescription.componentType == anotherComponentDescription.componentType) &&
(_componentDescription.componentSubType == anotherComponentDescription.componentSubType) &&
(_componentDescription.componentManufacturer == anotherComponentDescription.componentManufacturer);
}
@end
JSMovieExportSetting
@interface JSMovieExportSetting : NSObject <NSCoding>
{
NSString* _name;
NSNumber* _componentSubType;
NSNumber* _componentManufacturer;
NSData* _exportSettings;
NSDictionary* _movieAttributes;
}
- (id)initWithName:(NSString*)name attributes:(NSDictionary*)attributes;
@property (nonatomic, retain) NSString* name;
@property (nonatomic, retain) NSNumber* componentSubType;
@property (nonatomic, retain) NSNumber* componentManufacturer;
@property (nonatomic, retain) NSData* exportSettings;
@property (nonatomic, readonly) NSDictionary* movieAttributes;
@end
@implementation JSMovieExportSetting
...
- (NSDictionary*)movieAttributes
{
if (_movieAttributes == nil)
_movieAttributes = [[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], QTMovieExport,
_componentSubType, QTMovieExportType,
_componentManufacturer, QTMovieExportManufacturer,
_exportSettings, QTMovieExportSettings, nil] retain];
return _movieAttributes;
}
- (void)setExportSettings:(NSData*)exportSettings
{
if (_exportSettings != exportSettings)
{
[_exportSettings release];
_exportSettings = [exportSettings retain];
[_movieAttributes release];
_movieAttributes = nil;
}
}
...
@end