Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
MyDocument.m
// |
// File: MyDocument.m |
// |
// Abstract: The NSDocument subclass for the CDMetadataBrowser's coredata document. |
// Overrides the NSDocument methods involving cut-copy-pasting, document |
// reading, writing . Responds also as a delegate to the enclosed table-view. |
// |
// Version: 1.0 |
// Originally introducted at WWDC 2008 at Session: |
// Extending and Integrating Post-Production Applications with Final Cut Pro |
// |
// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. ("Apple") |
// in consideration of your agreement to the following terms, and your use, |
// installation, modification or redistribution of this Apple software |
// constitutes acceptance of these terms. If you do not agree with these |
// terms, please do not use, install, modify or redistribute this Apple |
// software. |
// |
// In consideration of your agreement to abide by the following terms, and |
// subject to these terms, Apple grants you a personal, non - exclusive |
// license, under Apple's copyrights in this original Apple software ( the |
// "Apple Software" ), to use, reproduce, modify and redistribute the Apple |
// Software, with or without modifications, in source and / or binary forms; |
// provided that if you redistribute the Apple Software in its entirety and |
// without modifications, you must retain this notice and the following text |
// and disclaimers in all such redistributions of the Apple Software. Neither |
// the name, trademarks, service marks or logos of Apple Inc. may be used to |
// endorse or promote products derived from the Apple Software without specific |
// prior written permission from Apple. Except as expressly stated in this |
// notice, no other rights or licenses, express or implied, are granted by |
// Apple herein, including but not limited to any patent rights that may be |
// infringed by your derivative works or by other works in which the Apple |
// Software may be incorporated. |
// |
// The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
// WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
// WARRANTIES OF NON - INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A |
// PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION |
// ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
// |
// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
// CONSEQUENTIAL DAMAGES ( INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
// INTERRUPTION ) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION |
// AND / OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER |
// UNDER THEORY OF CONTRACT, TORT ( INCLUDING NEGLIGENCE ), STRICT LIABILITY OR |
// OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// |
// Copyright ( C ) 2008 Apple Inc. All Rights Reserved. |
// |
#import "MyDocument.h" |
#import <Foundation/Foundation.h> |
#import "MovieDocument.h" |
#import "MetadataAttributeMO.h" |
NSString *MetadataAttributePBType = @"MetadataAttributePBType"; |
// forward declarations for QT metadata utility functions |
NSString * LocalMetaDataGetItemKey(QTMetaDataRef metadataContainer, QTMetaDataItem metadataItem); |
id LocalMetaDataGetItemValue(QTMetaDataRef metadataContainer, QTMetaDataItem metadataItem, UInt32 *pKeyDataType); |
BOOL LocalRemoveSupportedMetadataElements( QTMetaDataRef metadataContainer ); |
BOOL LocalEmptyMovieMetadataContainers( Movie theMovie ); |
// document class implementation |
@implementation MyDocument |
//---------------------------------------- |
- (id)init |
{ |
self = [super init]; |
if (self != nil) { |
// initialization code |
theMovie = NULL; |
} |
return self; |
} |
//---------------------------------------- |
- (void)dealloc |
{ |
// dispose of local data |
if (theMovie) { [theMovie release]; theMovie = NULL; } |
[super dealloc]; |
} |
//---------------------------------------- |
- (NSString *)windowNibName |
{ |
return @"MyDocument"; |
} |
//---------------------------------------- |
//setup the bindings for the metadata controllers to update against the movie |
- (IBAction)resetMetadataBindings{ |
[myMetadataController unbind:@"contentSet"]; |
[myMetadataController setContent:nil]; |
//set the content set of the metadataTreeController |
NSMutableDictionary* options; |
options = [NSMutableDictionary new]; |
[options setObject:[NSNumber numberWithBool:NO] forKey:@"NSConditionallySetsEnabled"]; |
[options setObject:[NSNumber numberWithBool:NO] forKey:@"NSRaisesForNotApplicableKeys"]; |
[myMetadataController bind:@"contentSet" toObject:myMovieController withKeyPath:@"selection.metadata" options:options]; |
[options release]; |
} |
//---------------------------------------- |
// once the UI is up and running, setup the data |
- (void)windowControllerDidLoadNib:(NSWindowController *)windowController |
{ |
// do the normal stuff |
[super windowControllerDidLoadNib:windowController]; |
// point the movie view at our document movie |
[myMovieView setMovie:theMovie]; |
//set the metadata content-- needs to happen after the movie is loaded or you'll confuse the controllers. |
[self resetMetadataBindings]; |
} |
//======================================== |
# pragma mark table view delegate |
//delegate of the table view stuff: |
//-------------------------------------------------------------------------------- |
// if the user tries to edit some binary data or one of the image file types, |
// pop open the inspector. Otherwise, let them edit! |
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item{ |
BOOL allowEdit = YES; |
//if the selection is on the data |
if (tableColumn == myValueTableColumn){ |
// get the object from the right row |
MetadataAttributeMO *firstSelectedObject = [[myMetadataController selectedObjects] objectAtIndex: 0]; |
//check if this is a piece of binary data that requires the inspector to edit |
UInt32 selectedObjectType = [[firstSelectedObject valueForKey:@"type"] intValue]; |
switch (selectedObjectType){ |
case kQTMetaDataTypeBinary: |
case kQTMetaDataTypeJPEGImage: |
case kQTMetaDataTypePNGImage: |
case kQTMetaDataTypeBMPImage: |
[self showInspector:self]; |
allowEdit = NO; |
break; |
// default: |
// //do nothing, allow the edit to happen in the table. |
} |
} |
return allowEdit; |
} |
//================================================== |
# pragma mark cut/copy/paste |
// cut/copy/paste |
//---------------------------------------- |
//build up a copy of the managed objects, both string and metadata types |
- (void)copy:sender { |
// get the selected objects |
NSArray *selectedObjects = [myMetadataController selectedObjects]; |
unsigned i, count = [selectedObjects count]; |
if (count == 0) { |
return; |
} |
//make some arrays to store our copied representations |
NSMutableArray *copyObjectsArray = [NSMutableArray arrayWithCapacity:count]; |
NSMutableArray *copyStringsArray = [NSMutableArray arrayWithCapacity:count]; |
MetadataAttributeMO *metadata; |
//iterate through the objects storing off a representation of each |
for (i = 0; i < count; i++) { |
metadata = (MetadataAttributeMO *)[selectedObjects objectAtIndex:i]; |
[copyObjectsArray addObject:[metadata dictionaryRepresentation]]; |
[copyStringsArray addObject:[metadata stringDescription]]; |
} |
NSPasteboard *generalPasteboard = [NSPasteboard generalPasteboard]; |
//make sure the pasteboard knows to send my type objects to me |
[generalPasteboard declareTypes: |
[NSArray arrayWithObjects:MetadataAttributePBType, NSStringPboardType, nil] |
owner:self]; |
// cache away the copies on the paste-board. |
NSData *copyData = [NSArchiver archivedDataWithRootObject:copyObjectsArray]; |
[generalPasteboard setData:[copyData retain] forType:MetadataAttributePBType]; |
[generalPasteboard setString: |
[[copyStringsArray componentsJoinedByString:@"\n"] retain] |
forType:NSStringPboardType]; |
} |
//---------------------------------------- |
//when we cut, just cache away the selection for me to use |
- (void)cut:sender { |
[self copy:sender]; |
NSManagedObjectContext *moc = [self managedObjectContext]; |
NSArray *selectedMetadata = [myMetadataController selectedObjects]; |
unsigned i, count = [selectedMetadata count]; |
for (i = 0; i < count; i++) { |
NSManagedObject *metadata; |
metadata = (NSManagedObject *)[selectedMetadata objectAtIndex:i]; |
[moc deleteObject:metadata]; |
} |
} |
//---------------------------------------- |
//when we paste, add each item in |
- (void)paste:sender { |
// collect up the right type from the pasteboard |
NSPasteboard *generalPasteboard = [NSPasteboard generalPasteboard]; |
NSData *data = [generalPasteboard dataForType:MetadataAttributePBType]; |
if (data == nil) { |
return; |
} |
//figure out where we are pasting to (which movie container) |
NSManagedObjectContext *documentMOC = [self managedObjectContext]; |
NSArray *selectedObjects = [myMovieController selectedObjects]; |
NSArray *pasteArray = [NSUnarchiver unarchiveObjectWithData:data]; |
unsigned i, pasteFromCount = [pasteArray count], j, pasteToCount = [selectedObjects count]; |
//for each place we're pasting to |
for (j = 0; j < pasteToCount; j++) { |
NSMutableSet *container = [[selectedObjects objectAtIndex:j] mutableSetValueForKey:@"metadata"]; |
//for each object we're pasting |
for (i = 0; i < pasteFromCount; i++) { |
NSManagedObject *keyEntry = NULL; |
//add the object |
keyEntry = [NSEntityDescription insertNewObjectForEntityForName:@"attribute" inManagedObjectContext:documentMOC]; |
[keyEntry setValuesForKeysWithDictionary:[pasteArray objectAtIndex:i]]; |
[container addObject:keyEntry]; |
} |
} |
} |
//================================================== |
# pragma mark inspector |
// open/update the inspector |
//---------------------------------------- |
//open up the inspector with the first item in the selection |
- (IBAction)showInspector:(id)sender{ |
id selectedObjects = [myMetadataController selectedObjects]; |
//open up the inspector with either the first item in the selection or |
// clear out the selection data in the inspector |
if ([selectedObjects count] > 0) |
[myInspectorController editValue:[selectedObjects objectAtIndex: 0]]; |
else [myInspectorController editValue: nil]; |
//refresh |
[myMetadataController prepareContent]; |
} |
//---------------------------------------- |
//update the inspector with either the first item in the selection or clear it out |
- (IBAction)updateInspector:(id)sender{ |
id selectedObjects = [myMetadataController selectedObjects]; |
if ([selectedObjects count] > 0) |
[myInspectorController setEditItem:[selectedObjects objectAtIndex: 0]]; |
else [myInspectorController setEditItem: nil]; |
} |
//================================================== |
# pragma mark read |
// read metadata from QT movie |
//---------------------------------------- |
//add the object to the destination set |
- (BOOL) readMetadataAddValue:(id)theValue forKey:(NSString*)theKey ofType:(UInt32)dataType ofSize:(UInt32)dataSize withIndex:(long)index toContainer:(NSMutableSet*)destinationSet error:(NSError **)errorPtr; |
{ |
// shared locals |
NSManagedObjectContext *documentMOC = [self managedObjectContext]; |
NSManagedObject *keyEntry = NULL; |
keyEntry = [NSEntityDescription insertNewObjectForEntityForName:@"attribute" inManagedObjectContext:documentMOC]; |
if (theKey != nil){ |
[keyEntry setValue:theKey forKey:@"name"]; |
[keyEntry setValue:[NSNumber numberWithLong:(dataType)] forKey:@"type"]; |
[keyEntry setValue:[NSNumber numberWithLong:(index)] forKey:@"index"]; |
if (dataSize > 0) |
[keyEntry setValue:[NSNumber numberWithLong:(dataSize)] forKey:@"size"]; |
[keyEntry setValue:theValue forKey:[MetadataAttributeMO dataKeyForType:dataType]]; |
} |
[destinationSet addObject:keyEntry]; |
// return results |
return ((keyEntry != NULL)?(YES):(NO)); |
} |
//---------------------------------------- |
// read the metadata out of a movie container and add it to the set specified |
- (BOOL) readMetadataFromMovieContainer:(QTMetaDataRef)metadataContainer toSet:(NSMutableSet*)destinationSet error:(NSError **)errorPtr |
{ |
// shared locals |
QTMetaDataItem curMetadataItem = kQTMetaDataItemUninitialized; |
long keyOrderingIndex = 0; |
BOOL success = YES, foundMetadata = FALSE; |
// iterate over the metadata items using the reverse-dns namespace |
while (metadataContainer != nil && QTMetaDataGetNextItem(metadataContainer, kQTMetaDataStorageFormatQuickTime, |
curMetadataItem, kQTMetaDataKeyFormatQuickTime, NULL, 0, &curMetadataItem) == noErr) |
{ |
UInt32 keyDataType = 0, finalValueSize = 0; |
id finalValueInstance = NULL; |
NSString *finalKeyName = NULL; |
BOOL useThisKey = YES; |
foundMetadata = TRUE; |
// retrieve the key |
if ((finalKeyName = LocalMetaDataGetItemKey(metadataContainer, curMetadataItem)) == NULL) |
useThisKey = NO; |
else if ((finalValueInstance = LocalMetaDataGetItemValue(metadataContainer, curMetadataItem, &keyDataType)) == NULL) |
useThisKey = NO; |
// assuming we were successful, add an entry to the output set/container |
if (success == YES && useThisKey == YES && finalValueInstance != NULL && finalKeyName != NULL) |
{ |
success = [self readMetadataAddValue:finalValueInstance |
forKey:finalKeyName |
ofType:keyDataType |
ofSize:finalValueSize |
withIndex:keyOrderingIndex |
toContainer:destinationSet |
error:errorPtr]; |
// increment ordering index |
keyOrderingIndex++; |
} |
} |
// return results |
return (success); |
} |
//---------------------------------------- |
// read a movie file from the URL, creating a metadata cache |
- (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError |
{ |
// shared locals |
BOOL success = YES; |
// try to open the movie from the URL |
if ((theMovie = [QTMovie movieWithURL:absoluteURL error:outError]) == NULL) |
success = NO; |
else |
[theMovie retain]; |
// assuming we opened the movie, call the superclass to configure an in-memory core data store |
// (the use of an in-memory store is specified in the file types pane) |
if (success == YES) |
success = [super readFromURL:absoluteURL ofType:typeName error:outError]; |
// read data from the movie, filling our in-memory data store |
if (success == YES) |
success = [self readDataFromMovieFile:outError]; |
// mark as NOT dirty at this point |
if (success == YES) { |
success = [[self managedObjectContext] save:outError]; |
[[self undoManager] removeAllActions]; |
} |
// return results |
return (success); |
} // readFromURL |
//---------------------------------------- |
// read a movie file from the URL, filling our in-memory data store |
- (BOOL) readDataFromMovieFile:(NSError **)errorPtr |
{ |
// shared locals |
NSManagedObjectContext *documentMOC = [self managedObjectContext]; |
QTMetaDataRef curMetadataContainer = NULL; |
BOOL success = YES; |
// reset store state |
[documentMOC reset]; |
// build base movie entry |
NSManagedObject *movieContainer = [NSEntityDescription insertNewObjectForEntityForName:@"container" inManagedObjectContext:documentMOC]; |
[movieContainer setValue:[NSString stringWithFormat:@"Movie (%@)", [self displayName]] forKey:@"name"]; |
NSMutableSet *movieChildrenSet = [movieContainer mutableSetValueForKey:@"children"]; |
// read movie metadata |
QTCopyMovieMetaData([theMovie quickTimeMovie], &curMetadataContainer); |
if (curMetadataContainer != NULL) |
{ |
success = [self readMetadataFromMovieContainer:curMetadataContainer toSet:[movieContainer mutableSetValueForKey:@"metadata"] error:errorPtr]; |
QTMetaDataRelease(curMetadataContainer); |
curMetadataContainer = NULL; |
} |
// start iterating over tracks |
QTTrack * curTrack = NULL; |
NSEnumerator * trackEnumerator = [[theMovie tracks] objectEnumerator]; |
while (success == YES && (curTrack = [trackEnumerator nextObject]) != NULL) |
{ |
// add the track |
NSManagedObject *trackContainer = [NSEntityDescription insertNewObjectForEntityForName:@"container" inManagedObjectContext:documentMOC]; |
[trackContainer setValue:[NSString stringWithFormat:@"%@ - %@", |
[curTrack attributeForKey:QTTrackIDAttribute], |
[curTrack attributeForKey:QTTrackDisplayNameAttribute]] |
forKey:@"name"]; |
[movieChildrenSet addObject:trackContainer]; |
NSMutableSet *trackChildrenSet = [trackContainer mutableSetValueForKey:@"children"]; |
// read track metadata |
QTCopyTrackMetaData([curTrack quickTimeTrack], &curMetadataContainer); |
if (curMetadataContainer != NULL) |
{ |
success = [self readMetadataFromMovieContainer:curMetadataContainer toSet:[trackContainer mutableSetValueForKey:@"metadata"] error:errorPtr]; |
QTMetaDataRelease(curMetadataContainer); |
curMetadataContainer = NULL; |
} |
// add the media |
QTMedia * curTrackMedia = [curTrack media]; |
NSManagedObject *mediaContainer = [NSEntityDescription insertNewObjectForEntityForName:@"container" inManagedObjectContext:documentMOC]; |
[mediaContainer setValue:[NSString stringWithFormat:@"Media (%@)", [curTrackMedia attributeForKey:QTMediaTypeAttribute]] forKey:@"name"]; |
[trackChildrenSet addObject:mediaContainer]; |
// read media metadata |
QTCopyMediaMetaData([curTrackMedia quickTimeMedia], &curMetadataContainer); |
if (curMetadataContainer != NULL) |
{ |
success = [self readMetadataFromMovieContainer:curMetadataContainer toSet:[mediaContainer mutableSetValueForKey:@"metadata"] error:errorPtr]; |
QTMetaDataRelease(curMetadataContainer); |
curMetadataContainer = NULL; |
} |
} |
// return results |
return (success); |
} |
//================================================== |
# pragma mark write |
// write metadata to QT movie |
//---------------------------------------- |
//for saving back to the movie, override dataRepresentationOfType to map back to the movie's info |
- (NSData *)dataRepresentationOfType:(NSString *)docType |
{ |
return [theMovie movieFormatRepresentation]; |
} |
//-------------------------------------------------------------------------------- |
//write out metadata from our runtime managed object container to a specific a QT container |
- (BOOL) writeMetadataFromContainer:(NSManagedObject*)container forKey:(NSString*)theKey ofType:(QTPropertyValueType)dataType toFileContainer:(QTMetaDataRef)destinationContainer error:(NSError **)errorPtr; |
{ |
BOOL success = YES; |
NSData* nameData = [theKey dataUsingEncoding:NSUTF8StringEncoding]; |
id data = [container valueForKey:@"data"]; |
NSData* dataValue = nil; |
UInt8 *dataValuePtr = NULL; |
ByteCount dataSize = 0; |
//switch here on type to get various things. |
switch (dataType) |
{ |
case kQTMetaDataTypeJPEGImage: |
case kQTMetaDataTypePNGImage: |
case kQTMetaDataTypeBMPImage: |
case kQTMetaDataTypeBinary: |
dataValue = (NSData*)data; |
break; |
case kQTMetaDataTypeUTF8: |
dataValue = [(NSString*)data dataUsingEncoding:NSUTF8StringEncoding]; |
break; |
case kQTMetaDataTypeUTF16BE: |
dataValue = [(NSString*)data dataUsingEncoding:NSUnicodeStringEncoding]; |
break; |
case kQTMetaDataTypeMacEncodedText: |
dataValue = [(NSString*)data dataUsingEncoding:NSMacOSRomanStringEncoding]; |
break; |
case kQTMetaDataTypeSignedIntegerBE: |
{ |
int intValue = NSSwapHostIntToBig([(NSNumber*)data intValue]); |
dataValuePtr = (UInt8*)(&intValue); |
dataSize = sizeof( int); |
} |
break; |
case kQTMetaDataTypeUnsignedIntegerBE: |
{ |
unsigned int unsignedIntValue = NSSwapHostIntToBig([(NSNumber*)data unsignedIntValue]); |
dataValuePtr = (UInt8*)(&unsignedIntValue); |
dataSize = sizeof(unsigned int); |
} |
break; |
case kQTMetaDataTypeFloat32BE: |
{ |
float floatValue = [(NSNumber*)data floatValue]; |
dataValuePtr = (UInt8*)(&floatValue); |
dataSize = sizeof(float); |
} |
break; |
case kQTMetaDataTypeFloat64BE: |
{ |
double doubleValue = [(NSNumber*)data doubleValue]; |
dataValuePtr = (UInt8*)(&doubleValue); |
dataSize = sizeof(double); |
} |
break; |
// NOTE: in addition to the other data types, deal with containers! |
default: |
dataValue = (NSData*)data; |
NSLog (@"(unable to read value for type: %u, reading as binary)", dataType); |
break; |
} |
if (dataValue != nil && dataValuePtr== NULL){ |
dataValuePtr = (UInt8*)[dataValue bytes]; |
dataSize = [dataValue length]; |
} |
//now actually make the new metadata entry |
OSStatus errors = noErr; |
QTMetaDataItem newMetadataEntry = kQTMetaDataItemUninitialized; |
if ((errors = QTMetaDataAddItem(destinationContainer, |
kQTMetaDataStorageFormatQuickTime, // format |
kQTMetaDataKeyFormatQuickTime, // namespace |
[nameData bytes], ([nameData length]), // element key name (strip trailing NULL) |
dataValuePtr, dataSize, // element data |
dataType, &newMetadataEntry)) != noErr){ |
success = NO; |
// NSLog(@"failed. No metadata written. :( :::::::::::::::::::::::::::::"); |
} else { |
// NSLog(@"wrote metadata!!!"); |
} |
if (errorPtr!= nil) |
*errorPtr = [NSError errorWithDomain:NSOSStatusErrorDomain code:errors userInfo:nil]; |
return success; |
} |
//-------------------------------------------------------------------------------- |
// write the contents of the cache dictionary to a movie file |
- (BOOL) writeMetadata: ( NSMutableSet *)metadataSet toMovieContainer: (QTMetaDataRef)targetMetadataContainer |
{ |
// locals |
BOOL success = YES; |
NSEnumerator * metadataEnumerator = NULL; |
NSManagedObject *currMetadata = NULL; |
// enumerate keys |
if ((metadataEnumerator = [metadataSet objectEnumerator]) != NULL) |
while ((currMetadata = [metadataEnumerator nextObject]) != NULL && success == YES) |
{ |
// get the data, convert it into bytes, and add it to the metadata container |
NSString* name = [currMetadata valueForKey:@"name"]; |
UInt32 dataType = [[currMetadata valueForKey:@"type"] longValue]; |
// only one value for this name |
success = [self writeMetadataFromContainer: currMetadata forKey:name ofType:(QTPropertyValueType)dataType toFileContainer:targetMetadataContainer error:nil]; |
} |
// cleanup and return |
return (success); |
} // writeMetadata:metadataSet toMovieContainer:targetMetadataContainer |
//---------------------------------------- |
// write any changes to the movie file |
- (BOOL)writeWithBackupToFile:(NSString *)fullDocumentPath ofType:(NSString *)type saveOperation:(NSSaveOperationType)saveOperation |
{ |
BOOL success = NO; |
// shared locals |
NSManagedObjectContext *documentMOC = [self managedObjectContext]; |
QTMetaDataRef curMetadataContainer = NULL; |
//first wipe out everything in the container (it is easier to simply do this than to diff each piece of data) |
success = LocalEmptyMovieMetadataContainers( [theMovie quickTimeMovie] ); |
if (success == YES && documentMOC){ |
NSFetchRequest * theFetch = [[NSFetchRequest alloc] init]; |
//at the top level, the movie container |
[theFetch setEntity:[NSEntityDescription entityForName:@"container" inManagedObjectContext:documentMOC]]; |
NSString *movieContainerName = [NSString stringWithFormat:@"Movie (%@)", [self displayName]]; |
//build up a predicate to find all the metadata that goes at this level |
[theFetch setPredicate:[NSPredicate |
predicateWithFormat:@"(name != nil) AND (name == %@)", movieContainerName]]; |
//find all the matching metadata in our run time database we want to write at this level |
NSError * curError = NULL; |
NSArray * theMovieFindResults = [documentMOC executeFetchRequest:theFetch error:&curError]; |
if (theMovieFindResults != nil){ |
NSEnumerator *storedMovieMetadataParentContainers = [theMovieFindResults objectEnumerator]; |
NSManagedObject *storedParent = nil; |
// get the container where we're going to write the metadata to |
QTCopyMovieMetaData([theMovie quickTimeMovie], &curMetadataContainer); |
//for each metadata container in our data base |
while (curMetadataContainer != NULL && ((storedParent = [storedMovieMetadataParentContainers nextObject]) != NULL)) |
{ |
//write the metadata to the specified container |
success = [self writeMetadata: [storedParent mutableSetValueForKey:@"metadata"] toMovieContainer: curMetadataContainer]; |
QTMetaDataRelease(curMetadataContainer); |
curMetadataContainer = NULL; |
} |
} |
// start iterating over tracks |
QTTrack * curTrack = NULL; |
NSEnumerator * trackEnumerator = [[theMovie tracks] objectEnumerator]; |
while (success == YES && (curTrack = [trackEnumerator nextObject]) != NULL) |
{ |
// get the metadata for the track |
NSString *trackContainerName = [NSString stringWithFormat:@"%@ - %@", |
[curTrack attributeForKey:QTTrackIDAttribute], |
[curTrack attributeForKey:QTTrackDisplayNameAttribute]]; |
//build up a predicate to find all the metadata that goes at this level |
[theFetch setPredicate:[NSPredicate |
predicateWithFormat:@"(name != nil) AND (name == %@)", trackContainerName]]; |
//find all the matching metadata in our run time database we want to write at this level |
NSError * curTrackError = NULL; |
NSArray * theTrackFindResults = [documentMOC executeFetchRequest:theFetch error:&curTrackError]; |
if (theTrackFindResults != nil){ |
NSEnumerator *storedTrackMetadataParentContainers = [theTrackFindResults objectEnumerator]; |
NSManagedObject *storedParent = nil; |
// get the container where we're going to write the metadata to |
QTCopyTrackMetaData([curTrack quickTimeTrack], &curMetadataContainer); |
// for each metadata container in our data base |
while ((curMetadataContainer != NULL) && ((storedParent = [storedTrackMetadataParentContainers nextObject]) != nil)) |
{ |
//write the metadata to the specified container |
success = [self writeMetadata: [storedParent mutableSetValueForKey:@"metadata"] toMovieContainer: curMetadataContainer]; |
QTMetaDataRelease(curMetadataContainer); |
curMetadataContainer = NULL; |
} |
} |
// get the metadata for the media |
QTMedia * curTrackMedia = [curTrack media]; |
NSString *mediaContainerName = [NSString stringWithFormat:@"Media (%@)", [curTrackMedia attributeForKey:QTMediaTypeAttribute]]; |
//build up a predicate to find all the metadata that goes at this level |
[theFetch setPredicate:[NSPredicate |
predicateWithFormat:@"(name != nil) AND (name == %@)", mediaContainerName]]; |
//find all the matching metadata in our run time database we want to write at this level |
NSError * curMediaErr = NULL; |
NSArray * theMediaFindResults = [documentMOC executeFetchRequest:theFetch error:&curMediaErr]; |
if (theMediaFindResults != nil){ |
NSEnumerator *storedMediaMetadataParentContainers = [theMediaFindResults objectEnumerator]; |
NSManagedObject *storedParent = nil; |
// get the container where we're going to write the metadata to |
QTCopyMediaMetaData([curTrackMedia quickTimeMedia], &curMetadataContainer); |
// for each metadata container in our data base |
while (curMetadataContainer != NULL && ((storedParent = [storedMediaMetadataParentContainers nextObject])!= nil)) |
{ |
//write the metadata to the specified container |
success = [self writeMetadata: [storedParent mutableSetValueForKey:@"metadata"] toMovieContainer: curMetadataContainer]; |
QTMetaDataRelease(curMetadataContainer); |
curMetadataContainer = NULL; |
} |
} |
} |
} |
// update/save |
if (success == YES){ |
//save the qt movie (just updating an existing file) |
if (saveOperation == NSSaveOperation) |
success = [theMovie updateMovieFile]; |
else{ |
//save off the movieDoc (not this doc which is an NSPersistent doc of the metadata hierarchy tree, but instead the QT movie). |
// this is a save as operation |
MovieDocument* newMovieDoc = [MovieDocument movieDocumentWithMovie:theMovie]; |
success = [newMovieDoc writeWithBackupToFile:fullDocumentPath ofType:type saveOperation:saveOperation]; |
//update our runtime database to match the saved off file rather than be un-saved. |
if (success== YES){ |
NSError * readErr = NULL; |
NSURL *fileURL = [NSURL fileURLWithPath:fullDocumentPath]; |
//clear out the file so then we get our displayName refreshed when we revert the file to the newly saved as one. |
[self setFileURL:fileURL]; |
[theMovie release]; |
//revert to cleanup managedObjectContext and just get what we pushed down to the file, and to read in metadata back into UI with the new file name (at the top level). |
[self revertToContentsOfURL:fileURL ofType:type error:&readErr]; //Overridden to clean up the managedObjectContext and controllers during a revert. |
} |
} |
} |
return (success); |
} |
@end |
//======================================== |
# pragma mark utilites |
// metadata utility functions |
//---------------------------------------- |
// get the key for a bit of metadata on the QT movie |
NSString * LocalMetaDataGetItemKey(QTMetaDataRef metadataContainer, QTMetaDataItem metadataItem) |
{ |
// lcoal shared variables |
ByteCount propValueSize = 0, propValueSizeUsed = 0; |
QTPropertyValueType propType = kQTMetaDataTypeBinary; |
QTPropertyValuePtr propValuePtr = NULL; |
NSString *finalString = NULL; |
// check arguments |
if (metadataContainer == NULL || metadataItem == kQTMetaDataItemUninitialized) |
return (NULL); |
// retrieve the key (FUNCTION) |
if (QTMetaDataGetItemPropertyInfo(metadataContainer, metadataItem, kPropertyClass_MetaDataItem, |
kQTMetaDataItemPropertyID_Key, &propType, &propValueSize, NULL) == noErr && propType == 'cbuf') |
{ |
// alloc a buffer |
if ((propValuePtr = malloc(propValueSize+4)) != NULL) |
{ |
// clear the pointer (just to be safe) |
memset(propValuePtr, 0, (propValueSize+4)); |
// retrieve the key, and create an NSString from it |
if (QTMetaDataGetItemProperty(metadataContainer, metadataItem, kPropertyClass_MetaDataItem, |
kQTMetaDataItemPropertyID_Key, propValueSize, propValuePtr, &propValueSizeUsed) == noErr) |
finalString = [NSString stringWithCString:propValuePtr encoding:NSUTF8StringEncoding]; |
// cleanup |
free(propValuePtr); |
propValuePtr = NULL; |
} |
} |
// return final string object |
return (finalString); |
} // LocalMetaDataGetItemKey |
//---------------------------------------- |
// get the value for a bit of metadata on the QT movie |
id LocalMetaDataGetItemValue(QTMetaDataRef metadataContainer, QTMetaDataItem metadataItem, UInt32 *pKeyDataType) |
{ |
// lcoal shared variables |
ByteCount propValueSize = 0, propValueSizeUsed = 0; |
QTPropertyValueType propType = kQTMetaDataTypeBinary; |
QTPropertyValuePtr propValuePtr = NULL; |
UInt32 keyDataType = 0; |
id finalValueObject = NULL; |
OSErr qtErr = noErr; |
// check arguments |
if (metadataContainer == NULL || metadataItem == kQTMetaDataItemUninitialized) |
return (NULL); |
// retrieve the data type for the value |
if ((qtErr = QTMetaDataGetItemPropertyInfo(metadataContainer, metadataItem, kPropertyClass_MetaDataItem, |
kQTMetaDataItemPropertyID_DataType, &propType, &propValueSize, NULL)) == noErr && propType == 'ui32') |
if ((qtErr = QTMetaDataGetItemProperty(metadataContainer, metadataItem, kPropertyClass_MetaDataItem, |
kQTMetaDataItemPropertyID_DataType, sizeof(keyDataType), &keyDataType, &propValueSizeUsed)) == noErr) |
keyDataType = keyDataType; |
// retrieve the actual value payload |
if (qtErr == noErr) |
if ((qtErr = QTMetaDataGetItemPropertyInfo(metadataContainer, metadataItem, kPropertyClass_MetaDataItem, |
kQTMetaDataItemPropertyID_Value, &propType, &propValueSize, NULL)) == noErr) |
{ |
// alloc a buffer |
if ((propValuePtr = malloc(propValueSize+4)) != NULL) |
{ |
// clear the pointer (just to be safe) |
memset(propValuePtr, 0, (propValueSize+4)); |
// retrieve the value |
if ((qtErr = QTMetaDataGetItemProperty(metadataContainer, metadataItem, kPropertyClass_MetaDataItem, |
kQTMetaDataItemPropertyID_Value, propValueSize, propValuePtr, &propValueSizeUsed)) == noErr) |
switch (keyDataType) |
{ |
case kQTMetaDataTypeJPEGImage: |
case kQTMetaDataTypePNGImage: |
case kQTMetaDataTypeBMPImage: |
case kQTMetaDataTypeBinary: |
finalValueObject = [NSData dataWithBytes:propValuePtr length:propValueSize]; |
break; |
case kQTMetaDataTypeUTF8: |
finalValueObject = [NSString stringWithCString:propValuePtr encoding:NSUTF8StringEncoding]; |
break; |
case kQTMetaDataTypeUTF16BE: |
finalValueObject = [NSString stringWithCString:propValuePtr encoding:NSUnicodeStringEncoding]; |
break; |
case kQTMetaDataTypeMacEncodedText: |
finalValueObject = [NSString stringWithCString:propValuePtr encoding:NSMacOSRomanStringEncoding]; |
break; |
case kQTMetaDataTypeSignedIntegerBE: |
if (propValueSize == sizeof(short)) |
finalValueObject = [NSNumber numberWithShort:(NSSwapBigShortToHost(*((short *)propValuePtr)))]; |
else if (propValueSize == sizeof(long)) |
finalValueObject = [NSNumber numberWithLong:(NSSwapBigLongToHost(*((long *)propValuePtr)))]; |
else if (propValueSize == sizeof(int)) |
finalValueObject = [NSNumber numberWithLong:(NSSwapBigIntToHost(*((int *)propValuePtr)))]; |
break; |
case kQTMetaDataTypeUnsignedIntegerBE: |
if (propValueSize == sizeof(unsigned short)) |
finalValueObject = [NSNumber numberWithUnsignedShort:(NSSwapBigShortToHost(*((unsigned short *)propValuePtr)))]; |
else if (propValueSize == sizeof(unsigned long)) |
finalValueObject = [NSNumber numberWithUnsignedLong:(NSSwapBigLongToHost(*((unsigned long *)propValuePtr)))]; |
else if (propValueSize == sizeof(unsigned int)) |
finalValueObject = [NSNumber numberWithLong:(NSSwapBigIntToHost(*((unsigned int *)propValuePtr)))]; |
break; |
case kQTMetaDataTypeFloat32BE: |
finalValueObject = [NSNumber numberWithFloat:(*((float *)propValuePtr))]; |
break; |
case kQTMetaDataTypeFloat64BE: |
finalValueObject = [NSNumber numberWithDouble:(*((double *)propValuePtr))]; |
break; |
// NOTE: in addition to the other data types, deal with containers! |
default: |
NSLog (@"(unable to read value for type: %u, storing as binary)", keyDataType); |
finalValueObject = [NSData dataWithBytes:propValuePtr length:propValueSize]; |
break; |
} |
// cleanup |
free(propValuePtr); |
propValuePtr = NULL; |
} |
} |
// retain and return the data type |
if (finalValueObject && pKeyDataType != NULL) |
(*pKeyDataType) = keyDataType; |
// return pointer |
return (finalValueObject); |
} // LocalMetaDataGetItemValue |
//-------------------------------------------------------------------------------- |
// empty any metadata containers found on the specified movie |
BOOL LocalEmptyMovieMetadataContainers( Movie theMovie ) |
{ |
// locals |
BOOL success = YES; |
QTMetaDataRef curMetadataContainer = NULL; |
long trackCounter, trackTotal = 0; |
Track curTrack = nil; |
// enumerate container contents on the movie |
QTCopyMovieMetaData(theMovie, &curMetadataContainer); |
if (curMetadataContainer != NULL) |
{ |
// clear supported entries |
success = LocalRemoveSupportedMetadataElements(curMetadataContainer); |
// release and clear metadata container reference |
QTMetaDataRelease(curMetadataContainer); |
curMetadataContainer = NULL; |
} |
// enumerate tracks and media |
trackTotal = GetMovieTrackCount(theMovie); |
for (trackCounter = 1; trackCounter<=trackTotal && success==YES; trackCounter++) |
if ((curTrack = GetMovieIndTrack(theMovie, trackCounter)) != nil) |
{ |
// locals |
Media curMedia = nil; |
// go for track metadata |
curMetadataContainer = NULL; |
QTCopyTrackMetaData(curTrack, &curMetadataContainer); |
if (curMetadataContainer != NULL) |
{ |
// clear supported entries |
success = LocalRemoveSupportedMetadataElements(curMetadataContainer); |
// release and clear metadata container reference |
QTMetaDataRelease(curMetadataContainer); |
curMetadataContainer = NULL; |
} |
// go for media metadata |
if ((curMedia = GetTrackMedia(curTrack)) != NULL) |
QTCopyMediaMetaData(curMedia, &curMetadataContainer); |
if (curMetadataContainer != NULL) |
{ |
// clear supported entries |
success = LocalRemoveSupportedMetadataElements(curMetadataContainer); |
// release and clear metadata container reference |
QTMetaDataRelease(curMetadataContainer); |
curMetadataContainer = NULL; |
} |
} |
// cleanup and return |
return (success); |
} // LocalEmptyMovieMetadataContainers |
//-------------------------------------------------------------------------------- |
// check to see if we have metadata in a container that we care about |
BOOL LocalRemoveSupportedMetadataElements( QTMetaDataRef metadataContainer ) |
{ |
// locals |
QTMetaDataItem curMetadataItem = kQTMetaDataItemUninitialized; |
// see if we have any items present |
if (QTMetaDataGetNextItem(metadataContainer, kQTMetaDataStorageFormatQuickTime, |
kQTMetaDataItemUninitialized, kQTMetaDataKeyFormatQuickTime, NULL, 0, &curMetadataItem) == noErr) |
{ |
// while we have entries AND we haven't found one that is appropriate, keep on enumerating |
while (curMetadataItem != kQTMetaDataItemUninitialized) |
{ |
QTMetaDataItem elementToRemove = kQTMetaDataItemUninitialized; |
ByteCount propValueSize = 0, propValueSizeUsed = 0; |
QTPropertyValueType propType = kQTMetaDataTypeBinary; |
UInt32 keyFormat = 0; |
// confirm this item uses the quicktime metadata format (reverse dns) |
if (QTMetaDataGetItemPropertyInfo(metadataContainer, curMetadataItem, kPropertyClass_MetaDataItem, |
kQTMetaDataItemPropertyID_KeyFormat, &propType, &propValueSize, NULL) == noErr && propValueSize == sizeof(keyFormat)) |
if (QTMetaDataGetItemProperty(metadataContainer, curMetadataItem, kPropertyClass_MetaDataItem, |
kQTMetaDataItemPropertyID_KeyFormat, propValueSize, &keyFormat, &propValueSizeUsed) == noErr) |
if (keyFormat == kQTMetaDataKeyFormatQuickTime) // NOTE: QuickTime handles endian swapping for values retrieved via the properties API |
elementToRemove = curMetadataItem; |
// get the next element |
if (QTMetaDataGetNextItem(metadataContainer, kQTMetaDataStorageFormatQuickTime, |
curMetadataItem, kQTMetaDataKeyFormatQuickTime, NULL, 0, &curMetadataItem) != noErr) |
curMetadataItem = kQTMetaDataItemUninitialized; |
// if the previous element met our criteria, remove it |
if (elementToRemove != kQTMetaDataItemUninitialized) |
QTMetaDataRemoveItem(metadataContainer, elementToRemove); |
} |
} |
// return success |
return (YES); |
} // LocalRemoveSupportedMetadataElements |
Copyright © 2010 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2010-05-27