diff --git a/LoveLiver-osx/AppDelegate.swift b/LoveLiver-osx/AppDelegate.swift
new file mode 100644
index 0000000..5ca2361
--- /dev/null
+++ b/LoveLiver-osx/AppDelegate.swift
@@ -0,0 +1,27 @@
+//
+// AppDelegate.swift
+// LoveLiver-osx
+//
+// Created by BAN Jun on 2016/02/08.
+// Copyright © 2016 mzp. All rights reserved.
+//
+
+import Cocoa
+import AVFoundation
+import AVKit
+import NorthLayout
+import Ikemen
+
+
+@NSApplicationMain
+class AppDelegate: NSObject, NSApplicationDelegate {
+ func applicationOpenUntitledFile(sender: NSApplication) -> Bool {
+ openDocument(sender)
+ return true
+ }
+
+ @objc private func openDocument(sender: AnyObject?) {
+ NSDocumentController.sharedDocumentController().openDocument(sender)
+ }
+}
+
diff --git a/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/Contents.json b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..441d0c2
--- /dev/null
+++ b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+ "images" : [
+ {
+ "size" : "16x16",
+ "idiom" : "mac",
+ "filename" : "icon16.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "16x16",
+ "idiom" : "mac",
+ "filename" : "icon16@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "32x32",
+ "idiom" : "mac",
+ "filename" : "icon32.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "32x32",
+ "idiom" : "mac",
+ "filename" : "icon32@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "128x128",
+ "idiom" : "mac",
+ "filename" : "icon128.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "128x128",
+ "idiom" : "mac",
+ "filename" : "icon128@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "256x256",
+ "idiom" : "mac",
+ "filename" : "icon256.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "256x256",
+ "idiom" : "mac",
+ "filename" : "icon256@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "512x512",
+ "idiom" : "mac",
+ "filename" : "icon512.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "512x512",
+ "idiom" : "mac",
+ "filename" : "icon512@2x.png",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon128.png b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon128.png
new file mode 100644
index 0000000..a445c12
Binary files /dev/null and b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon128.png differ
diff --git a/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon128@2x.png b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon128@2x.png
new file mode 100644
index 0000000..7b6d938
Binary files /dev/null and b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon128@2x.png differ
diff --git a/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon16.png b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon16.png
new file mode 100644
index 0000000..133e418
Binary files /dev/null and b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon16.png differ
diff --git a/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon16@2x.png b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon16@2x.png
new file mode 100644
index 0000000..2d2e351
Binary files /dev/null and b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon16@2x.png differ
diff --git a/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon256.png b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon256.png
new file mode 100644
index 0000000..7b6d938
Binary files /dev/null and b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon256.png differ
diff --git a/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon256@2x.png b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon256@2x.png
new file mode 100644
index 0000000..43025cb
Binary files /dev/null and b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon256@2x.png differ
diff --git a/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon32.png b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon32.png
new file mode 100644
index 0000000..2d2e351
Binary files /dev/null and b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon32.png differ
diff --git a/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon32@2x.png b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon32@2x.png
new file mode 100644
index 0000000..759a53b
Binary files /dev/null and b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon32@2x.png differ
diff --git a/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon512.png b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon512.png
new file mode 100644
index 0000000..43025cb
Binary files /dev/null and b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon512.png differ
diff --git a/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon512@2x.png b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon512@2x.png
new file mode 100644
index 0000000..4d23f8c
Binary files /dev/null and b/LoveLiver-osx/Assets.xcassets/AppIcon.appiconset/icon512@2x.png differ
diff --git a/LoveLiver-osx/Assets.xcassets/Contents.json b/LoveLiver-osx/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..da4a164
--- /dev/null
+++ b/LoveLiver-osx/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/LoveLiver-osx/Base.lproj/MainMenu.xib b/LoveLiver-osx/Base.lproj/MainMenu.xib
new file mode 100644
index 0000000..002c785
--- /dev/null
+++ b/LoveLiver-osx/Base.lproj/MainMenu.xib
@@ -0,0 +1,667 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LoveLiver-osx/Info.plist b/LoveLiver-osx/Info.plist
new file mode 100644
index 0000000..9e04103
--- /dev/null
+++ b/LoveLiver-osx/Info.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleDocumentTypes
+
+
+ CFBundleTypeExtensions
+
+ CFBundleTypeMIMETypes
+
+ CFBundleTypeName
+ Movie
+ CFBundleTypeRole
+ Viewer
+ LSItemContentTypes
+
+ public.movie
+
+ LSTypeIsPackage
+ 0
+ NSDocumentClass
+ LoveLiver.MovieDocument
+
+
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIconFile
+
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+ LSMinimumSystemVersion
+ $(MACOSX_DEPLOYMENT_TARGET)
+ NSHumanReadableCopyright
+ Copyright © 2016 mzp. All rights reserved.
+ NSMainNibFile
+ MainMenu
+ NSPrincipalClass
+ NSApplication
+
+
diff --git a/LoveLiver-osx/MovieDocument.swift b/LoveLiver-osx/MovieDocument.swift
new file mode 100644
index 0000000..7fab1f3
--- /dev/null
+++ b/LoveLiver-osx/MovieDocument.swift
@@ -0,0 +1,23 @@
+//
+// MovieDocument.swift
+// LoveLiver
+//
+// Created by BAN Jun on 2016/02/09.
+// Copyright © 2016 mzp. All rights reserved.
+//
+
+import Cocoa
+
+
+class MovieDocument: NSDocument {
+ override func readFromURL(url: NSURL, ofType typeName: String) throws {
+ NSLog("%@", "opening \(url)")
+ }
+
+ override func makeWindowControllers() {
+ let vc = MovieDocumentViewController(movieURL: fileURL!)
+ let window = NSWindow(contentViewController: vc)
+ let wc = NSWindowController(window: window)
+ addWindowController(wc)
+ }
+}
diff --git a/LoveLiver-osx/MovieDocumentViewController.swift b/LoveLiver-osx/MovieDocumentViewController.swift
new file mode 100644
index 0000000..3e4e411
--- /dev/null
+++ b/LoveLiver-osx/MovieDocumentViewController.swift
@@ -0,0 +1,195 @@
+//
+// MovieDocumentViewController.swift
+// LoveLiver
+//
+// Created by BAN Jun on 2016/02/09.
+// Copyright © 2016 mzp. All rights reserved.
+//
+
+import Cocoa
+import AVFoundation
+import AVKit
+import NorthLayout
+import Ikemen
+
+
+private let outputDir = NSURL(fileURLWithPath: NSHomeDirectory()).URLByAppendingPathComponent("Pictures/LoveLiver")
+
+
+class MovieDocumentViewController: NSViewController {
+ private let player: AVPlayer
+ private let playerItem: AVPlayerItem
+ private let imageGenerator: AVAssetImageGenerator
+ private var exportSession: AVAssetExportSession?
+ private var posterFrameTime: CMTime?
+
+ private let playerView: AVPlayerView = AVPlayerView() ※ { v in
+ v.controlsStyle = .Floating
+ v.showsFrameSteppingButtons = true
+ }
+ private let posterFrameView = NSImageView() ※ { v in
+ v.imageScaling = .ScaleProportionallyUpOrDown
+ v.wantsLayer = true
+ v.layer?.backgroundColor = NSColor.blackColor().CGColor
+ v.setContentCompressionResistancePriority(NSLayoutPriorityFittingSizeCompression, forOrientation: .Horizontal)
+ v.setContentCompressionResistancePriority(NSLayoutPriorityFittingSizeCompression, forOrientation: .Vertical)
+ }
+ private lazy var posterFrameButton: NSButton = NSButton() ※ { b in
+ b.title = "Poster Frame ->>"
+ b.setButtonType(.MomentaryLightButton)
+ b.bezelStyle = .RoundedBezelStyle
+ b.target = self
+ b.action = "capturePosterFrame:"
+ }
+
+ private lazy var positionsLabel: NSTextField = NSTextField() ※ { tf in
+ tf.bezeled = false
+ tf.editable = false
+ tf.drawsBackground = false
+ tf.textColor = NSColor.grayColor()
+ }
+
+ private lazy var createLivePhotoButton: NSButton = NSButton() ※ { b in
+ b.title = "Create Live Photo"
+ b.setButtonType(.MomentaryLightButton)
+ b.bezelStyle = .RoundedBezelStyle
+ b.target = self
+ b.action = "createLivePhoto:"
+ }
+
+ init!(movieURL: NSURL) {
+ playerItem = AVPlayerItem(URL: movieURL)
+ player = AVPlayer(playerItem: playerItem)
+ playerView.player = player
+ imageGenerator = AVAssetImageGenerator(asset: playerItem.asset) ※ {
+ $0.requestedTimeToleranceBefore = kCMTimeZero
+ $0.requestedTimeToleranceAfter = kCMTimeZero
+ }
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func loadView() {
+ view = NSView(frame: NSRect(x: 0, y: 0, width: 500, height: 500))
+
+ let autolayout = view.northLayoutFormat(["p": 20], [
+ "player": playerView,
+ "posterButton": posterFrameButton,
+ "posterView": posterFrameView,
+ "createLivePhoto": createLivePhotoButton,
+ "positionsLabel": positionsLabel,
+ ])
+ autolayout("H:|-p-[player]-p-[posterView(==player)]-p-|")
+ autolayout("H:|-p-[posterButton(==player)]-p-[createLivePhoto(<=player)]-p-|")
+ autolayout("H:[positionsLabel(==createLivePhoto)]-p-|")
+ autolayout("V:|-p-[player]-p-[posterButton]")
+ autolayout("V:|-p-[posterView]-p-[posterButton]")
+ autolayout("V:[posterButton]-p-|")
+ autolayout("V:[posterView][positionsLabel][createLivePhoto]-p-|")
+
+ setupAspectRatioConstraints()
+ updateViews()
+ }
+
+ private func setupAspectRatioConstraints() {
+ // wait until movie is loaded
+ guard playerView.videoBounds.width > 0 && playerView.videoBounds.height > 0 else {
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
+ self.setupAspectRatioConstraints()
+ }
+ return
+ }
+
+ self.playerView.addConstraint(NSLayoutConstraint(
+ item: self.playerView, attribute: .Width, relatedBy: .Equal,
+ toItem: self.playerView, attribute: .Height, multiplier: self.playerView.videoBounds.width / self.playerView.videoBounds.height, constant: 0))
+ }
+
+ private func updateViews() {
+ createLivePhotoButton.enabled = (posterFrameView.image != nil && exportSession == nil)
+ positionsLabel.stringValue = positionsLabelText
+ }
+
+ private var positionsLabelText: String {
+ guard let time = posterFrameTime else { return "" }
+ let duration = CMTimeGetSeconds(time)
+ let minutes = Int(floor(duration / 60))
+ let seconds = Int(floor(duration - Double(minutes) * 60))
+ let milliseconds = Int((duration - floor(duration)) * 100)
+ let timeString = String(format: "%02d:%02d.%02d", minutes, seconds, milliseconds)
+ return "Poster Frame: \(timeString)"
+ }
+
+ @objc private func capturePosterFrame(sender: AnyObject?) {
+ guard let cgImage = try? imageGenerator.copyCGImageAtTime(player.currentTime(), actualTime: nil) else { return }
+ let image = NSImage(CGImage: cgImage, size: CGSize(width: CGImageGetWidth(cgImage), height: CGImageGetHeight(cgImage)))
+ posterFrameView.image = image
+ posterFrameTime = player.currentTime()
+ updateViews()
+ }
+
+ @objc private func createLivePhoto(sender: AnyObject?) {
+ guard let image = posterFrameView.image else { return }
+
+ guard let _ = try? NSFileManager.defaultManager().createDirectoryAtPath(outputDir.path!, withIntermediateDirectories: true, attributes: nil) else { return }
+
+ let assetIdentifier = NSUUID().UUIDString
+ let tmpImagePath = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent("\(assetIdentifier).tiff").path!
+ let tmpMoviePath = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent("\(assetIdentifier).mov").path!
+ let imagePath = outputDir.URLByAppendingPathComponent("\(assetIdentifier).JPG").path!
+ let moviePath = outputDir.URLByAppendingPathComponent("\(assetIdentifier).MOV").path!
+ let paths = [tmpImagePath, tmpMoviePath, imagePath, moviePath]
+
+ for path in paths {
+ guard !NSFileManager.defaultManager().fileExistsAtPath(path) else { return }
+ }
+
+ guard image.TIFFRepresentation?.writeToFile(tmpImagePath, atomically: true) == true else { return }
+ // create AVAssetExportSession each time because it cannot be reused after export completion
+ guard let session = AVAssetExportSession(asset: playerItem.asset, presetName: AVAssetExportPresetPassthrough) else { return }
+ session.outputFileType = "com.apple.quicktime-movie"
+ session.outputURL = NSURL(fileURLWithPath: tmpMoviePath)
+ session.timeRange = CMTimeRange(start: player.currentTime(), duration: CMTime(value: 3*600, timescale: 600))
+ session.exportAsynchronouslyWithCompletionHandler {
+ dispatch_async(dispatch_get_main_queue()) {
+ switch session.status {
+ case .Completed:
+ JPEG(path: tmpImagePath).write(imagePath, assetIdentifier: assetIdentifier)
+ NSLog("%@", "LivePhoto JPEG created: \(imagePath)")
+
+ QuickTimeMov(path: tmpMoviePath).write(moviePath, assetIdentifier: assetIdentifier)
+ NSLog("%@", "LivePhoto MOV created: \(moviePath)")
+
+ self.showInFinderAndOpenInPhotos([imagePath, moviePath].map{NSURL(fileURLWithPath: $0)})
+ case .Cancelled, .Exporting, .Failed, .Unknown, .Waiting:
+ NSLog("%@", "exportAsynchronouslyWithCompletionHandler = \(session.status)")
+ }
+
+ for path in [tmpImagePath, tmpMoviePath] {
+ let _ = try? NSFileManager.defaultManager().removeItemAtPath(path)
+ }
+ self.exportSession = nil
+ self.updateViews()
+ }
+ }
+ exportSession = session
+ updateViews()
+ }
+
+ private func showInFinderAndOpenInPhotos(fileURLs: [NSURL]) {
+ NSWorkspace.sharedWorkspace().activateFileViewerSelectingURLs(fileURLs)
+
+ // wait until Finder is active or timed out,
+ // to avoid openURLs overtaking Finder activation
+ dispatch_async(dispatch_get_global_queue(0, 0)) {
+ let start = NSDate()
+ while NSWorkspace.sharedWorkspace().frontmostApplication?.bundleIdentifier != "com.apple.finder" && NSDate().timeIntervalSinceDate(start) < 5 {
+ NSThread.sleepForTimeInterval(0.1)
+ }
+ NSWorkspace.sharedWorkspace().openURLs(fileURLs, withAppBundleIdentifier: "com.apple.Photos", options: [], additionalEventParamDescriptor: nil, launchIdentifiers: nil)
+ }
+ }
+}
diff --git a/LoveLiver.xcodeproj/project.pbxproj b/LoveLiver.xcodeproj/project.pbxproj
index 119836d..3da3df4 100644
--- a/LoveLiver.xcodeproj/project.pbxproj
+++ b/LoveLiver.xcodeproj/project.pbxproj
@@ -8,11 +8,19 @@
/* 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 */; settings = {ASSET_TAGS = (); }; };
- 133DFE381BC8C3E000902407 /* Option.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DFE351BC8C3E000902407 /* Option.swift */; settings = {ASSET_TAGS = (); }; };
- 133DFE391BC8C3E000902407 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DFE361BC8C3E000902407 /* StringExtensions.swift */; settings = {ASSET_TAGS = (); }; };
- 133DFE3B1BC8C69900902407 /* JPEG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DFE3A1BC8C69900902407 /* JPEG.swift */; settings = {ASSET_TAGS = (); }; };
- 133DFE3D1BC8C6EF00902407 /* QuickTimeMov.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DFE3C1BC8C6EF00902407 /* QuickTimeMov.swift */; settings = {ASSET_TAGS = (); }; };
+ 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 */; };
+ EA379AB91C68DE6D00106AEF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA379AB81C68DE6D00106AEF /* AppDelegate.swift */; };
+ EA379ABB1C68DE6D00106AEF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EA379ABA1C68DE6D00106AEF /* Assets.xcassets */; };
+ EA379ABE1C68DE6D00106AEF /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = EA379ABC1C68DE6D00106AEF /* MainMenu.xib */; };
+ EA379ADA1C6A283500106AEF /* MovieDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA379AD81C6A283500106AEF /* MovieDocument.swift */; };
+ EA379ADD1C6A2E8800106AEF /* MovieDocumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA379ADC1C6A2E8800106AEF /* MovieDocumentViewController.swift */; };
+ EA379ADE1C6A33A500106AEF /* JPEG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DFE3A1BC8C69900902407 /* JPEG.swift */; };
+ EA379ADF1C6A33A700106AEF /* QuickTimeMov.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133DFE3C1BC8C6EF00902407 /* QuickTimeMov.swift */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -36,6 +44,16 @@
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; };
+ 4F0CA317E59A557EE5DD45B6 /* Pods-LoveLiver-osx.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LoveLiver-osx.release.xcconfig"; path = "Pods/Target Support Files/Pods-LoveLiver-osx/Pods-LoveLiver-osx.release.xcconfig"; sourceTree = ""; };
+ DCD5B633553004CEA3DAF5F6 /* Pods-LoveLiver-osx.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LoveLiver-osx.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LoveLiver-osx/Pods-LoveLiver-osx.debug.xcconfig"; sourceTree = ""; };
+ EA379AB61C68DE6D00106AEF /* LoveLiver.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LoveLiver.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ EA379AB81C68DE6D00106AEF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ EA379ABA1C68DE6D00106AEF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ EA379ABD1C68DE6D00106AEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
+ EA379ABF1C68DE6D00106AEF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ EA379AD81C6A283500106AEF /* MovieDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MovieDocument.swift; sourceTree = ""; };
+ EA379ADC1C6A2E8800106AEF /* MovieDocumentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MovieDocumentViewController.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -46,14 +64,34 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ EA379AB31C68DE6D00106AEF /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ C2C41F0CC51A39D97981D73D /* Pods_LoveLiver_osx.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 0D762A778661040BF90B4214 /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ DCD5B633553004CEA3DAF5F6 /* Pods-LoveLiver-osx.debug.xcconfig */,
+ 4F0CA317E59A557EE5DD45B6 /* Pods-LoveLiver-osx.release.xcconfig */,
+ );
+ name = Pods;
+ sourceTree = "";
+ };
133DFE1C1BC8B71100902407 = {
isa = PBXGroup;
children = (
133DFE271BC8B71100902407 /* LoveLiver */,
+ EA379AB71C68DE6D00106AEF /* LoveLiver-osx */,
133DFE261BC8B71100902407 /* Products */,
+ 0D762A778661040BF90B4214 /* Pods */,
+ A5911014E6D1DEA080967902 /* Frameworks */,
);
sourceTree = "";
};
@@ -61,6 +99,7 @@
isa = PBXGroup;
children = (
133DFE251BC8B71100902407 /* LoveLiver */,
+ EA379AB61C68DE6D00106AEF /* LoveLiver.app */,
);
name = Products;
sourceTree = "";
@@ -87,6 +126,27 @@
path = CommandLine;
sourceTree = "";
};
+ A5911014E6D1DEA080967902 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 3B34746BF27B18F34F5E26C5 /* Pods_LoveLiver_osx.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ EA379AB71C68DE6D00106AEF /* LoveLiver-osx */ = {
+ isa = PBXGroup;
+ children = (
+ EA379AB81C68DE6D00106AEF /* AppDelegate.swift */,
+ EA379AD81C6A283500106AEF /* MovieDocument.swift */,
+ EA379ADC1C6A2E8800106AEF /* MovieDocumentViewController.swift */,
+ EA379ABA1C68DE6D00106AEF /* Assets.xcassets */,
+ EA379ABC1C68DE6D00106AEF /* MainMenu.xib */,
+ EA379ABF1C68DE6D00106AEF /* Info.plist */,
+ );
+ path = "LoveLiver-osx";
+ sourceTree = "";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -107,18 +167,42 @@
productReference = 133DFE251BC8B71100902407 /* LoveLiver */;
productType = "com.apple.product-type.tool";
};
+ EA379AB51C68DE6D00106AEF /* LoveLiver-osx */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = EA379AC21C68DE6D00106AEF /* Build configuration list for PBXNativeTarget "LoveLiver-osx" */;
+ buildPhases = (
+ 84A9B9667A59E6E73A3C7C91 /* Check Pods Manifest.lock */,
+ EA379AB21C68DE6D00106AEF /* Sources */,
+ EA379AB31C68DE6D00106AEF /* Frameworks */,
+ EA379AB41C68DE6D00106AEF /* Resources */,
+ B6AC598A4BA044ACC8349FBC /* Embed Pods Frameworks */,
+ 2E4D54806FC3D28A38335C53 /* Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "LoveLiver-osx";
+ productName = "LoveLiver-osx";
+ productReference = EA379AB61C68DE6D00106AEF /* LoveLiver.app */;
+ productType = "com.apple.product-type.application";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
133DFE1D1BC8B71100902407 /* Project object */ = {
isa = PBXProject;
attributes = {
+ LastSwiftUpdateCheck = 0720;
LastUpgradeCheck = 0700;
ORGANIZATIONNAME = mzp;
TargetAttributes = {
133DFE241BC8B71100902407 = {
CreatedOnToolsVersion = 7.0.1;
};
+ EA379AB51C68DE6D00106AEF = {
+ CreatedOnToolsVersion = 7.2;
+ };
};
};
buildConfigurationList = 133DFE201BC8B71100902407 /* Build configuration list for PBXProject "LoveLiver" */;
@@ -127,6 +211,7 @@
hasScannedForEncodings = 0;
knownRegions = (
en,
+ Base,
);
mainGroup = 133DFE1C1BC8B71100902407;
productRefGroup = 133DFE261BC8B71100902407 /* Products */;
@@ -134,10 +219,71 @@
projectRoot = "";
targets = (
133DFE241BC8B71100902407 /* LoveLiver */,
+ EA379AB51C68DE6D00106AEF /* LoveLiver-osx */,
);
};
/* End PBXProject section */
+/* Begin PBXResourcesBuildPhase section */
+ EA379AB41C68DE6D00106AEF /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ EA379ABB1C68DE6D00106AEF /* Assets.xcassets in Resources */,
+ EA379ABE1C68DE6D00106AEF /* MainMenu.xib in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 2E4D54806FC3D28A38335C53 /* Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LoveLiver-osx/Pods-LoveLiver-osx-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 84A9B9667A59E6E73A3C7C91 /* Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Check Pods Manifest.lock";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
+ showEnvVarsInLog = 0;
+ };
+ B6AC598A4BA044ACC8349FBC /* Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Embed Pods Frameworks";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LoveLiver-osx/Pods-LoveLiver-osx-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
/* Begin PBXSourcesBuildPhase section */
133DFE211BC8B71100902407 /* Sources */ = {
isa = PBXSourcesBuildPhase;
@@ -152,8 +298,31 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ EA379AB21C68DE6D00106AEF /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ EA379AB91C68DE6D00106AEF /* AppDelegate.swift in Sources */,
+ EA379ADF1C6A33A700106AEF /* QuickTimeMov.swift in Sources */,
+ EA379ADA1C6A283500106AEF /* MovieDocument.swift in Sources */,
+ EA379ADD1C6A2E8800106AEF /* MovieDocumentViewController.swift in Sources */,
+ EA379ADE1C6A33A500106AEF /* JPEG.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
+/* Begin PBXVariantGroup section */
+ EA379ABC1C68DE6D00106AEF /* MainMenu.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ EA379ABD1C68DE6D00106AEF /* Base */,
+ );
+ name = MainMenu.xib;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
/* Begin XCBuildConfiguration section */
133DFE2A1BC8B71100902407 /* Debug */ = {
isa = XCBuildConfiguration;
@@ -247,6 +416,34 @@
};
name = Release;
};
+ EA379AC01C68DE6D00106AEF /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = DCD5B633553004CEA3DAF5F6 /* Pods-LoveLiver-osx.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "-";
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = "LoveLiver-osx/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = "jp.mzp.LoveLiver-osx";
+ PRODUCT_NAME = LoveLiver;
+ };
+ name = Debug;
+ };
+ EA379AC11C68DE6D00106AEF /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 4F0CA317E59A557EE5DD45B6 /* Pods-LoveLiver-osx.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "-";
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = "LoveLiver-osx/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = "jp.mzp.LoveLiver-osx";
+ PRODUCT_NAME = LoveLiver;
+ };
+ name = Release;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -268,6 +465,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ EA379AC21C68DE6D00106AEF /* Build configuration list for PBXNativeTarget "LoveLiver-osx" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ EA379AC01C68DE6D00106AEF /* Debug */,
+ EA379AC11C68DE6D00106AEF /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
};
rootObject = 133DFE1D1BC8B71100902407 /* Project object */;
diff --git a/LoveLiver.xcodeproj/xcshareddata/xcschemes/LoveLiver-osx.xcscheme b/LoveLiver.xcodeproj/xcshareddata/xcschemes/LoveLiver-osx.xcscheme
new file mode 100644
index 0000000..fc3ed02
--- /dev/null
+++ b/LoveLiver.xcodeproj/xcshareddata/xcschemes/LoveLiver-osx.xcscheme
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LoveLiver.xcworkspace/contents.xcworkspacedata b/LoveLiver.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..d9b19b7
--- /dev/null
+++ b/LoveLiver.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/Podfile b/Podfile
new file mode 100644
index 0000000..8d56157
--- /dev/null
+++ b/Podfile
@@ -0,0 +1,12 @@
+platform :osx, '10.11'
+use_frameworks!
+
+target 'LoveLiver' do
+
+end
+
+target 'LoveLiver-osx' do
+ pod 'NorthLayout'
+ pod '※ikemen'
+end
+
diff --git a/README.md b/README.md
index 5ef859c..bc2ebba 100644
--- a/README.md
+++ b/README.md
@@ -2,23 +2,39 @@
![live photo demo](https://raw.githubusercontent.com/mzp/LoveLiver/master/demo.gif)
-LoveLiver is a CLI tool to create Apple's Live Photos from JPEG and MOV.
+LoveLiver is a Mac OS X GUI application and a CLI tool to create Apple's Live Photos from JPEG and MOV.
## Requirements
* MacOS X 10.11 (El Capitan)
* Photos.app
-## Install
+## GUI application
+
+### Install
+
+1. Go to the [releases page](https://github.com/mzp/LoveLiver/releases), find the version you want.
+2. Download the file.
+
+### Usage
+
+1. Open movie files on LiveLover.app
+2. Set poster frame
+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
+### Usage
-### Create Live Photos
+#### Create Live Photos
```
$ ./LoveLiver --operation=livephoto --jpeg sample/original/IMG.JPG --mov sample/original/IMG.MOV --output sample/livephoto
@@ -27,14 +43,14 @@ finish writing.
and drop & drag `sample/livephoto` directory to `Photos.app`.
-### Show metadata of JPEG
+#### Show metadata of JPEG
```
$ ./LoveLiver --operation=jpeg --jpeg sample/livephoto/IMG.JPG
asset identifier: CDDD4450-642F-442B-8371-B46BC4229XXY
```
-### Show metadata of QuickTime MOV
+#### Show metadata of QuickTime MOV
```
$ ./LoveLiver --operation=mov --mov sample/livephoto/IMG.MOV
diff --git a/fastlane/Gymfile b/fastlane/Gymfile
new file mode 100644
index 0000000..bedd13e
--- /dev/null
+++ b/fastlane/Gymfile
@@ -0,0 +1,4 @@
+scheme "LoveLiver-osx"
+clean true
+codesigning_identity "Developer ID Application"
+output_directory "./build"