Swift/AVFoundationExporter/ArgumentParsing.swift
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s 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) |
} |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-13