Skip to content

Instantly share code, notes, and snippets.

@bryan1anderson
Last active August 23, 2016 02:38
Show Gist options
  • Select an option

  • Save bryan1anderson/c75afe6609b6189773e3f9dff79c5f2e to your computer and use it in GitHub Desktop.

Select an option

Save bryan1anderson/c75afe6609b6189773e3f9dff79c5f2e to your computer and use it in GitHub Desktop.
Credit goes to Genady Okrain for his work on creating Live Photos from video assets. I was able to use a lot of his code to make sure all of the PHLivePhoto meta-data was working properly: https://github.com/genadyo/LivePhotoDemo
//
// LivePhotoCompressible.swift
// Swop
//
// Created by Bryan on 5/18/16.
// Copyright © 2016 Swop. All rights reserved.
//
import Foundation
import UIKit
import Photos
import ImageIO
import MobileCoreServices
enum LivePhotoQuality: String {
case Low
case Medium
case High
case Highest
var videoAssetQuality: String {
switch self {
case .Low:
return AVAssetExportPresetMediumQuality
case .Medium:
return AVAssetExportPreset640x480
case .High:
return AVAssetExportPreset960x540
case .Highest:
return AVAssetExportPreset1280x720
}
}
var photoLossyCompressionQuality: Int {
switch self {
case .Low:
return 1
case .Medium:
return 3
case .High:
return 5
case .Highest:
return 7
}
}
var photoDPI: Int {
switch self {
case .Low:
return 50
case .Medium:
return 75
case .High:
return 100
case .Highest:
return 125
}
}
var photoPixelSize: Int {
switch self {
case .Low:
return 300
case .Medium:
return 500
case .High:
return 800
case .Highest:
return 1200
}
}
}
protocol LivePhotoCompressible {
func didCompressLivePhoto(quality: LivePhotoQuality, photoURL: NSURL, videoURL: NSURL)
func didFailCompressingLivePhoto(error: String)
}
extension LivePhotoCompressible {
@available(iOS 9.1, *)
func compressLivePhoto(photo: PHLivePhoto, fileName: String, quality: LivePhotoQuality) {
let assetResources = PHAssetResource.assetResourcesForLivePhoto(photo)
guard let photoResource = assetResources.filter({$0.type == .Photo}).first,
videoResource = assetResources.filter({$0.type == .PairedVideo}).first
else { didFailCompressingLivePhoto("missing photo or video asset"); return }
savePhotoAssetResource(photoResource, uniqueFilePathEndComponent: fileName + ".jpg", quality: quality, completion: { (photoURL, filePath) in
self.saveVideoAssetResource(videoResource, quality: quality, uniqueFilePathEndComponent: fileName + ".mov", photoURL: photoURL, photoFilePath: filePath)
})
}
func savePhotoAssetResource(resource: PHAssetResource, uniqueFilePathEndComponent component: String, quality: LivePhotoQuality, completion: (photoURL: NSURL, filePath: String) -> ()) {
let filenameTemp = getDocumentsDirectory().stringByAppendingPathComponent("tempphoto\(quality.rawValue + NSUUID().UUIDString).jpg")
let url = NSURL(fileURLWithPath: filenameTemp)
deleteFileAtPath(filenameTemp)
PHAssetResourceManager.defaultManager().writeDataForAssetResource(resource, toFile: url, options: nil, completionHandler: { (error) in
let filepath = self.getDocumentsDirectory().stringByAppendingPathComponent(component)
self.deleteFileAtPath(filepath)
NSUserDefaults.standardUserDefaults().setURL(url, forKey: "photoURL")
NSUserDefaults.standardUserDefaults().setObject(filepath, forKey: "photoFilepath")
return completion(photoURL: url, filePath: filepath)
})
}
func saveVideoAssetResource(resource: PHAssetResource, quality: LivePhotoQuality, uniqueFilePathEndComponent component: String, photoURL: NSURL, photoFilePath: String) {
let filenameTemp = self.getDocumentsDirectory().stringByAppendingPathComponent("tempphoto.mov")
let url = NSURL(fileURLWithPath: filenameTemp)
deleteFileAtPath(filenameTemp)
PHAssetResourceManager.defaultManager().writeDataForAssetResource(resource, toFile: url, options: nil, completionHandler: { (error) in
let filepath = self.getDocumentsDirectory().stringByAppendingPathComponent(component)
self.deleteFileAtPath(filepath)
let asset = AVURLAsset(URL: url)
guard let exportSession = AVAssetExportSession(asset: asset, presetName: quality.videoAssetQuality) else { return }
exportSession.outputURL = NSURL(fileURLWithPath: filepath)
exportSession.outputFileType = AVFileTypeQuickTimeMovie
exportSession.shouldOptimizeForNetworkUse = true
exportSession.exportAsynchronouslyWithCompletionHandler({
switch exportSession.status {
// case .Cancelled:
// print("cancelled!!!")
// case .Failed:
// print("Failed!!")
// case .Unknown:
// print("Unknown")
case .Cancelled, .Failed, .Unknown:
// print(quality.rawValue)
break
case .Completed:
if let assetIdentifier = self.readAssetIdentifierFor(asset),
jpg = JPEG(url: photoURL, quality: quality) {
jpg.write(photoFilePath, assetIdentifier: assetIdentifier)
self.didCompressLivePhoto(quality, photoURL: NSURL(fileURLWithPath: photoFilePath), videoURL: NSURL(fileURLWithPath: filepath))
}
case .Exporting:
break
case .Waiting:
break
}
})
})
}
func getDocumentsDirectory() -> NSString {
let paths = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true)
let documentsDirectory = paths[0]
let dataPath = documentsDirectory + "/LivePhotos/"
do {
try NSFileManager.defaultManager().createDirectoryAtPath(dataPath, withIntermediateDirectories: false, attributes: nil)
return dataPath
} catch let error as NSError {
// print(error.localizedDescription);
return dataPath
}
}
func deleteFileAtPath(path: String) {
do {
try NSFileManager.defaultManager().removeItemAtPath(path)
} catch {
// print(error)
}
}
func readAssetIdentifierFor(asset: AVAsset) -> String? {
let kKeyContentIdentifier = "com.apple.quicktime.content.identifier"
let kKeySpaceQuickTimeMetadata = "mdta"
for item in asset.metadataForFormat(AVMetadataFormatQuickTimeMetadata) {
if item.key as? String == kKeyContentIdentifier &&
item.keySpace == kKeySpaceQuickTimeMetadata {
return item.value as? String
}
}
return nil
}
}
class JPEG {
private let kFigAppleMakerNote_AssetIdentifier = "17"
private let data: NSData
private let quality: LivePhotoQuality
init?(url : NSURL, quality: LivePhotoQuality = .Low) {
if let udata = NSData(contentsOfURL: url) {
self.data = udata
self.quality = quality
} else {
return nil
}
}
init(data: NSData, quality: LivePhotoQuality = .Low) {
self.data = data
self.quality = quality
}
func read() -> String? {
guard let makerNote = metadata()?.objectForKey(kCGImagePropertyMakerAppleDictionary) as! NSDictionary? else {
return nil }
return makerNote.objectForKey(kFigAppleMakerNote_AssetIdentifier) as! String?
}
func write(dest : String, assetIdentifier : String) {
guard let dest = CGImageDestinationCreateWithURL(NSURL(fileURLWithPath: dest), kUTTypeJPEG, 1, nil)
else { return }
defer { CGImageDestinationFinalize(dest) }
guard let imageSource = self.imageSource else { return }
guard let metadata = self.metadata()?.mutableCopy() as! NSMutableDictionary! else { return }
let makerNote = NSMutableDictionary()
makerNote.setObject(assetIdentifier, forKey: kFigAppleMakerNote_AssetIdentifier)
metadata.setObject(makerNote, forKey: kCGImagePropertyMakerAppleDictionary as String)
metadata.setValue(quality.photoLossyCompressionQuality, forKey: kCGImageDestinationLossyCompressionQuality as String)
metadata.setValue(quality.photoDPI, forKey: kCGImagePropertyDPIWidth as String)
metadata.setValue(quality.photoDPI, forKey: kCGImagePropertyDPIHeight as String)
metadata.setValue(quality.photoPixelSize, forKey: kCGImageDestinationImageMaxPixelSize as String)
// kCGImageSourceThumbnailMaxPixelSize
CGImageDestinationAddImageFromSource(dest, imageSource, 0, metadata)
}
private func metadata() -> NSDictionary? {
return self.imageSource.flatMap {
CGImageSourceCopyPropertiesAtIndex($0, 0, nil) as NSDictionary?
}
}
var imageSource: CGImageSource? {
return CGImageSourceCreateWithData(data, nil)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment