Using a PHLivePhoto with a UIActivityViewController - ios

Trying to share as activity a PHLivePhoto within a UIActivityViewController without any result more than the console output:
Resource not found (3 times logged)
I use the PHLivePhoto request with local URL's from document directory: Both files are already there. I set a PHLivePhotoView with the completion Live Photo witch works fine and I can reproduce/replay without any issues.
PHLivePhoto.request(withResourceFileURLs: [ URL(fileURLWithPath: FilePaths.VidToLive.livePath + "/IMG.MOV"), URL(fileURLWithPath: FilePaths.VidToLive.livePath + "/IMG.JPG")],
placeholderImage: nil,
targetSize: self!.view.bounds.size,
contentMode: PHImageContentMode.aspectFit,
resultHandler: { (livePhoto, info) -> Void in
self?.livePhotoView.livePhoto = livePhoto
self?.exportLivePhoto()
})
The problem is that I'm not quite sure what should be the content of the activity, I've try without any results:
let ac = UIActivityViewController(activityItems: [self.livePhotoView.livePhoto], applicationActivities: nil)
Been trying to fill as activity items with:
URL Document path (where video and image exist)
Both URLS from image and video (reverse order)
LivePhoto itself (as code below)
Trying to delay the LivePhoto creation as other people may suggest.

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.

Sharing Video PHAsset via UIActivityController

I am trying to share video PHAsset via UIActivityController using requestAVAsset. This works with Messaging, but not with AirDrop, indicating as 'Failed'.
PHCachingImageManager.default().requestAVAsset(forVideo: asset, options: nil, resultHandler:
{ (givenAsset, audioMix, info) in
let videoAsset = givenAsset as! AVURLAsset
let videoURL = videoAsset.url
DispatchQueue.main.async {
let activityViewController = UIActivityViewController(
activityItems: [videoURL],
applicationActivities: nil)
activityViewController.excludedActivityTypes = [UIActivityType.saveToCameraRoll]
if let popoverPresentationController = activityViewController.popoverPresentationController {
popoverPresentationController.barButtonItem = (sender)
}
self.present(activityViewController, animated: true, completion: nil)
}
})
This seems to properly put up UIActivityController and only work with certain activities:
Messaging - ✔️Works, properly exports video.
AirDrop - ✖️Shows "Failed"
Dropbox - ✖️Puts up the proper Dropbox View, yet says "Unknown error occurred"
I've run into similarly odd behavior when working with PHAssets. My guess is this is a (purposely) undocumented security/sandboxing restriction.
I was able to work around this problem by copying the underlying file to a user directory, and then performing the operation on the copied file.
I did this in a loop. Occasionally, the copy fails with a vague file permissions error. When it does, I retry it after a few seconds (using DispatchQueue.main.asyncAfter). Eventually, it works!

Extraction of video URL for sharing via UIActivityViewController

I'm working on share videos using UIActivityViewController and have some questions about the URL extraction from PHAsset objects.
I use "requestAVAsset" in "PHImageManager" and cast the AVAsset object to AVURLAsset to access its url property. I've tried the following types of activities:
Copy to Drive - this opens Google Drive app (Success)
Google Drive - a dialog shows up for confirmation (Fail) (no video attached in the dialog)
Gmail - (Fail) (mail can be sent but no video attached)
Add to Notes - this add video to built-in Notes app, a dialog should show up for confirmation (Fail) (app freezes after UIActivityViewController disappears and no dialog shows up)
Facebook/LINE - (Fail) (the progress bar never moves)
My questions:
Does the URL extracted by this method has the access to the real resource file of video?
If yes, am I missing something? Are there bugs in my code (see below)?
Code to share contents (inside an UIViewController):
PHImageManager.default().requestAVAsset(forVideo: videoAsset, options: nil, resultHandler: {
(asset: AVAsset?, audioMix: AVAudioMix?, info: [AnyHashable: Any]?) in
if let urlAsset = asset as? AVURLAsset {
print("Share url=\(urlAsset.url.absoluteURL)")
let shareVC: UIActivityViewController = UIActivityViewController(activityItems: [urlAsset.url.absoluteURL], applicationActivities: nil)
shareVC.completionWithItemsHandler = {
(type: UIActivityType?, completed: Bool, returnedItems: [Any]?, err: Error?) in
print("Share result: completed=\(completed), \(type)")
if err != nil {
print("\(err.debugDescription)")
}
}
DispatchQueue.main.async {
self.present(shareVC, animated: true, completion: nil)
}
}
})
Environment: iPhone 7 plus, iOS 10.1.1
Btw, I also tried another 2 methods for sharing.
Using "writeData" in "PHAssetResourceManager" to output video to a temporary directory and then build URL by the file path.
Using "requestExportSession" in "PHImageManager" to output video to a temporary directory and then build URL by the file path.
These method work fine. In my opinion, this is because the video file can be accessed directly by the extracted url. But they are not suitable for me since I would like to share not only single file but also multiple files within one action. (They take time to process data)

Extract video portion from Live Photo

Has anyone figured out how to extract the video portion from a Live Photo? I'm working on an app to convert Live Photos into a GIF, and the first step is to get the video file from the Live Photo. It seems like it should be possible, because if you plug in your phone to a Mac you can see the separate image and video files. I've kinda run into a brick wall in the extraction process, and I've tried many ways to do it and they all fail.
The first thing I did was obtain a PHAsset for what I think is the video part of the Live Photo, by doing the following:
if let livePhoto = info["UIImagePickerControllerLivePhoto"] as? PHLivePhoto {
let assetResources = PHAssetResource.assetResourcesForLivePhoto(livePhoto)
for assetRes in assetResources {
if (assetRes.type == .PairedVideo) {
let assets = PHAsset.fetchAssetsWithLocalIdentifiers([assetRes.assetLocalIdentifier], options: nil)
if let asset = assets.firstObject as? PHAsset {
To convert the PHAsset to an AVAsset I've tried:
asset.requestContentEditingInputWithOptions(nil, completionHandler: { (contentEditingInput, info) -> Void in
if let url = contentEditingInput?.fullSizeImageURL {
let movieUrl = url.absoluteString + ".mov"
let avAsset = AVURLAsset(URL: NSURL(fileURLWithPath: movieUrl), options: nil)
debugPrint(avAsset)
debugPrint(avAsset.duration.value)
}
})
I don't think this one works because the debug print with the duration.value gives 0.
I've also tried without the ".mov" addition and it still doesn't work.
I also tried:
PHImageManager.defaultManager().requestAVAssetForVideo(asset, options: nil, resultHandler: { (avAsset, audioMix, info) -> Void in
debugPrint(avAsset)
})
And the debugPrint(avAsset) prints nil so it doesn't work.
I'm kind of afraid they might have made it impossible to do, it seems like I'm going in circles since it seems like the PHAsset I got is still a Live Photo and not actually a video.
Use the PHAssetResourceManager to get the video file from the PHAssetResource.
PHAssetResourceManager.defaultManager().writeDataForAssetResource(assetRes,
toFile: fileURL, options: nil, completionHandler:
{
// Video file has been written to path specified via fileURL
}
NOTE: The Live Photo specific APIs were introduced in iOS 9.1
// suppose you have PHAsset instance (you can get it via [PHAsset fetchAssetsWithOptions:...])
PHAssetResource *videoResource = nil;
NSArray *resourcesArray = [PHAssetResource assetResourcesForAsset:asset];
const NSInteger livePhotoAssetResourcesCount = 2;
const NSInteger videoPartIndex = 1;
if (resourcesArray.count == livePhotoAssetResourcesCount) {
videoResource = resourcesArray[videoPartIndex];
}
if (videoResource) {
NSString * const fileURLKey = #"_fileURL";
NSURL *videoURL = [videoResource valueForKey:fileURLKey];
// load video url using AVKit or AVFoundation
}
I accidentally did. I have an ios app called Goodreader (available in the appstore) which features a windows-like file manager. When importing a live photo, it will save it as a folder ending in .pvt containing the jpg and mov files in it. There is only one caveat: you need to open the live photo from within the messages app after you've sent it to yourself or somebody else to see the "import to goodreader" option, not from the photos app.

Resources