Saving Already Created Live Photos - ios

I've got some live photos created JPEG and MOV files, now i want to import them into the app that would allow the user to save the live photos to their photo library. How can i go about doing this?
I've looked into this: https://github.com/genadyo/LivePhotoDemoSwift Which basically allows you to record video and turn it into a live photo. But since i've already created the "live photos", can i save them to the camera roll right away or do i need to follow a different route?

You can create a LivePhoto from separate elements from a LivePhoto by using PHLivePhoto.requestLivePhotoWithResourceFileURLs, you will then be able to save it to the library.
func makeLivePhotoFromItems(imageURL: NSURL, videoURL: NSURL, previewImage: UIImage, completion: (livePhoto: PHLivePhoto) -> Void) {
PHLivePhoto.requestLivePhotoWithResourceFileURLs([imageURL, videoURL], placeholderImage: previewImage, targetSize: CGSizeZero, contentMode: PHImageContentMode.AspectFit) {
(livePhoto, infoDict) -> Void in
if let lp = livePhoto {
completion(livePhoto: lp)
}
}
}
makeLivePhotoFromItems(imgURL, videoURL: movURL, previewImage: prevImg) { (livePhoto) -> Void in
// "livePhoto" is your PHLivePhoto object, save it/use it here
}
You will need the JPEG file URL, the MOV file URL, and a "preview" image (which is usually just the JPEG or a lighter version of it).
Full example working in a Playground here.

Related

iOS: How to extract thumbnail and metadata from photo file on disk

Apple doc here https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/capturing_still_and_live_photos/capturing_thumbnail_and_preview_images explains how to capture thubnails.
At the bottom, it says
If you requested an embedded thumbnail image, that image isn't
directly accessible from the AVCapturePhoto object—it's embedded in
the image file data that you get by calling the photo object's
fileDataRepresentation() method.
Seems impossible to separate the embedded thumbnail from the main photo. So what is the meaning of embedded thumbnail?
I want to save the AVCapturePhoto in JPG and raw DNG (requested embedded thumbnails for both) to App's Documents directory (I do not use PhotoKit) and then load it back to a UIImageView.
I save a photo like this:
if let data = capturePhoto.fileDataRepresentation() {
data.write(to: documentsPath, options: [])
}
And load it back to a UIImage like this:
if let data = FileManager.default.contents(at: path) {
let image = UIImage(data: data)
}
But it will be better to load the embedded thubmail first. If a user clicks to see the large image, load the full imgage file then.
I also want to show the metadata, e.g., GPS location, flash status, ISO, shutter speed etc. I wonder how to do that.
AVFoundation's AVAsset wraps some metadata types, but apparently not EXIF data. If you want the thumbnail you have to use the CoreGraphics framework. This function fetches the thumbnail if present, limiting the maximum side length to 512 pixels.
public func getImageThumbnail(url: URL) -> CGImage? {
guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil) else { return nil }
let thumbnailOptions: [String: Any] = [
kCGImageSourceCreateThumbnailWithTransform as String: true,
kCGImageSourceCreateThumbnailFromImageIfAbsent as String: false, // true will create if thumbnail not present
kCGImageSourceThumbnailMaxPixelSize as String: 512
]
return CGImageSourceCreateThumbnailAtIndex(imageSource, 0, thumbnailOptions as CFDictionary);
}
For all the rest of the metadata, you can use CGImageSourceCopyPropertiesAtIndex or CGImageSourceCopyMetadataAtIndex.

Share PHAsset stored in camera roll using UIActivityViewController

I'm storing images and videos in a Camera Roll album using PhotoKit, and want to allow the user to share them using UIActivityViewController. If I pass UIActivityViewController a UIImage instance, it works as expected, probably because the image data is passed in memory. However, videos need to be passed by URL because there's no video analogue to UIImage. When I pass a URL to a video, I get an error "Could not create sandbox extension". If I pass a URL to an image, I get a similar error.
Based on this, it seems as though I might be able to get around this error by exporting the assets to the Documents directory, and passing UIActivityViewController the URL to the asset in Documents. However, I've read elsewhere that the Camera Roll can serve a similar purpose, and it goes to reason that the Camera Roll would be one of the few places that can hold data for sharing between apps.
Is there a way to pass UIActivityViewController URLs to Camera Roll assets without copying them to Documents? Is there a better way to be sharing images and video that are already in Camera Roll?
Implementation Details:
I'm generating URLs for assets using this:
func videoFor(asset: PHAsset, resultHander: #escaping (AVAsset?, AVAudioMix?, [AnyHashable : Any]?) -> Void) {
imageManager.requestAVAsset(forVideo: asset, options: nil, resultHandler: resultHander)
}
func urlFor(asset: PHAsset, resultHandler: #escaping (URL?) -> Void) {
if ( asset.mediaType == .video ) {
videoFor(asset: asset) { (asset, audioMix, info) in
let asset = asset as! AVURLAsset
resultHandler(asset.url)
}
}
else if ( asset.mediaType == .image ) {
let options: PHContentEditingInputRequestOptions = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
asset.requestContentEditingInput(with: options, completionHandler: {(contentEditingInput: PHContentEditingInput?, info: [AnyHashable : Any]) -> Void in
resultHandler(contentEditingInput!.fullSizeImageURL as URL?)
})
}
else {
resultHandler(nil)
}
}
Here is the full error I get in console when trying to share an image by URL:
Failed to determine whether URL /var/mobile/Media/DCIM/100APPLE/IMG_0201.JPG (n) is managed by a file provider
Could not create sandbox extension. Error: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedDescription=Could not create sandbox extension of type com.apple.app-sandbox.read for URL /var/mobile/Media/DCIM/100APPLE/IMG_0201.JPG. Error: No such file or directory}
... and for a video:
Failed to determine whether URL /var/mobile/Media/DCIM/100APPLE/IMG_0202.M4V (n) is managed by a file provider
Could not create sandbox extension. Error: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedDescription=Could not create sandbox extension of type com.apple.app-sandbox.read for URL /var/mobile/Media/DCIM/100APPLE/IMG_0202.M4V. Error: Operation not permitted}
I was stuck on the same problem today. Here is my solution. Hope this helps or guides you to the right path.
PHImageManager.default().requestExportSession(forVideo: video, options: nil, exportPreset: AVAssetExportPresetPassthrough) { (exportSession, nil) in
if let exportSession = exportSession {
exportSession.outputURL = destinationURLForFile
exportSession.outputFileType = AVFileType.m4v
exportSession.exportAsynchronously() {
// Load the share sheet using destinationURLForFile
}
}
}
What this does is export the video to the provided location destinationURLForFile (i used the Documents directory. Make sure you delete the file if its already there otherwise the export MAY not work cause it may not override the file).
You can set the type based on available types. I needed m4v.
Then, export async and just call the share sheet or whatever sharing mechanism you have.

How to save JPEG/RAW image data to camera roll in iOS 11, can't access processed image data

I'm attempting to write a photoapp that can take both RAW and JPEG images and save them to the camera roll. The functions jpegPhotoDataRepresentation and dngPhotoDataRepresentation seem to be the key to all examples I've found, however both of these are deprecated in iOS 11 and the function for saving after "capturePhoto" is now
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
The main example I've been able to find of a working RAW iOS11 app is this:
https://ubunifu.co/swift/raw-photo-capture-sample-swift-4-ios-11
which works, however it only shoots RAW and saving is clumsy because it's not on the camera roll.
I've changed my photo settings to allow for both raw and processed capture with this line
photoSettings = AVCapturePhotoSettings(rawPixelFormatType: availableRawFormat.uint32Value, processedFormat: [AVVideoCodecKey : AVVideoCodecType.jpeg])
But once I've actually captured the photo I have no idea how to access the processedFormat data. fileDataRepresentation seems to be the only way to access the dng stuff, but there's no way to get at the jpeg separately? The code I've found from Apple pre-iOS11 suggests to use PHPhotoLibrary and add a resource, but this requires a data representation which I'm unable to access other than as a dng file, which when saved to the library is just white because the library is not able to handle RAW files. Here's my photoOutput code in case it helps.
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
let dir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! as String
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMddHHmmss"
formatter.locale = Locale.init(identifier: "en_US_POSIX")
let filePath = dir.appending(String(format: "/%#.dng", formatter.string(from: Date())))
let dngFileURL = URL(fileURLWithPath: filePath)
let dngData = photo.fileDataRepresentation()!
do {
try dngData.write(to: dngFileURL, options: [])
} catch {
print("Unable to write DNG file.")
return
}
PHPhotoLibrary.shared().performChanges( {
let creationRequest = PHAssetCreationRequest.forAsset()
let creationOptions = PHAssetResourceCreationOptions()
creationOptions.shouldMoveFile = true
//dngData is the problem, this should be the jpeg representation
creationRequest.addResource(with: .photo, data: dngData, options: nil)
//This line works fine, the associated file is the correct RAW file, but the jpeg preview is garbage
creationRequest.addResource(with: .alternatePhoto, fileURL: dngFileURL, options: creationOptions)
}, completionHandler: nil)
}
Okay, following up on comment from earlier and the Apple docs on Capturing Photos in RAW Format:
As you’ve noticed, if you want to shoot RAW and save it in the Photos library, you need to save DNG+processed versions together in the same asset so that Photos library clients that don’t support RAW still have a readable version of the asset. (That includes the Photos app itself...) Saving both RAW+processed means specifying that in the capture.
If you’re requesting RAW+processed capture (where processed is JPEG, or even better, HEIF), you’re getting two photos for every shot you take. That means your didFinishProcessingPhoto callback gets called twice: once to deliver the JPEG (or HEIF), again to deliver the RAW.
Since you need to add RAW+processed versions of the asset to Photos together, you should wait until the capture output delivers both versions before trying to create the Photos asset. You’ll notice the code snippets in that Apple doc stash the data for both versions in the didFinishProcessingPhoto callback:
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
if photo.isRawPhoto {
// Save the RAW (DNG) fileDataRepresentation to a URL
} else {
// Hold JPEG/HEIF fileDataRepresentation in a property
}
}
Then, when the didFinishCaptureFor callback fires, they make sure they have both versions, and add them together to the Photos library.
Notice that when you add DNG and JPEG or HEIF versions of a photo together...
The JPEG/HEIF needs to be the primary photo resource, and the DNG the alternatePhoto resource.
You can add a JPEG/HEIF resource straight from Data in memory, but DNG needs to be added from a file URL.
So the Photos library part goes like this (again, inside the didFinishCaptureFor callback):
PHPhotoLibrary.shared().performChanges({
// Add the compressed (HEIF) data as the main resource for the Photos asset.
let creationRequest = PHAssetCreationRequest.forAsset()
creationRequest.addResource(with: .photo, data: compressedData, options: nil)
// Add the RAW (DNG) file as an altenate resource.
let options = PHAssetResourceCreationOptions()
options.shouldMoveFile = true
creationRequest.addResource(with: .alternatePhoto, fileURL: rawURL, options: options)
}, completionHandler: self.handlePhotoLibraryError)
You can make CGImage extension like here and then get pixel buffer from that cgImage.
if let cgImage = photo.cgImageRepresentation() {
let pixelBuffer = cgImage.pixelBuffer()
}

How to get ALAsset from saved photo

Is there a way to know the ALAsset corresponding to an UIImage?
I use UIImageWriteToSavedPhotosAlbum for saving a photo and I need to identify it in my asset list
UPDATE :
I used writeImageToSavedPhotosAlbum instead of UIImageWriteToSavedPhotosAlbum
alAssetLibrary.writeImageToSavedPhotosAlbum(image.CGImage, orientation: orientation) { (url, error) -> Void in
alAssetLibrary.assetForURL(url, resultBlock: { (asset) -> Void in
//code
}, failureBlock: { (error) -> Void in
//code
})
}
I use UIImageWriteToSavedPhotosAlbum for saving a photo and I need to identify it in my asset list
If you need to identify the resulting asset, why are you using UIImageWriteToSavedPhotosAlbum? It is a "stupid" function.
If you want to save an image as an asset into the library in such a way that you have access to that asset, you need to save it a "smart" way, such ALAssetsLibrary's writeImageToSavedPhotosAlbum - which has a completion handler that hands you the URL of the resulting asset.

Swift Share extension, ALAsset is nil

i want to get some extra info about the images i'll share with the Share extension. I can create the UIImage from the url but when i want to obtain an ALAsset i get nil. Anyone had this problem?
itemProvider!.loadItemForTypeIdentifier(String(kUTTypeImage), options: nil, completionHandler: { (decoder: NSSecureCoding!, error: NSError!) -> Void in
if ALAssetsLibrary.authorizationStatus() == ALAuthorizationStatus.Authorized {
if let url = decoder as? NSURL {
ALAssetsLibrary().assetForURL(url, resultBlock: { (myasset:ALAsset!) -> Void in
println(url)
println(fm.fileExistsAtPath(url.path!))
println(myasset)
let location = myasset?.valueForProperty(ALAssetPropertyLocation) as CLLocation?
let date = myasset?.valueForProperty(ALAssetPropertyDate) as NSDate?
self.extensionContext?.completeRequestReturningItems([AnyObject](), completionHandler: nil)
}, failureBlock: { (myerror:NSError!) -> Void in
})
}
}
The output is
file:///var/mobile/Media/DCIM/102APPLE/IMG_2977.JPG
true
nil
the immediate issue is you are passing a file url in place of an asset url for this line: ALAssetsLibrary().assetForURL(url, resultBlock: { (myasset:ALAsset!) -> Void in.
Share extensions return the url to the path on the iphone's file system...something of the form: file:///..... These are not the same as the urls that an ALAsset require in the assetForURL method.
Unfortunately, though this makes the code more correct, it still doesn't fix the issue. I spent some time with many different approaches. Writing a new image to disk via the AssetsLibrary and the given file path will return an asset url upon completion which will work successfully - though you obviously don't want duplicate photos in your camera roll. (Note: there is no way to delete an ALAsset). You could probably hold onto the file path and delete the new image when you are done with it, but that is an extremely messy approach.
I ended up rewriting my approach given these limitations.

Resources