From 7b773c1060e7cb7c136503a491857e18a25e10bc Mon Sep 17 00:00:00 2001 From: banjun Date: Tue, 22 Mar 2016 12:25:58 +0900 Subject: [PATCH 1/2] use #selector and class-type early nil return of Swift 2.2 --- .../LivePhotoSandboxViewController.swift | 22 +++++++++---------- .../MovieDocumentViewController.swift | 2 +- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/LoveLiver-osx/LivePhotoSandboxViewController.swift b/LoveLiver-osx/LivePhotoSandboxViewController.swift index fab14bd..b4f39ff 100644 --- a/LoveLiver-osx/LivePhotoSandboxViewController.swift +++ b/LoveLiver-osx/LivePhotoSandboxViewController.swift @@ -34,7 +34,7 @@ class LivePhotoSandboxViewController: NSViewController { b.bezelStyle = .RegularSquareBezelStyle b.title = "Create Live Photo" b.target = self - b.action = "export" + b.action = #selector(self.export) } private var exportSession: AVAssetExportSession? @@ -42,7 +42,7 @@ class LivePhotoSandboxViewController: NSViewController { b.bezelStyle = .RegularSquareBezelStyle b.title = "Close" b.target = self - b.action = "close" + b.action = #selector(self.close) } var closeAction: (Void -> Void)? @@ -105,31 +105,29 @@ class LivePhotoSandboxViewController: NSViewController { } init!(player: AVPlayer, baseFilename: String) { - // use guard let and return nil with Swift 2.2 - let asset = player.currentItem?.asset - let item = asset.map {AVPlayerItem(asset: $0)} + guard let asset = player.currentItem?.asset else { return nil } + let item = AVPlayerItem(asset: asset) self.baseFilename = baseFilename posterTime = player.currentTime() - let duration = item?.duration ?? kCMTimeZero + let duration = item.duration let offset = CMTime(seconds: livePhotoDuration / 2, preferredTimescale: posterTime.timescale) startTime = CMTimeMaximum(kCMTimeZero, CMTimeSubtract(posterTime, offset)) endTime = CMTimeMinimum(CMTimeAdd(posterTime, offset), duration) - self.player = item.map {AVPlayer(playerItem: $0)} ?? player + self.player = AVPlayer(playerItem: item) - imageGenerator = AVAssetImageGenerator(asset: asset ?? AVAsset()) ※ { g -> Void in + imageGenerator = AVAssetImageGenerator(asset: asset) ※ { g -> Void in g.requestedTimeToleranceBefore = kCMTimeZero g.requestedTimeToleranceAfter = kCMTimeZero g.maximumSize = CGSize(width: 128 * 2, height: 128 * 2) } - overview = MovieOverviewControl(player: self.player, playerItem: item ?? AVPlayerItem(asset: AVAsset())) + overview = MovieOverviewControl(player: self.player, playerItem: item) overview.draggingMode = .Scope overview.imageGeneratorTolerance = kCMTimeZero super.init(nibName: nil, bundle: nil) - guard let _ = item else { return nil } self.player.volume = player.volume self.player.actionAtItemEnd = .Pause @@ -201,7 +199,7 @@ class LivePhotoSandboxViewController: NSViewController { updateScope() // hook playerView click - let playerViewClickGesture = NSClickGestureRecognizer(target: self, action: "playOrPause") + let playerViewClickGesture = NSClickGestureRecognizer(target: self, action: #selector(playOrPause)) playerView.addGestureRecognizer(playerViewClickGesture) } @@ -227,7 +225,7 @@ class LivePhotoSandboxViewController: NSViewController { let assetIdentifier = NSUUID().UUIDString let basename = [ - baseFilename ?? "", + baseFilename, posterTime.stringInmmmsssSS, assetIdentifier].joinWithSeparator("-") let tmpImagePath = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent("\(basename).tiff").path! diff --git a/LoveLiver-osx/MovieDocumentViewController.swift b/LoveLiver-osx/MovieDocumentViewController.swift index 4eb0b76..6329915 100644 --- a/LoveLiver-osx/MovieDocumentViewController.swift +++ b/LoveLiver-osx/MovieDocumentViewController.swift @@ -28,7 +28,7 @@ class MovieDocumentViewController: NSViewController { b.setButtonType(.MomentaryLightButton) b.bezelStyle = .RoundedBezelStyle b.target = self - b.action = "createLivePhotoSandbox" + b.action = #selector(self.createLivePhotoSandbox) } init!(movieURL: NSURL, playerItem: AVPlayerItem, player: AVPlayer) { From 1a818260725ee1618b33471cd6753c2b70079e9e Mon Sep 17 00:00:00 2001 From: banjun Date: Sat, 16 Apr 2016 19:07:04 +0900 Subject: [PATCH 2/2] remove CLI to focus on GUI --- LoveLiver.xcodeproj/project.pbxproj | 102 ------ .../xcshareddata/xcschemes/LoveLiver.xcscheme | 117 ------- LoveLiver/CommandLine/CommandLine.swift | 296 ------------------ LoveLiver/CommandLine/Info.plist | 28 -- LoveLiver/CommandLine/Option.swift | 271 ---------------- LoveLiver/CommandLine/StringExtensions.swift | 162 ---------- LoveLiver/main.swift | 71 ----- README.md | 55 +--- 8 files changed, 8 insertions(+), 1094 deletions(-) delete mode 100644 LoveLiver.xcodeproj/xcshareddata/xcschemes/LoveLiver.xcscheme delete mode 100644 LoveLiver/CommandLine/CommandLine.swift delete mode 100644 LoveLiver/CommandLine/Info.plist delete mode 100644 LoveLiver/CommandLine/Option.swift delete mode 100644 LoveLiver/CommandLine/StringExtensions.swift delete mode 100644 LoveLiver/main.swift diff --git a/LoveLiver.xcodeproj/project.pbxproj b/LoveLiver.xcodeproj/project.pbxproj index 1ea66e1..9e3c48c 100644 --- a/LoveLiver.xcodeproj/project.pbxproj +++ b/LoveLiver.xcodeproj/project.pbxproj @@ -7,12 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 133DFE291BC8B71100902407 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DFE281BC8B71100902407 /* main.swift */; }; - 133DFE371BC8C3E000902407 /* CommandLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DFE331BC8C3E000902407 /* CommandLine.swift */; }; - 133DFE381BC8C3E000902407 /* Option.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DFE351BC8C3E000902407 /* Option.swift */; }; - 133DFE391BC8C3E000902407 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DFE361BC8C3E000902407 /* StringExtensions.swift */; }; - 133DFE3B1BC8C69900902407 /* JPEG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DFE3A1BC8C69900902407 /* JPEG.swift */; }; - 133DFE3D1BC8C6EF00902407 /* QuickTimeMov.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DFE3C1BC8C6EF00902407 /* QuickTimeMov.swift */; }; C2C41F0CC51A39D97981D73D /* Pods_LoveLiver_osx.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B34746BF27B18F34F5E26C5 /* Pods_LoveLiver_osx.framework */; }; EA27F5EF1C98426A00C23F40 /* MovieOverviewControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA27F5EE1C98426A00C23F40 /* MovieOverviewControl.swift */; }; EA379AB91C68DE6D00106AEF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA379AB81C68DE6D00106AEF /* AppDelegate.swift */; }; @@ -27,25 +21,7 @@ EA88135F1C9ED065000CE151 /* MovieOverviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA88135E1C9ED065000CE151 /* MovieOverviewViewController.swift */; }; /* End PBXBuildFile section */ -/* Begin PBXCopyFilesBuildPhase section */ - 133DFE231BC8B71100902407 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 1; - }; -/* End PBXCopyFilesBuildPhase section */ - /* Begin PBXFileReference section */ - 133DFE251BC8B71100902407 /* LoveLiver */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = LoveLiver; sourceTree = BUILT_PRODUCTS_DIR; }; - 133DFE281BC8B71100902407 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; - 133DFE331BC8C3E000902407 /* CommandLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandLine.swift; sourceTree = ""; }; - 133DFE341BC8C3E000902407 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 133DFE351BC8C3E000902407 /* Option.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Option.swift; sourceTree = ""; }; - 133DFE361BC8C3E000902407 /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; 133DFE3A1BC8C69900902407 /* JPEG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JPEG.swift; sourceTree = ""; }; 133DFE3C1BC8C6EF00902407 /* QuickTimeMov.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickTimeMov.swift; sourceTree = ""; }; 3B34746BF27B18F34F5E26C5 /* Pods_LoveLiver_osx.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LoveLiver_osx.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -65,13 +41,6 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 133DFE221BC8B71100902407 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; EA379AB31C68DE6D00106AEF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -106,7 +75,6 @@ 133DFE261BC8B71100902407 /* Products */ = { isa = PBXGroup; children = ( - 133DFE251BC8B71100902407 /* LoveLiver */, EA379AB61C68DE6D00106AEF /* LoveLiver.app */, ); name = Products; @@ -115,25 +83,12 @@ 133DFE271BC8B71100902407 /* LoveLiver */ = { isa = PBXGroup; children = ( - 133DFE321BC8C3E000902407 /* CommandLine */, - 133DFE281BC8B71100902407 /* main.swift */, 133DFE3A1BC8C69900902407 /* JPEG.swift */, 133DFE3C1BC8C6EF00902407 /* QuickTimeMov.swift */, ); path = LoveLiver; sourceTree = ""; }; - 133DFE321BC8C3E000902407 /* CommandLine */ = { - isa = PBXGroup; - children = ( - 133DFE331BC8C3E000902407 /* CommandLine.swift */, - 133DFE341BC8C3E000902407 /* Info.plist */, - 133DFE351BC8C3E000902407 /* Option.swift */, - 133DFE361BC8C3E000902407 /* StringExtensions.swift */, - ); - path = CommandLine; - sourceTree = ""; - }; A5911014E6D1DEA080967902 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -162,23 +117,6 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 133DFE241BC8B71100902407 /* LoveLiver */ = { - isa = PBXNativeTarget; - buildConfigurationList = 133DFE2C1BC8B71100902407 /* Build configuration list for PBXNativeTarget "LoveLiver" */; - buildPhases = ( - 133DFE211BC8B71100902407 /* Sources */, - 133DFE221BC8B71100902407 /* Frameworks */, - 133DFE231BC8B71100902407 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = LoveLiver; - productName = LoveLiver; - productReference = 133DFE251BC8B71100902407 /* LoveLiver */; - productType = "com.apple.product-type.tool"; - }; EA379AB51C68DE6D00106AEF /* LoveLiver-osx */ = { isa = PBXNativeTarget; buildConfigurationList = EA379AC21C68DE6D00106AEF /* Build configuration list for PBXNativeTarget "LoveLiver-osx" */; @@ -209,9 +147,6 @@ LastUpgradeCheck = 0700; ORGANIZATIONNAME = mzp; TargetAttributes = { - 133DFE241BC8B71100902407 = { - CreatedOnToolsVersion = 7.0.1; - }; EA379AB51C68DE6D00106AEF = { CreatedOnToolsVersion = 7.2; }; @@ -230,7 +165,6 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 133DFE241BC8B71100902407 /* LoveLiver */, EA379AB51C68DE6D00106AEF /* LoveLiver-osx */, ); }; @@ -297,19 +231,6 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 133DFE211BC8B71100902407 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 133DFE391BC8C3E000902407 /* StringExtensions.swift in Sources */, - 133DFE3B1BC8C69900902407 /* JPEG.swift in Sources */, - 133DFE291BC8B71100902407 /* main.swift in Sources */, - 133DFE381BC8C3E000902407 /* Option.swift in Sources */, - 133DFE371BC8C3E000902407 /* CommandLine.swift in Sources */, - 133DFE3D1BC8C6EF00902407 /* QuickTimeMov.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; EA379AB21C68DE6D00106AEF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -418,20 +339,6 @@ }; name = Release; }; - 133DFE2D1BC8B71100902407 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 133DFE2E1BC8B71100902407 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; EA379AC01C68DE6D00106AEF /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = DCD5B633553004CEA3DAF5F6 /* Pods-LoveLiver-osx.debug.xcconfig */; @@ -472,15 +379,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 133DFE2C1BC8B71100902407 /* Build configuration list for PBXNativeTarget "LoveLiver" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 133DFE2D1BC8B71100902407 /* Debug */, - 133DFE2E1BC8B71100902407 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; EA379AC21C68DE6D00106AEF /* Build configuration list for PBXNativeTarget "LoveLiver-osx" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/LoveLiver.xcodeproj/xcshareddata/xcschemes/LoveLiver.xcscheme b/LoveLiver.xcodeproj/xcshareddata/xcschemes/LoveLiver.xcscheme deleted file mode 100644 index 2dd6964..0000000 --- a/LoveLiver.xcodeproj/xcshareddata/xcschemes/LoveLiver.xcscheme +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/LoveLiver/CommandLine/CommandLine.swift b/LoveLiver/CommandLine/CommandLine.swift deleted file mode 100644 index a70c054..0000000 --- a/LoveLiver/CommandLine/CommandLine.swift +++ /dev/null @@ -1,296 +0,0 @@ -/* - * CommandLine.swift - * Copyright (c) 2014 Ben Gollmer. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* Required for setlocale(3) */ -@exported import Darwin - -let ShortOptionPrefix = "-" -let LongOptionPrefix = "--" - -/* Stop parsing arguments when an ArgumentStopper (--) is detected. This is a GNU getopt - * convention; cf. https://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html - */ -let ArgumentStopper = "--" - -/* Allow arguments to be attached to flags when separated by this character. - * --flag=argument is equivalent to --flag argument - */ -let ArgumentAttacher: Character = "=" - -/* An output stream to stderr; used by CommandLine.printUsage(). */ -private struct StderrOutputStream: OutputStreamType { - static let stream = StderrOutputStream() - func write(s: String) { - fputs(s, stderr) - } -} - -/** - * The CommandLine class implements a command-line interface for your app. - * - * To use it, define one or more Options (see Option.swift) and add them to your - * CommandLine object, then invoke `parse()`. Each Option object will be populated with - * the value given by the user. - * - * If any required options are missing or if an invalid value is found, `parse()` will throw - * a `ParseError`. You can then call `printUsage()` to output an automatically-generated usage - * message. - */ -public class CommandLine { - private var _arguments: [String] - private var _options: [Option] = [Option]() - - /** A ParseError is thrown if the `parse()` method fails. */ - public enum ParseError: ErrorType, CustomStringConvertible { - /** Thrown if an unrecognized argument is passed to `parse()` in strict mode */ - case InvalidArgument(String) - - /** Thrown if the value for an Option is invalid (e.g. a string is passed to an IntOption) */ - case InvalidValueForOption(Option, [String]) - - /** Thrown if an Option with required: true is missing */ - case MissingRequiredOptions([Option]) - - public var description: String { - switch self { - case let .InvalidArgument(arg): - return "Invalid argument: \(arg)" - case let .InvalidValueForOption(opt, vals): - let vs = vals.joinWithSeparator(", ") - return "Invalid value(s) for option \(opt.flagDescription): \(vs)" - case let .MissingRequiredOptions(opts): - return "Missing required options: \(opts.map { return $0.flagDescription })" - } - } - } - - /** - * Initializes a CommandLine object. - * - * - parameter arguments: Arguments to parse. If omitted, the arguments passed to the app - * on the command line will automatically be used. - * - * - returns: An initalized CommandLine object. - */ - public init(arguments: [String] = Process.arguments) { - self._arguments = arguments - - /* Initialize locale settings from the environment */ - setlocale(LC_ALL, "") - } - - /* Returns all argument values from flagIndex to the next flag or the end of the argument array. */ - private func _getFlagValues(flagIndex: Int) -> [String] { - var args: [String] = [String]() - var skipFlagChecks = false - - /* Grab attached arg, if any */ - var attachedArg = _arguments[flagIndex].splitByCharacter(ArgumentAttacher, maxSplits: 1) - if attachedArg.count > 1 { - args.append(attachedArg[1]) - } - - for var i = flagIndex + 1; i < _arguments.count; i++ { - if !skipFlagChecks { - if _arguments[i] == ArgumentStopper { - skipFlagChecks = true - continue - } - - if _arguments[i].hasPrefix(ShortOptionPrefix) && Int(_arguments[i]) == nil && - _arguments[i].toDouble() == nil { - break - } - } - - args.append(_arguments[i]) - } - - return args - } - - /** - * Adds an Option to the command line. - * - * - parameter option: The option to add. - */ - public func addOption(option: Option) { - _options.append(option) - } - - /** - * Adds one or more Options to the command line. - * - * - parameter options: An array containing the options to add. - */ - public func addOptions(options: [Option]) { - _options += options - } - - /** - * Adds one or more Options to the command line. - * - * - parameter options: The options to add. - */ - public func addOptions(options: Option...) { - _options += options - } - - /** - * Sets the command line Options. Any existing options will be overwritten. - * - * - parameter options: An array containing the options to set. - */ - public func setOptions(options: [Option]) { - _options = options - } - - /** - * Sets the command line Options. Any existing options will be overwritten. - * - * - parameter options: The options to set. - */ - public func setOptions(options: Option...) { - _options = options - } - - /** - * Parses command-line arguments into their matching Option values. Throws `ParseError` if - * argument parsing fails. - * - * - parameter strict: Fail if any unrecognized arguments are present (default: false). - */ - public func parse(strict: Bool = false) throws { - for (idx, arg) in _arguments.enumerate() { - if arg == ArgumentStopper { - break - } - - if !arg.hasPrefix(ShortOptionPrefix) { - continue - } - - let skipChars = arg.hasPrefix(LongOptionPrefix) ? - LongOptionPrefix.characters.count : ShortOptionPrefix.characters.count - let flagWithArg = arg[Range(start: arg.startIndex.advancedBy(skipChars), end: arg.endIndex)] - - /* The argument contained nothing but ShortOptionPrefix or LongOptionPrefix */ - if flagWithArg.isEmpty { - continue - } - - /* Remove attached argument from flag */ - let flag = flagWithArg.splitByCharacter(ArgumentAttacher, maxSplits: 1)[0] - - var flagMatched = false - for option in _options where option.flagMatch(flag) { - let vals = self._getFlagValues(idx) - guard option.setValue(vals) else { - throw ParseError.InvalidValueForOption(option, vals) - } - - flagMatched = true - break - } - - /* Flags that do not take any arguments can be concatenated */ - let flagLength = flag.characters.count - if !flagMatched && !arg.hasPrefix(LongOptionPrefix) { - for (i, c) in flag.characters.enumerate() { - for option in _options where option.flagMatch(String(c)) { - /* Values are allowed at the end of the concatenated flags, e.g. - * -xvf - */ - let vals = (i == flagLength - 1) ? self._getFlagValues(idx) : [String]() - guard option.setValue(vals) else { - throw ParseError.InvalidValueForOption(option, vals) - } - - flagMatched = true - break - } - } - } - - /* Invalid flag */ - guard !strict || flagMatched else { - throw ParseError.InvalidArgument(arg) - } - } - - /* Check to see if any required options were not matched */ - let missingOptions = _options.filter { $0.required && !$0.wasSet } - guard missingOptions.count == 0 else { - throw ParseError.MissingRequiredOptions(missingOptions) - } - } - - /* printUsage() is generic for OutputStreamType because the Swift compiler crashes - * on inout protocol function parameters in Xcode 7 beta 1 (rdar://21372694). - */ - - /** - * Prints a usage message. - * - * - parameter to: An OutputStreamType to write the error message to. - */ - public func printUsage(inout to: TargetStream) { - let name = _arguments[0] - - var flagWidth = 0 - for opt in _options { - flagWidth = max(flagWidth, " \(opt.flagDescription):".characters.count) - } - - print("Usage: \(name) [options]", toStream: &to) - for opt in _options { - let flags = " \(opt.flagDescription):".paddedToWidth(flagWidth) - print("\(flags)\n \(opt.helpMessage)", toStream: &to) - } - } - - /** - * Prints a usage message. - * - * - parameter error: An error thrown from `parse()`. A description of the error - * (e.g. "Missing required option --extract") will be printed before the usage message. - * - parameter to: An OutputStreamType to write the error message to. - */ - public func printUsage(error: ErrorType, inout to: TargetStream) { - print("\(error)\n", toStream: &to) - printUsage(&to) - } - - /** - * Prints a usage message. - * - * - parameter error: An error thrown from `parse()`. A description of the error - * (e.g. "Missing required option --extract") will be printed before the usage message. - */ - public func printUsage(error: ErrorType) { - var out = StderrOutputStream.stream - printUsage(error, to: &out) - } - - /** - * Prints a usage message. - */ - public func printUsage() { - var out = StderrOutputStream.stream - printUsage(&out) - } -} diff --git a/LoveLiver/CommandLine/Info.plist b/LoveLiver/CommandLine/Info.plist deleted file mode 100644 index e095737..0000000 --- a/LoveLiver/CommandLine/Info.plist +++ /dev/null @@ -1,28 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSHumanReadableCopyright - Copyright © 2014 Ben Gollmer. Licensed under the Apache License, Version 2.0. - NSPrincipalClass - - - diff --git a/LoveLiver/CommandLine/Option.swift b/LoveLiver/CommandLine/Option.swift deleted file mode 100644 index 74466bb..0000000 --- a/LoveLiver/CommandLine/Option.swift +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Option.swift - * Copyright (c) 2014 Ben Gollmer. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * The base class for a command-line option. - */ -public class Option { - public let shortFlag: String? - public let longFlag: String? - public let required: Bool - public let helpMessage: String - - /** True if the option was set when parsing command-line arguments */ - public var wasSet: Bool { - return false - } - - public var flagDescription: String { - switch (shortFlag, longFlag) { - case (let sf, let lf) where sf != nil && lf != nil: - return "\(ShortOptionPrefix)\(sf!), \(LongOptionPrefix)\(lf!)" - case (_, let lf) where lf != nil: - return "\(LongOptionPrefix)\(lf!)" - default: - return "\(ShortOptionPrefix)\(shortFlag!)" - } - } - - private init(_ shortFlag: String?, _ longFlag: String?, _ required: Bool, _ helpMessage: String) { - if let sf = shortFlag { - assert(sf.characters.count == 1, "Short flag must be a single character") - assert(Int(sf) == nil && sf.toDouble() == nil, "Short flag cannot be a numeric value") - } - - if let lf = longFlag { - assert(Int(lf) == nil && lf.toDouble() == nil, "Long flag cannot be a numeric value") - } - - self.shortFlag = shortFlag - self.longFlag = longFlag - self.helpMessage = helpMessage - self.required = required - } - - /* The optional casts in these initalizers force them to call the private initializer. Without - * the casts, they recursively call themselves. - */ - - /** Initializes a new Option that has both long and short flags. */ - public convenience init(shortFlag: String, longFlag: String, required: Bool = false, helpMessage: String) { - self.init(shortFlag as String?, longFlag, required, helpMessage) - } - - /** Initializes a new Option that has only a short flag. */ - public convenience init(shortFlag: String, required: Bool = false, helpMessage: String) { - self.init(shortFlag as String?, nil, required, helpMessage) - } - - /** Initializes a new Option that has only a long flag. */ - public convenience init(longFlag: String, required: Bool = false, helpMessage: String) { - self.init(nil, longFlag as String?, required, helpMessage) - } - - func flagMatch(flag: String) -> Bool { - return flag == shortFlag || flag == longFlag - } - - func setValue(values: [String]) -> Bool { - return false - } -} - -/** - * A boolean option. The presence of either the short or long flag will set the value to true; - * absence of the flag(s) is equivalent to false. - */ -public class BoolOption: Option { - private var _value: Bool = false - - public var value: Bool { - return _value - } - - override public var wasSet: Bool { - return _value - } - - override func setValue(values: [String]) -> Bool { - _value = true - return true - } -} - -/** An option that accepts a positive or negative integer value. */ -public class IntOption: Option { - private var _value: Int? - - public var value: Int? { - return _value - } - - override public var wasSet: Bool { - return _value != nil - } - - override func setValue(values: [String]) -> Bool { - if values.count == 0 { - return false - } - - if let val = Int(values[0]) { - _value = val - return true - } - - return false - } -} - -/** - * An option that represents an integer counter. Each time the short or long flag is found - * on the command-line, the counter will be incremented. - */ -public class CounterOption: Option { - private var _value: Int = 0 - - public var value: Int { - return _value - } - - override public var wasSet: Bool { - return _value > 0 - } - - override func setValue(values: [String]) -> Bool { - _value += 1 - return true - } -} - -/** An option that accepts a positive or negative floating-point value. */ -public class DoubleOption: Option { - private var _value: Double? - - public var value: Double? { - return _value - } - - override public var wasSet: Bool { - return _value != nil - } - - override func setValue(values: [String]) -> Bool { - if values.count == 0 { - return false - } - - if let val = values[0].toDouble() { - _value = val - return true - } - - return false - } -} - -/** An option that accepts a string value. */ -public class StringOption: Option { - private var _value: String? = nil - - public var value: String? { - return _value - } - - override public var wasSet: Bool { - return _value != nil - } - - override func setValue(values: [String]) -> Bool { - if values.count == 0 { - return false - } - - _value = values[0] - return true - } -} - -/** An option that accepts one or more string values. */ -public class MultiStringOption: Option { - private var _value: [String]? - - public var value: [String]? { - return _value - } - - override public var wasSet: Bool { - return _value != nil - } - - override func setValue(values: [String]) -> Bool { - if values.count == 0 { - return false - } - - _value = values - return true - } -} - -/** An option that represents an enum value. */ -public class EnumOption: Option { - private var _value: T? - public var value: T? { - return _value - } - - override public var wasSet: Bool { - return _value != nil - } - - /* Re-defining the intializers is necessary to make the Swift 2 compiler happy, as - * of Xcode 7 beta 2. - */ - - private override init(_ shortFlag: String?, _ longFlag: String?, _ required: Bool, _ helpMessage: String) { - super.init(shortFlag, longFlag, required, helpMessage) - } - - /** Initializes a new Option that has both long and short flags. */ - public convenience init(shortFlag: String, longFlag: String, required: Bool = false, helpMessage: String) { - self.init(shortFlag as String?, longFlag, required, helpMessage) - } - - /** Initializes a new Option that has only a short flag. */ - public convenience init(shortFlag: String, required: Bool = false, helpMessage: String) { - self.init(shortFlag as String?, nil, required, helpMessage) - } - - /** Initializes a new Option that has only a long flag. */ - public convenience init(longFlag: String, required: Bool = false, helpMessage: String) { - self.init(nil, longFlag as String?, required, helpMessage) - } - - override func setValue(values: [String]) -> Bool { - if values.count == 0 { - return false - } - - if let v = T(rawValue: values[0]) { - _value = v - return true - } - - return false - } -} diff --git a/LoveLiver/CommandLine/StringExtensions.swift b/LoveLiver/CommandLine/StringExtensions.swift deleted file mode 100644 index 8d3777e..0000000 --- a/LoveLiver/CommandLine/StringExtensions.swift +++ /dev/null @@ -1,162 +0,0 @@ -/* - * StringExtensions.swift - * Copyright (c) 2014 Ben Gollmer. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* Required for localeconv(3) */ -import Darwin - -internal extension String { - /* Retrieves locale-specified decimal separator from the environment - * using localeconv(3). - */ - private func _localDecimalPoint() -> Character { - let locale = localeconv() - if locale != nil { - let decimalPoint = locale.memory.decimal_point - if decimalPoint != nil { - return Character(UnicodeScalar(UInt32(decimalPoint.memory))) - } - } - - return "." - } - - /** - * Attempts to parse the string value into a Double. - * - * - returns: A Double if the string can be parsed, nil otherwise. - */ - func toDouble() -> Double? { - var characteristic: String = "0" - var mantissa: String = "0" - var inMantissa: Bool = false - var isNegative: Bool = false - let decimalPoint = self._localDecimalPoint() - - for (i, c) in self.characters.enumerate() { - if i == 0 && c == "-" { - isNegative = true - continue - } - - if c == decimalPoint { - inMantissa = true - continue - } - - if Int(String(c)) != nil { - if !inMantissa { - characteristic.append(c) - } else { - mantissa.append(c) - } - } else { - /* Non-numeric character found, bail */ - return nil - } - } - - return (Double(Int(characteristic)!) + - Double(Int(mantissa)!) / pow(Double(10), Double(mantissa.characters.count - 1))) * - (isNegative ? -1 : 1) - } - - /** - * Splits a string into an array of string components. - * - * - parameter splitBy: The character to split on. - * - parameter maxSplit: The maximum number of splits to perform. If 0, all possible splits are made. - * - * - returns: An array of string components. - */ - func splitByCharacter(splitBy: Character, maxSplits: Int = 0) -> [String] { - var s = [String]() - var numSplits = 0 - - var curIdx = self.startIndex - for(var i = self.startIndex; i != self.endIndex; i = i.successor()) { - let c = self[i] - if c == splitBy && (maxSplits == 0 || numSplits < maxSplits) { - s.append(self[Range(start: curIdx, end: i)]) - curIdx = i.successor() - numSplits++ - } - } - - if curIdx != self.endIndex { - s.append(self[Range(start: curIdx, end: self.endIndex)]) - } - - return s - } - - /** - * Pads a string to the specified width. - * - * - parameter width: The width to pad the string to. - * - parameter padBy: The character to use for padding. - * - * - returns: A new string, padded to the given width. - */ - func paddedToWidth(width: Int, padBy: Character = " ") -> String { - var s = self - var currentLength = self.characters.count - - while currentLength++ < width { - s.append(padBy) - } - - return s - } - - /** - * Wraps a string to the specified width. - * - * This just does simple greedy word-packing, it doesn't go full Knuth-Plass. - * If a single word is longer than the line width, it will be placed (unsplit) - * on a line by itself. - * - * - parameter width: The maximum length of a line. - * - parameter wrapBy: The line break character to use. - * - parameter splitBy: The character to use when splitting the string into words. - * - * - returns: A new string, wrapped at the given width. - */ - func wrappedAtWidth(width: Int, wrapBy: Character = "\n", splitBy: Character = " ") -> String { - var s = "" - var currentLineWidth = 0 - - for word in self.splitByCharacter(splitBy) { - let wordLength = word.characters.count - - if currentLineWidth + wordLength + 1 > width { - /* Word length is greater than line length, can't wrap */ - if wordLength >= width { - s += word - } - - s.append(wrapBy) - currentLineWidth = 0 - } - - currentLineWidth += wordLength + 1 - s += word - s.append(splitBy) - } - - return s - } -} diff --git a/LoveLiver/main.swift b/LoveLiver/main.swift deleted file mode 100644 index 48fda9a..0000000 --- a/LoveLiver/main.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// main.swift -// LoveLiver -// -// Created by mzp on 10/10/15. -// Copyright © 2015 mzp. All rights reserved. -// - -import Foundation - -enum Operation: String { - case DumpJPEGMetaData = "jpeg" - case DumpMOVMetaData = "mov" - case CreateLivePhoto = "livephoto" -} - -let cli = CommandLine() -let op = EnumOption(shortFlag: "o", longFlag: "operation", required: true, - helpMessage: "LivePhoto option - jpeg for dump JPEG metadata, mov for dump MOV metadata, livephoto for create LivePhoto") -let image = StringOption(shortFlag: "i", longFlag: "jpeg", required: false, - helpMessage: "Path to the image file.") -let mov = StringOption(shortFlag: "m", longFlag: "mov", required: false, - helpMessage: "Path to the mov file.") -let output = StringOption(shortFlag: "d", longFlag: "output", required: false, - helpMessage: "Path to the output live photo.") - -cli.setOptions(op, image, mov, output) - -do { - try cli.parse() -} catch { - cli.printUsage(error) - exit(EX_USAGE) -} - -let kErrorMessage = "no metadata" -switch op.value! { -case Operation.DumpJPEGMetaData: - if let path = image.value { - print("asset identifier: \(JPEG(path: path).read() ?? kErrorMessage)") - } else { - print("Please specify --jpeg option.") - } -case Operation.DumpMOVMetaData: - if let path = mov.value { - let qt = QuickTimeMov(path: path) - print("asset identifier: \(qt.readAssetIdentifier() ?? kErrorMessage)") - print("still image time: \(qt.readStillImageTime() ?? kErrorMessage)") - } else { - print("Please specify --mov option.") - } -case Operation.CreateLivePhoto: - if let image = image.value { - if let mov = mov.value { - if let output = output.value { - let assetIdentifier = NSUUID().UUIDString - let _ = try? NSFileManager.defaultManager().createDirectoryAtPath(output, withIntermediateDirectories: true, attributes: nil) - JPEG(path: image).write(output.stringByAppendingString("/IMG.JPG"), - assetIdentifier: assetIdentifier) - QuickTimeMov(path: mov).write(output.stringByAppendingString("/IMG.MOV"), - assetIdentifier: assetIdentifier) - } else { - print("Please specify --output option.") - } - } else { - print("Please specify --mov option.") - } - } else { - print("Please specify --jpeg option.") - } -} diff --git a/README.md b/README.md index 49146cb..5c9da59 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,20 @@ # LoveLiver -![live photo demo](https://raw.githubusercontent.com/mzp/LoveLiver/m%40ster/demo.gif) +

+ + + +

+ +LoveLiver is a Mac OS X GUI application to create Apple's Live Photos from movie files. -LoveLiver is a Mac OS X GUI application and a CLI tool to create Apple's Live Photos from JPEG and MOV. +![live photo demo](https://raw.githubusercontent.com/mzp/LoveLiver/m%40ster/demo.gif) ## Requirements * MacOS X 10.11 (El Capitan) * Photos.app -## GUI application - -

- - -
- LoveLiver -
-

### Install @@ -31,42 +28,6 @@ LoveLiver is a Mac OS X GUI application and a CLI tool to create Apple's Live Ph 3. Seek movie position and `Create Live Photo` 4. `Import All New Photos` on Photos.app -## CLI tool - -### Install - - 1. Go to the [releases page](https://github.com/mzp/LoveLiver/releases), find the version you want. - 2. Download the file. - 3. Put the binary to somewhere you want (e.g. `/usr/local/bin`). - 4. Make sure it has execution bits turned on by `chmod a+x LoveLiver`. - -### Usage - -#### Create Live Photos - -``` -$ ./LoveLiver --operation=livephoto --jpeg sample/original/IMG.JPG --mov sample/original/IMG.MOV --output sample/livephoto -finish writing. -``` - -and drop & drag `sample/livephoto` directory to `Photos.app`. - -#### Show metadata of JPEG - -``` -$ ./LoveLiver --operation=jpeg --jpeg sample/livephoto/IMG.JPG -asset identifier: CDDD4450-642F-442B-8371-B46BC4229XXY -``` - -#### Show metadata of QuickTime MOV - -``` -$ ./LoveLiver --operation=mov --mov sample/livephoto/IMG.MOV -asset identifier: CDDD4450-642F-442B-8371-B46BC4229XXY -still image time: 0 -``` - ## Acknowledge - * [CommandLine](https://github.com/jatoben/CommandLine) is Copyright (c) 2014 Ben Gollmer and is licensed under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). * [Tda-style Hatsune Miku](https://bowlroll.net/file/4576) is created by tda.