AVFoundationExporter: Version 3.0, 2016-09-13

Converted to Swift 2.3.

The AVFoundationExporter sample demonstrates how to export and transcode movie files, add metadata to movie files, and filter existing metadata in movie files using the AVAssetExportSession class.
This commit is contained in:
Liu Lantao 2016-12-24 12:03:34 +08:00
parent b05769f6be
commit ec9471a469
7 changed files with 1587 additions and 0 deletions

View File

@ -0,0 +1,42 @@
Sample code project: AVFoundationExporter: Exporting and Transcoding Movies
Version: 3.0
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) 2016 Apple Inc. All Rights Reserved.

View File

@ -0,0 +1,237 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1E1D58BC1368D74F00D93743 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1D58BB1368D74F00D93743 /* Foundation.framework */; };
1E1D58BF1368D74F00D93743 /* AVFoundationExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E1D58BE1368D74F00D93743 /* AVFoundationExporter.m */; };
1E1D58CF1368D7E600D93743 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1D58CD1368D7E600D93743 /* CoreMedia.framework */; };
1E1D58D01368D7E600D93743 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E1D58CE1368D7E600D93743 /* AVFoundation.framework */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
1E1D58B51368D74F00D93743 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1E1D58B71368D74F00D93743 /* AVFoundationExporter */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = AVFoundationExporter; sourceTree = BUILT_PRODUCTS_DIR; };
1E1D58BB1368D74F00D93743 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
1E1D58BE1368D74F00D93743 /* AVFoundationExporter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AVFoundationExporter.m; sourceTree = "<group>"; };
1E1D58CD1368D7E600D93743 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = "<group>"; };
1E1D58CE1368D7E600D93743 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = "<group>"; };
3EAA11C51B1B895500EC0006 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
1E1D58B41368D74F00D93743 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1E1D58BC1368D74F00D93743 /* Foundation.framework in Frameworks */,
1E1D58CF1368D7E600D93743 /* CoreMedia.framework in Frameworks */,
1E1D58D01368D7E600D93743 /* AVFoundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1E1D58AC1368D74F00D93743 = {
isa = PBXGroup;
children = (
3EAA11C51B1B895500EC0006 /* README.md */,
1E1D58BD1368D74F00D93743 /* AVFoundationExporter */,
1E1D58BA1368D74F00D93743 /* Frameworks */,
1E1D58B81368D74F00D93743 /* Products */,
);
sourceTree = "<group>";
};
1E1D58B81368D74F00D93743 /* Products */ = {
isa = PBXGroup;
children = (
1E1D58B71368D74F00D93743 /* AVFoundationExporter */,
);
name = Products;
sourceTree = "<group>";
};
1E1D58BA1368D74F00D93743 /* Frameworks */ = {
isa = PBXGroup;
children = (
1E1D58BB1368D74F00D93743 /* Foundation.framework */,
1E1D58CD1368D7E600D93743 /* CoreMedia.framework */,
1E1D58CE1368D7E600D93743 /* AVFoundation.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
1E1D58BD1368D74F00D93743 /* AVFoundationExporter */ = {
isa = PBXGroup;
children = (
1E1D58BE1368D74F00D93743 /* AVFoundationExporter.m */,
);
path = AVFoundationExporter;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
1E1D58B61368D74F00D93743 /* AVFoundationExporter */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1E1D58C61368D74F00D93743 /* Build configuration list for PBXNativeTarget "AVFoundationExporter" */;
buildPhases = (
1E1D58B31368D74F00D93743 /* Sources */,
1E1D58B41368D74F00D93743 /* Frameworks */,
1E1D58B51368D74F00D93743 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = AVFoundationExporter;
productName = AVFoundationExporter;
productReference = 1E1D58B71368D74F00D93743 /* AVFoundationExporter */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
1E1D58AE1368D74F00D93743 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0700;
ORGANIZATIONNAME = "Apple, Inc";
};
buildConfigurationList = 1E1D58B11368D74F00D93743 /* Build configuration list for PBXProject "AVFoundationExporter" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 1E1D58AC1368D74F00D93743;
productRefGroup = 1E1D58B81368D74F00D93743 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
1E1D58B61368D74F00D93743 /* AVFoundationExporter */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
1E1D58B31368D74F00D93743 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1E1D58BF1368D74F00D93743 /* AVFoundationExporter.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
1E1D58C41368D74F00D93743 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_VERSION = "";
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.7;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
};
1E1D58C51368D74F00D93743 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_VERSION = "";
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.7;
SDKROOT = macosx;
};
name = Release;
};
1E1D58C71368D74F00D93743 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ENABLE_MODULES = YES;
COPY_PHASE_STRIP = NO;
DEFINES_MODULE = NO;
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
GCC_PREFIX_HEADER = "";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = "";
};
name = Debug;
};
1E1D58C81368D74F00D93743 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ENABLE_MODULES = YES;
COPY_PHASE_STRIP = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = NO;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
GCC_PREFIX_HEADER = "";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1E1D58B11368D74F00D93743 /* Build configuration list for PBXProject "AVFoundationExporter" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1E1D58C41368D74F00D93743 /* Debug */,
1E1D58C51368D74F00D93743 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
1E1D58C61368D74F00D93743 /* Build configuration list for PBXNativeTarget "AVFoundationExporter" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1E1D58C71368D74F00D93743 /* Debug */,
1E1D58C81368D74F00D93743 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 1E1D58AE1368D74F00D93743 /* Project object */;
}

View File

@ -0,0 +1,534 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
Abstract:
This file shows an example of using the export and metadata functions in AVFoundation as a part of a command line tool for simple exports.
*/
@import Foundation;
@import AVFoundation;
// ---------------------------------------------------------------------------
// Convenience Functions
// ---------------------------------------------------------------------------
static void printNSString(NSString *string);
static void printArgs(int argc, const char **argv);
// ---------------------------------------------------------------------------
// AAPLExporter Class Interface
// ---------------------------------------------------------------------------
@interface AAPLExporter: NSObject {
NSString *programName;
NSString *exportType;
NSString *preset;
NSString *sourcePath;
NSString *destinationPath;
NSString *fileType;
NSNumber *progress;
NSNumber *startSeconds;
NSNumber *durationSeconds;
BOOL showProgress;
BOOL verbose;
BOOL exportFailed;
BOOL exportComplete;
BOOL listTracks;
BOOL listMetadata;
BOOL removePreExistingFiles;
}
@property (copy) NSString *programName;
@property (copy) NSString *exportType;
@property (copy) NSString *preset;
@property (copy) NSString *sourcePath;
@property (copy) NSString *destinationPath;
@property (copy) NSString *fileType;
@property (strong) NSNumber *progress;
@property (strong) NSNumber *startSeconds;
@property (strong) NSNumber *durationSeconds;
@property (getter=isVerbose) BOOL verbose;
@property BOOL showProgress;
@property BOOL exportFailed;
@property BOOL exportComplete;
@property BOOL listTracks;
@property BOOL listMetadata;
@property BOOL removePreExistingFiles;
- (id)initWithArgs:(int)argc argv:(const char **)argv environ:(const char **)environ;
- (void)printUsage;
- (int)run;
- (NSArray *)addNewMetadata:(NSArray *)sourceMetadataList presetName:(NSString *)presetName;
+ (void)doListPresets;
- (void)doListTracks:(NSString *)assetPath;
- (void)doListMetadata:(NSString *)assetPath;
@end
// ---------------------------------------------------------------------------
// AAPLExporter Class Implementation
// ---------------------------------------------------------------------------
@implementation AAPLExporter
@synthesize programName, exportType, preset;
@synthesize sourcePath, destinationPath, progress, fileType;
@synthesize startSeconds, durationSeconds;
@synthesize verbose, showProgress, exportComplete, exportFailed;
@synthesize listTracks, listMetadata;
@synthesize removePreExistingFiles;
-(id) initWithArgs: (int) argc argv: (const char **) argv environ: (const char **) environ
{
self = [super init];
if (self == nil) {
return nil;
}
printArgs(argc,argv);
BOOL gotpreset = NO;
BOOL gotsource = NO;
BOOL gotout = NO;
BOOL parseOK = NO;
BOOL listPresets = NO;
[self setProgramName:[NSString stringWithUTF8String: *argv++]];
argc--;
while ( argc > 0 && **argv == '-' )
{
const char* args = &(*argv)[1];
argc--;
argv++;
if ( ! strcmp ( args, "source" ) )
{
[self setSourcePath: [NSString stringWithUTF8String: *argv++] ];
gotsource = YES;
argc--;
}
else if (( ! strcmp ( args, "dest" )) || ( ! strcmp ( args, "destination" )) )
{
[self setDestinationPath: [NSString stringWithUTF8String: *argv++]];
gotout = YES;
argc--;
}
else if ( ! strcmp ( args, "preset" ) )
{
[self setPreset: [NSString stringWithUTF8String: *argv++]];
gotpreset = YES;
argc--;
}
else if ( ! strcmp ( args, "replace" ) )
{
[self setRemovePreExistingFiles: YES];
}
else if ( ! strcmp ( args, "filetype" ) )
{
[self setFileType: [NSString stringWithUTF8String: *argv++]];
argc--;
}
else if ( ! strcmp ( args, "verbose" ) )
{
[self setVerbose:YES];
}
else if ( ! strcmp ( args, "progress" ) )
{
[self setShowProgress: YES];
}
else if ( ! strcmp ( args, "start" ) )
{
[self setStartSeconds: [NSNumber numberWithFloat:[[NSString stringWithUTF8String: *argv++] floatValue]]];
argc--;
}
else if ( ! strcmp ( args, "duration" ) )
{
[self setDurationSeconds: [NSNumber numberWithFloat:[[NSString stringWithUTF8String: *argv++] floatValue]]];
argc--;
}
else if ( ! strcmp ( args, "listpresets" ) )
{
listPresets = YES;
parseOK = YES;
}
else if ( ! strcmp ( args, "listtracks" ) )
{
[self setListTracks: YES];
parseOK = YES;
}
else if ( ! strcmp ( args, "listmetadata" ) )
{
[self setListMetadata: YES];
parseOK = YES;
}
else if ( ! strcmp ( args, "help" ) )
{
[self printUsage];
}
else {
printf("Invalid input parameter: %s\n", args );
[self printUsage];
return nil;
}
}
[self setProgress: [NSNumber numberWithFloat:(float)0.0]];
[self setExportFailed: NO];
[self setExportComplete: NO];
if (listPresets) {
[AAPLExporter doListPresets];
}
if ([self isVerbose]) {
printNSString([NSString stringWithFormat:@"Running: %@\n", [self programName]]);
}
// There must be a source and either a preset and output (the normal case) or parseOK set for a listing
if ((gotsource == NO) || ((parseOK == NO) && ((gotpreset == NO) || (gotout == NO)))) {
[self printUsage];
return nil;
}
return self;
}
-(void) printUsage
{
printf("AVFoundationExporter - usage:\n");
printf(" ./AVFoundationExporter [-parameter <value> ...]\n");
printf(" parameters are all preceded by a -<parameterName>. The order of the parameters is unimportant.\n");
printf(" Required parameters are -preset <presetName> -source <sourceFileURL> -dest <outputFileURL>\n");
printf(" Source and destination URL strings cannot contain spaces.\n");
printf(" Available parameters are:\n");
printf(" -preset <preset name>. The preset name eg: AVAssetExportPreset640x480 AVAssetExportPresetAppleM4VWiFi. Use -listpresets to see a full list.\n");
printf(" -destination (or -dest) <outputFileURL>\n");
printf(" -source <sourceMovieURL>\n");
printf(" -replace If there is a preexisting file at the destination location, remove it before exporting.");
printf(" -filetype <file type string> The file type (eg com.apple.m4v-video) for the output file. If not specified, the first supported type will be used.\n");
printf(" -start <start time> time in seconds (decimal are OK). Removes the startClip time from the beginning of the movie before exporting.\n");
printf(" -duration <duration> time in seconds (decimal are OK). Trims the movie to this duration before exporting. \n");
printf(" Also available are some setup options:\n");
printf(" -verbose Print more information about the execution.\n");
printf(" -progress Show progress information.\n");
printf(" -listpresets For sourceMovieURL sources only, lists the tracks in the source movie before the export. \n");
printf(" -listtracks For sourceMovieURL sources only, lists the tracks in the source movie before the export. \n");
printf(" Always lists the tracks in the destination asset at the end of the export.\n");
printf(" -listmetadata Lists the metadata in the source movie before the export. \n");
printf(" Also lists the metadata in the destination asset at the end of the export.\n");
printf(" Sample export lines:\n");
printf(" ./AVFoundationExporter -dest /tmp/testOut.m4v -replace -preset AVAssetExportPresetAppleM4ViPod -listmetadata -source /path/to/myTestMovie.m4v\n");
printf(" ./AVFoundationExporter -destination /tmp/testOut.mov -preset AVAssetExportPreset640x480 -listmetadata -listtracks -source /path/to/myTestMovie.mov\n");
}
static dispatch_time_t getDispatchTimeFromSeconds(float seconds) {
long long milliseconds = seconds * 1000.0;
dispatch_time_t waitTime = dispatch_time( DISPATCH_TIME_NOW, 1000000LL * milliseconds );
return waitTime;
}
- (int)run
{
NSURL *sourceURL = nil;
AVAssetExportSession *avsession = nil;
NSURL *destinationURL = nil;
BOOL success = YES;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSParameterAssert( [self sourcePath] != nil );
if ([self listTracks] && [self sourcePath]) {
[self doListTracks:[self sourcePath]];
}
if ([self listMetadata] && [self sourcePath]) {
[self doListMetadata:[self sourcePath]];
}
if ([self destinationPath] == nil) {
NSLog(@"No output path specified, only listing tracks and/or metadata, export was not performed.");
goto bail;
}
if ([self preset] == nil) {
NSLog(@"No preset specified, only listing tracks and/or metadata, export was not performed.");
goto bail;
}
if ( [self isVerbose] && [self sourcePath] ) {
printNSString([NSString stringWithFormat:@"all av asset presets:%@", [AVAssetExportSession allExportPresets]]);
}
if ([self sourcePath] != nil) {
sourceURL = [[NSURL fileURLWithPath: [self sourcePath] isDirectory: NO] retain];
}
AVAsset *sourceAsset = nil;
NSError* error = nil;
if ([self isVerbose]) {
printNSString([NSString stringWithFormat:@"AVAssetExport for preset:%@ to with source:%@", [self preset], [destinationURL path]]);
}
destinationURL = [NSURL fileURLWithPath: [self destinationPath] isDirectory: NO];
if ([self removePreExistingFiles] && [[NSFileManager defaultManager] fileExistsAtPath:[self destinationPath]]) {
if ([self isVerbose]) {
printNSString([NSString stringWithFormat:@"Removing re-existing destination file at:%@", destinationURL]);
}
[[NSFileManager defaultManager] removeItemAtURL:destinationURL error:&error];
}
sourceAsset = [[[AVURLAsset alloc] initWithURL:sourceURL options:nil] autorelease];
if ([self isVerbose]) {
printNSString([NSString stringWithFormat:@"Compatible av asset presets:%@", [AVAssetExportSession exportPresetsCompatibleWithAsset:sourceAsset]]);
}
avsession = [[AVAssetExportSession alloc] initWithAsset:sourceAsset presetName:[self preset]];
[avsession setOutputURL:destinationURL];
if ([self fileType] != nil) {
[avsession setOutputFileType:[self fileType]];
}
else {
[avsession setOutputFileType:[[avsession supportedFileTypes] objectAtIndex:0]];
}
if ([self isVerbose]) {
printNSString([NSString stringWithFormat:@"Created AVAssetExportSession: %p", avsession]);
printNSString([NSString stringWithFormat:@"presetName:%@", [avsession presetName]]);
printNSString([NSString stringWithFormat:@"source URL:%@", [sourceURL path]]);
printNSString([NSString stringWithFormat:@"destination URL:%@", [[avsession outputURL] path]]);
printNSString([NSString stringWithFormat:@"output file type:%@", [avsession outputFileType]]);
}
// Add a metadata item to indicate how thie destination file was created.
NSArray *sourceMetadataList = [avsession metadata];
sourceMetadataList = [self addNewMetadata: sourceMetadataList presetName:[self preset]];
[avsession setMetadata:sourceMetadataList];
// Set up the time range
CMTime startTime = kCMTimeZero;
CMTime durationTime = kCMTimePositiveInfinity;
if ([self startSeconds] != nil) {
startTime = CMTimeMake([[self startSeconds] floatValue] * 1000, 1000);
}
if ([self durationSeconds] != nil) {
durationTime = CMTimeMake([[self durationSeconds] floatValue] * 1000, 1000);
}
CMTimeRange exportTimeRange = CMTimeRangeMake(startTime, durationTime);
[avsession setTimeRange:exportTimeRange];
// start a fresh pool for the export.
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
// Set up a semaphore for the completion handler and progress timer
dispatch_semaphore_t sessionWaitSemaphore = dispatch_semaphore_create( 0 );
void (^completionHandler)(void) = ^(void)
{
dispatch_semaphore_signal(sessionWaitSemaphore);
};
// do it.
[avsession exportAsynchronouslyWithCompletionHandler:completionHandler];
do {
dispatch_time_t dispatchTime = DISPATCH_TIME_FOREVER; // if we dont want progress, we will wait until it finishes.
if ([self showProgress]) {
dispatchTime = getDispatchTimeFromSeconds((float)1.0);
printNSString([NSString stringWithFormat:@"AVAssetExport running progress=%3.2f%%", [avsession progress]*100]);
}
dispatch_semaphore_wait(sessionWaitSemaphore, dispatchTime);
} while( [avsession status] < AVAssetExportSessionStatusCompleted );
if ([self showProgress]) {
printNSString([NSString stringWithFormat:@"AVAssetExport finished progress=%3.2f", [avsession progress]*100]);
}
[avsession release];
avsession = nil;
if ([self listMetadata] && [self destinationPath]) {
[self doListMetadata:[self destinationPath]];
}
if ([self listTracks] && [self destinationPath]) {
[self doListTracks:[self destinationPath]];
}
printNSString([NSString stringWithFormat:@"Finished export of %@ to %@ using preset:%@ success=%s\n", [self sourcePath], [self destinationPath], [self preset], (success ? "YES" : "NO")]);
bail:
[sourceURL release];
[pool drain];
return success;
}
- (NSArray *) addNewMetadata: (NSArray *)sourceMetadataList presetName:(NSString *)presetName
{
// This method creates a few new metadata items in different keySpaces to be inserted into the exported file along with the metadata that
// was in the original source.
// Depending on the output file format, not all of these items will be valid and not all of them will come through to the destination.
AVMutableMetadataItem *newUserDataCommentItem = [[[AVMutableMetadataItem alloc] init] autorelease];
[newUserDataCommentItem setKeySpace:AVMetadataKeySpaceQuickTimeUserData];
[newUserDataCommentItem setKey:AVMetadataQuickTimeUserDataKeyComment];
[newUserDataCommentItem setValue:[NSString stringWithFormat:@"QuickTime userdata: Exported to preset %@ using AVFoundationExporter at: %@", presetName,
[NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterMediumStyle timeStyle: NSDateFormatterShortStyle]]];
AVMutableMetadataItem *newMetaDataCommentItem = [[[AVMutableMetadataItem alloc] init] autorelease];
[newMetaDataCommentItem setKeySpace:AVMetadataKeySpaceQuickTimeMetadata];
[newMetaDataCommentItem setKey:AVMetadataQuickTimeMetadataKeyComment];
[newMetaDataCommentItem setValue:[NSString stringWithFormat:@"QuickTime metadata: Exported to preset %@ using AVFoundationExporter at: %@", presetName,
[NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterMediumStyle timeStyle: NSDateFormatterShortStyle]]];
AVMutableMetadataItem *newiTunesCommentItem = [[[AVMutableMetadataItem alloc] init] autorelease];
[newiTunesCommentItem setKeySpace:AVMetadataKeySpaceiTunes];
[newiTunesCommentItem setKey:AVMetadataiTunesMetadataKeyUserComment];
[newiTunesCommentItem setValue:[NSString stringWithFormat:@"iTunes metadata: Exported to preset %@ using AVFoundationExporter at: %@", presetName,
[NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterMediumStyle timeStyle: NSDateFormatterShortStyle]]];
NSArray *newMetadata = [NSArray arrayWithObjects:newUserDataCommentItem, newMetaDataCommentItem, newiTunesCommentItem, nil];
NSArray *newMetadataList = (sourceMetadataList == nil ? newMetadata : [sourceMetadataList arrayByAddingObjectsFromArray:newMetadata]);
return newMetadataList;
}
+ (void) doListPresets
{
// A simple listing of the presets available for export
printNSString(@"");
printNSString(@"Presets available for AVFoundation export:");
printNSString(@" QuickTime movie presets:");
printNSString([NSString stringWithFormat:@" %@", AVAssetExportPreset640x480]);
printNSString([NSString stringWithFormat:@" %@", AVAssetExportPreset960x540]);
printNSString([NSString stringWithFormat:@" %@", AVAssetExportPreset1280x720]);
printNSString([NSString stringWithFormat:@" %@", AVAssetExportPreset1920x1080]);
printNSString(@" Audio only preset:");
printNSString([NSString stringWithFormat:@" %@", AVAssetExportPresetAppleM4A]);
printNSString(@" Apple device presets:");
printNSString([NSString stringWithFormat:@" %@", AVAssetExportPresetAppleM4VCellular]);
printNSString([NSString stringWithFormat:@" %@", AVAssetExportPresetAppleM4ViPod]);
printNSString([NSString stringWithFormat:@" %@", AVAssetExportPresetAppleM4V480pSD]);
printNSString([NSString stringWithFormat:@" %@", AVAssetExportPresetAppleM4VAppleTV]);
printNSString([NSString stringWithFormat:@" %@", AVAssetExportPresetAppleM4VWiFi]);
printNSString([NSString stringWithFormat:@" %@", AVAssetExportPresetAppleM4V720pHD]);
printNSString(@" Interim format (QuickTime movie) preset:");
printNSString([NSString stringWithFormat:@" %@", AVAssetExportPresetAppleProRes422LPCM]);
printNSString(@" Passthrough preset:");
printNSString([NSString stringWithFormat:@" %@", AVAssetExportPresetPassthrough]);
printNSString(@"");
}
- (void)doListTracks:(NSString *)assetPath
{
// A simple listing of the tracks in the asset provided
NSURL *sourceURL = [NSURL fileURLWithPath: assetPath isDirectory: NO];
if (sourceURL) {
AVURLAsset *sourceAsset = [[[AVURLAsset alloc] initWithURL:sourceURL options:nil] autorelease];
printNSString([NSString stringWithFormat:@"Listing tracks for asset from url:%@", [sourceURL path]]);
NSInteger index = 0;
for (AVAssetTrack *track in [sourceAsset tracks]) {
[track retain];
printNSString([ NSString stringWithFormat:@" Track index:%ld, trackID:%d, mediaType:%@, enabled:%d, isSelfContained:%d", index, [track trackID], [track mediaType], [track isEnabled], [track isSelfContained] ] );
index++;
[track release];
}
}
}
enum {
kMaxMetadataValueLength = 80,
};
- (void)doListMetadata:(NSString *)assetPath
{
// A simple listing of the metadata in the asset provided
NSURL *sourceURL = [NSURL fileURLWithPath: assetPath isDirectory: NO];
if (sourceURL) {
AVURLAsset *sourceAsset = [[[AVURLAsset alloc] initWithURL:sourceURL options:nil] autorelease];
NSLog(@"Listing metadata for asset from url:%@", [sourceURL path]);
for (NSString *format in [sourceAsset availableMetadataFormats]) {
NSLog(@"Metadata for format:%@", format);
for (AVMetadataItem *item in [sourceAsset metadataForFormat:format]) {
NSObject *key = [item key];
NSString *itemValue = [[item value] description];
if ([itemValue length] > kMaxMetadataValueLength) {
itemValue = [NSString stringWithFormat:@"%@ ...", [itemValue substringToIndex:kMaxMetadataValueLength-4]];
}
if ([key isKindOfClass: [NSNumber class]]) {
NSInteger longValue = [(NSNumber *)key longValue];
char *charSource = (char *)&longValue;
char charValue[5] = {0};
charValue[0] = charSource[3];
charValue[1] = charSource[2];
charValue[2] = charSource[1];
charValue[3] = charSource[0];
NSString *stringKey = [[[NSString alloc] initWithBytes: charValue length:4 encoding:NSMacOSRomanStringEncoding] autorelease];
printNSString([NSString stringWithFormat:@" metadata item key:%@ (%ld), keySpace:%@ commonKey:%@ value:%@", stringKey, longValue, [item keySpace], [item commonKey], itemValue]);
}
else {
printNSString([NSString stringWithFormat:@" metadata item key:%@, keySpace:%@ commonKey:%@ value:%@", [item key], [item keySpace], [item commonKey], itemValue]);
}
}
}
}
}
@end
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main (int argc, const char * argv[], const char* environ[])
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
AAPLExporter* exportObj = [[AAPLExporter alloc] initWithArgs:argc argv:argv environ:environ];
BOOL success;
if (exportObj)
success = [exportObj run];
else {
success = NO;
}
[exportObj release];
[pool release];
return ((success == YES) ? 0 : -1);
}
// ---------------------------------------------------------------------------
// printNSString
// ---------------------------------------------------------------------------
static void printNSString(NSString *string)
{
printf("%s\n", [string cStringUsingEncoding:NSUTF8StringEncoding]);
}
// ---------------------------------------------------------------------------
// printArgs
// ---------------------------------------------------------------------------
static void printArgs(int argc, const char **argv)
{
int i;
for( i = 0; i < argc; i++ )
printf("%s ", argv[i]);
printf("\n");
}

View File

@ -0,0 +1,39 @@
# AVFoundationExporter
## Description
Demonstrates use of AVFoundation export APIs with a simple command line utility. The command line application will list some information about the asset, transcode the asset in accord with one of the AVAssetExportSession presets, and demonstrates simple manipulation of the metadata that is exported with the source.
## Build Requirements
Xcode 8.0, macOS 10.12
## Runtime Requirements
OS X 10.11
## Structure
The main files associates with this project are:
Objective-C Version:
Source file: AVFoundationExporter.m
Project bundle: Objective-C/AVFoundationExporter.xcodeproj
Swift Version:
Source files: main.swift, ArgumentParsing.swift
Project bundle: Swift/AVFoundationExporter.xcodeproj
## Changes
Version 1.0
- First version.
Version 2.0
- Add Swift version.
Version 3.0
- Updated project for Swift 2.3.
Copyright (C) 2015, 2016 Apple Inc. All rights reserved.

View File

@ -0,0 +1,261 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
004967D71AE9751900B10C98 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004967D61AE9751900B10C98 /* main.swift */; };
00599BBF1B1CFCC20093572A /* ArgumentParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00599BBE1B1CFCC20093572A /* ArgumentParsing.swift */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
004967CA1AE974A600B10C98 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
004967CC1AE974A600B10C98 /* AVFoundationExporter */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = AVFoundationExporter; sourceTree = BUILT_PRODUCTS_DIR; };
004967D61AE9751900B10C98 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
00599BBE1B1CFCC20093572A /* ArgumentParsing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArgumentParsing.swift; sourceTree = "<group>"; };
3EAA11C31B1B894900EC0006 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
004967C91AE974A600B10C98 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
004967C31AE974A600B10C98 = {
isa = PBXGroup;
children = (
3EAA11C31B1B894900EC0006 /* README.md */,
004967CE1AE974A600B10C98 /* AVFoundationExporter */,
004967CD1AE974A600B10C98 /* Products */,
);
sourceTree = "<group>";
};
004967CD1AE974A600B10C98 /* Products */ = {
isa = PBXGroup;
children = (
004967CC1AE974A600B10C98 /* AVFoundationExporter */,
);
name = Products;
sourceTree = "<group>";
};
004967CE1AE974A600B10C98 /* AVFoundationExporter */ = {
isa = PBXGroup;
children = (
004967D61AE9751900B10C98 /* main.swift */,
00599BBE1B1CFCC20093572A /* ArgumentParsing.swift */,
);
path = AVFoundationExporter;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
004967CB1AE974A600B10C98 /* AVFoundationExporter */ = {
isa = PBXNativeTarget;
buildConfigurationList = 004967D31AE974A600B10C98 /* Build configuration list for PBXNativeTarget "AVFoundationExporter" */;
buildPhases = (
004967C81AE974A600B10C98 /* Sources */,
004967C91AE974A600B10C98 /* Frameworks */,
004967CA1AE974A600B10C98 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = AVFoundationExporter;
productName = AVFoundationExporter;
productReference = 004967CC1AE974A600B10C98 /* AVFoundationExporter */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
004967C41AE974A600B10C98 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0800;
TargetAttributes = {
004967CB1AE974A600B10C98 = {
CreatedOnToolsVersion = 6.3;
};
};
};
buildConfigurationList = 004967C71AE974A600B10C98 /* Build configuration list for PBXProject "AVFoundationExporter" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 004967C31AE974A600B10C98;
productRefGroup = 004967CD1AE974A600B10C98 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
004967CB1AE974A600B10C98 /* AVFoundationExporter */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
004967C81AE974A600B10C98 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
00599BBF1B1CFCC20093572A /* ArgumentParsing.swift in Sources */,
004967D71AE9751900B10C98 /* main.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
004967D11AE974A600B10C98 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_VERSION = 2.3;
};
name = Debug;
};
004967D21AE974A600B10C98 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_VERSION = 2.3;
};
name = Release;
};
004967D41AE974A600B10C98 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 2.3;
};
name = Debug;
};
004967D51AE974A600B10C98 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 2.3;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
004967C71AE974A600B10C98 /* Build configuration list for PBXProject "AVFoundationExporter" */ = {
isa = XCConfigurationList;
buildConfigurations = (
004967D11AE974A600B10C98 /* Debug */,
004967D21AE974A600B10C98 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
004967D31AE974A600B10C98 /* Build configuration list for PBXNativeTarget "AVFoundationExporter" */ = {
isa = XCConfigurationList;
buildConfigurations = (
004967D41AE974A600B10C98 /* Debug */,
004967D51AE974A600B10C98 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 004967C41AE974A600B10C98 /* Project object */;
}

View File

@ -0,0 +1,246 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
Abstract:
Parses command-line arguments and invokes the appropriate command
*/
import CoreMedia
import AVFoundation
// Use enums to enforce uniqueness of option labels.
enum LongLabel: String {
case FileType = "filetype"
case PresetName = "preset"
case DeleteExistingFile = "replace"
case LogEverything = "verbose"
case TrimStartTime = "trim-start-time"
case TrimEndTime = "trim-end-time"
case FilterMetadata = "filter-metadata"
case InjectMetadata = "inject-metadata"
}
enum ShortLabel: String {
case FileType = "f"
case PresetName = "p"
case DeleteExistingFile = "r"
case LogEverything = "v"
}
let executableName = NSString(string: Process.arguments.first!).pathComponents.last!
func usage() {
print("Usage:")
print("\t\(executableName) <source path> <dest path> [options]")
print("\t\(executableName) list-presets [<source path>]")
print("") // newline
print("In the first form, \(executableName) performs an export of the file at <source path>, writing the result to a file at <dest path>. If no options are given, a passthrough export to a QuickTime Movie file is performed.")
print("")
print("In the second form, \(executableName) lists the available parameters to the -preset option. If <source path> is specified, only the presets compatible with the file at <source path> will be listed.")
print("")
print("Options for first form:")
print("\t-f, -filetype <UTI>")
print("\t\tThe file type (e.g. com.apple.m4v-video) for the output file")
print("")
print("\t-p, -preset <preset>")
print("\t\tThe preset name; use commmand list-presets to see available preset names")
print("")
print("\t-r, -replace YES")
print("\t\tIf there is a pre-existing file at the destination location, remove it before exporting")
print("")
print("\t-v, -verbose YES")
print("\t\tPrint more information about the execution")
print("")
print("\t-trim-start-time <seconds>")
print("\t\tWhen specified, all media before the start time will be trimmed out")
print("")
print("\t-trim-end-time <seconds>")
print("\t\tWhen specified, all media after the end time will be trimmed out")
print("")
print("\t-filter-metadata YES")
print("\t\tFilter out privacy-sensitive metadata")
print("")
print("\t-inject-metadata YES")
print("\t\tAdd simple metadata during export")
}
// Errors that can occur during argument parsing.
enum CommandLineError: ErrorType, CustomStringConvertible {
case TooManyArguments
case TooFewArguments(descriptionOfRequiredArguments: String)
case InvalidArgument(reason: String)
var description: String {
switch self {
case .TooManyArguments:
return "Too many arguments"
case .TooFewArguments(let descriptionOfRequiredArguments):
return "Missing argument(s). Must specify \(descriptionOfRequiredArguments)."
case .InvalidArgument(let reason):
return "Invalid argument. \(reason)."
}
}
}
/// A set of convenience methods to use with our specific command line arguments.
extension NSUserDefaults {
func stringForLongLabel(longLabel: LongLabel) -> String? {
return stringForKey(longLabel.rawValue)
}
func stringForShortLabel(shortLabel: ShortLabel) -> String? {
return stringForKey(shortLabel.rawValue)
}
func boolForLongLabel(longLabel: LongLabel) -> Bool {
return boolForKey(longLabel.rawValue)
}
func boolForShortLabel(shortLabel: ShortLabel) -> Bool {
return boolForKey(shortLabel.rawValue)
}
func timeForLongLabel(longLabel: LongLabel) throws -> CMTime? {
if let timeAsString = stringForLongLabel(longLabel) {
guard let timeAsSeconds = Float64(timeAsString) else {
throw CommandLineError.InvalidArgument(reason: "Non-numeric time \"\(timeAsString)\".")
}
return CMTimeMakeWithSeconds(timeAsSeconds, 600)
}
return nil
}
func timeForShortLabel(shortLabel: ShortLabel) throws -> CMTime? {
if let timeAsString = stringForShortLabel(shortLabel) {
guard let timeAsSeconds = Float64(timeAsString) else {
throw CommandLineError.InvalidArgument(reason: "Non-numeric time \"\(timeAsString)\".")
}
return CMTimeMakeWithSeconds(timeAsSeconds, 600)
}
return nil
}
}
// Lists all presets, or the presets compatible with the file at the given path
func listPresets(sourcePath: String? = nil) {
let presets: [String]
switch sourcePath {
case let sourcePath?:
print("Presets compatible with \(sourcePath):.")
let sourceURL = NSURL(fileURLWithPath: sourcePath)
let asset = AVAsset(URL: sourceURL)
presets = AVAssetExportSession.exportPresetsCompatibleWithAsset(asset)
case nil:
print("Available presets:")
presets = AVAssetExportSession.allExportPresets()
}
let presetsDescription = presets.joinWithSeparator("\n\t")
print("\t\(presetsDescription)")
}
/// The main function that handles all of the command line argument parsing.
func actOnCommandLineArguments() {
let arguments = Process.arguments
let firstArgumentAfterExecutablePath: String? = (arguments.count >= 2) ? arguments[1] : nil
if arguments.contains("-help") || arguments.contains("-h") {
usage()
exit(0)
}
do {
switch firstArgumentAfterExecutablePath {
case nil, "help"?:
usage()
exit(0)
case "list-presets"?:
if arguments.count == 3 {
listPresets(arguments[2])
}
else if arguments.count > 3 {
throw CommandLineError.TooManyArguments
}
else {
listPresets()
}
default:
guard arguments.count >= 3 else {
throw CommandLineError.TooFewArguments(descriptionOfRequiredArguments: "source and dest paths")
}
let sourceURL = NSURL(fileURLWithPath: arguments[1])
let destinationURL = NSURL(fileURLWithPath: arguments[2])
var exporter = Exporter(sourceURL: sourceURL, destinationURL: destinationURL)
let options = NSUserDefaults.standardUserDefaults()
if let fileType = options.stringForLongLabel(.FileType) ?? options.stringForShortLabel(.FileType) {
exporter.destinationFileType = fileType
}
if let presetName = options.stringForLongLabel(.PresetName) ?? options.stringForShortLabel(.PresetName) {
exporter.presetName = presetName
}
exporter.deleteExistingFile = options.boolForLongLabel(.DeleteExistingFile) || options.boolForShortLabel(.DeleteExistingFile)
exporter.isVerbose = options.boolForLongLabel(.LogEverything) || options.boolForShortLabel(.LogEverything)
let trimStartTime = try options.timeForLongLabel(.TrimStartTime)
let trimEndTime = try options.timeForLongLabel(.TrimEndTime)
switch (trimStartTime, trimEndTime) {
case (nil, nil):
exporter.timeRange = nil
case (let realStartTime?, nil):
exporter.timeRange = CMTimeRange(start: realStartTime, duration: kCMTimePositiveInfinity)
case (nil, let realEndTime?):
exporter.timeRange = CMTimeRangeFromTimeToTime(kCMTimeZero, realEndTime)
case (let realStartTime?, let realEndTime?):
exporter.timeRange = CMTimeRangeFromTimeToTime(realStartTime, realEndTime)
}
exporter.filterMetadata = options.boolForLongLabel(.FilterMetadata)
exporter.injectMetadata = options.boolForLongLabel(.InjectMetadata)
try exporter.export()
}
}
catch let error as CommandLineError {
print("error parsing arguments: \(error).")
print("") // newline
usage()
exit(1)
}
catch let error as NSError {
let highLevelFailure = error.localizedDescription
var errorOutput = highLevelFailure
if let detailedFailure = error.localizedRecoverySuggestion ?? error.localizedFailureReason {
errorOutput += ": \(detailedFailure)"
}
print("error: \(errorOutput).")
exit(1)
}
}

View File

@ -0,0 +1,228 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
Abstract:
Demonstrates how to use AVAssetExportSession to export and transcode media files
*/
import AVFoundation
/*
Perform all of the argument parsing / set up. The interesting AV exporting
code is done in the `Exporter` type.
*/
actOnCommandLineArguments()
/// The type that performs all of the asset exporting.
struct Exporter {
// MARK: Properties
let sourceURL: NSURL
let destinationURL: NSURL
var destinationFileType = AVFileTypeQuickTimeMovie
var presetName = AVAssetExportPresetPassthrough
var timeRange: CMTimeRange?
var filterMetadata = false
var injectMetadata = false
var deleteExistingFile = false
var isVerbose = false
// MARK: Initialization
init(sourceURL: NSURL, destinationURL: NSURL) {
self.sourceURL = sourceURL
self.destinationURL = destinationURL
}
func export() throws {
let asset = AVURLAsset(URL: sourceURL)
printVerbose("Exporting \"\(sourceURL)\" to \"\(destinationURL)\" (file type \(destinationFileType)), using preset \(presetName).")
// Set up export session.
let exportSession = try setUpExportSession(asset, destinationURL: destinationURL)
// AVAssetExportSession will not overwrite existing files.
try deleteExistingFile(destinationURL)
describeSourceFile(asset)
// Kick off asynchronous export operation.
let group = dispatch_group_create()
dispatch_group_enter(group)
exportSession.exportAsynchronouslyWithCompletionHandler {
dispatch_group_leave(group)
}
waitForExportToFinish(exportSession, group: group)
if exportSession.status == .Failed {
// `error` is non-nil when in the "failed" status.
throw exportSession.error!
}
else {
describeDestFile(destinationURL)
}
printVerbose("Export completed successfully.")
}
func setUpExportSession(asset: AVAsset, destinationURL: NSURL) throws -> AVAssetExportSession {
guard let exportSession = AVAssetExportSession(asset: asset, presetName: presetName) else {
throw CommandLineError.InvalidArgument(reason: "Invalid preset \(presetName).")
}
// Set required properties.
exportSession.outputURL = destinationURL
exportSession.outputFileType = destinationFileType
if let timeRange = timeRange {
exportSession.timeRange = timeRange
printVerbose("Trimming to time range \(CMTimeRangeCopyDescription(nil, timeRange)!).")
}
if filterMetadata {
printVerbose("Filtering metadata.")
exportSession.metadataItemFilter = AVMetadataItemFilter.metadataItemFilterForSharing()
}
if injectMetadata {
printVerbose("Injecting metadata")
let now = NSDate()
let currentDate = NSDateFormatter.localizedStringFromDate(now, dateStyle: .MediumStyle, timeStyle: .ShortStyle)
let userDataCommentItem = AVMutableMetadataItem()
userDataCommentItem.identifier = AVMetadataIdentifierQuickTimeUserDataComment
userDataCommentItem.value = "QuickTime userdata: Exported to preset \(presetName) using AVFoundationExporter at: \(currentDate)."
let metadataCommentItem = AVMutableMetadataItem()
metadataCommentItem.identifier = AVMetadataIdentifierQuickTimeMetadataComment
metadataCommentItem.value = "QuickTime metadata: Exported to preset \(presetName) using AVFoundationExporter at: \(currentDate)."
let iTunesCommentItem = AVMutableMetadataItem()
iTunesCommentItem.identifier = AVMetadataIdentifieriTunesMetadataUserComment
iTunesCommentItem.value = "iTunes metadata: Exported to preset \(presetName) using AVFoundationExporter at: \(currentDate)."
/*
To avoid replacing metadata from the asset:
1. Fetch existing metadata from the asset.
2. Combine it with the new metadata.
3. Set the result on the export session.
*/
exportSession.metadata = asset.metadata + [
userDataCommentItem,
metadataCommentItem,
iTunesCommentItem
]
}
return exportSession
}
func deleteExistingFile(destinationURL: NSURL) throws {
let fileManager = NSFileManager()
if let destinationPath = destinationURL.path {
if deleteExistingFile && fileManager.fileExistsAtPath(destinationPath) {
printVerbose("Removing pre-existing file at destination path \"\(destinationPath)\".")
try fileManager.removeItemAtURL(destinationURL)
}
}
}
func describeSourceFile(asset: AVAsset) {
guard isVerbose else { return }
printVerbose("Tracks in source file:")
let trackDescriptions = trackDescriptionsForAsset(asset)
let tracksDescription = trackDescriptions.joinWithSeparator("\n\t")
printVerbose("\t\(tracksDescription)")
printVerbose("Metadata in source file:")
let metadataDescriptions = metadataDescriptionsForAsset(asset)
let metadataDescription = metadataDescriptions.joinWithSeparator("\n\t")
printVerbose("\t\(metadataDescription)")
}
// Periodically polls & prints export session progress while waiting for the export to finish.
func waitForExportToFinish(exportSession: AVAssetExportSession, group: dispatch_group_t) {
while exportSession.status == .Waiting || exportSession.status == .Exporting {
printVerbose("Progress: \(exportSession.progress * 100.0)%.")
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, Int64(500 * NSEC_PER_MSEC)))
}
printVerbose("Progress: \(exportSession.progress * 100.0)%.")
}
func describeDestFile(destinationURL: NSURL) {
guard isVerbose else { return }
let destinationAsset = AVAsset(URL:destinationURL)
printVerbose("Tracks in written file:")
let trackDescriptions = trackDescriptionsForAsset(destinationAsset)
let tracksDescription = trackDescriptions.joinWithSeparator("\n\t")
printVerbose("\t\(tracksDescription)")
printVerbose("Metadata in written file:")
let metadataDescriptions = metadataDescriptionsForAsset(destinationAsset)
let metadataDescription = metadataDescriptions.joinWithSeparator("\n\t")
printVerbose("\t\(metadataDescription)")
}
func trackDescriptionsForAsset(asset: AVAsset) -> [String] {
return asset.tracks.map { track in
let enabledString = track.enabled ? "YES" : "NO"
let selfContainedString = track.selfContained ? "YES" : "NO"
let formatDescriptions = track.formatDescriptions as! [CMFormatDescriptionRef]
let formatStrings = formatDescriptions.map { formatDescription -> String in
let mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription)
let mediaSubTypeString = NSFileTypeForHFSTypeCode(mediaSubType)
return "'\(track.mediaType)'/\(mediaSubTypeString)"
}
let formatString = !formatStrings.isEmpty ? formatStrings.joinWithSeparator(", ") : "'\(track.mediaType)'"
return "Track ID \(track.trackID): \(formatString), data length: \(track.totalSampleDataLength), enabled: \(enabledString), self-contained: \(selfContainedString)"
}
}
func metadataDescriptionsForAsset(asset: AVAsset) -> [String] {
return asset.metadata.map { item in
let identifier = item.identifier ?? "<no identifier>"
let value = item.value?.description ?? "<no value>"
return "metadata item \(identifier): \(value)"
}
}
func printVerbose(string: String) {
if isVerbose {
print(string)
}
}
}