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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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"