Showing posts with label photokit. Show all posts
Showing posts with label photokit. Show all posts

Sunday, August 6, 2017

How to avoid duplicate video files when using PHAssetChangeRequest.FromVideo

Leave a Comment

I'm writing an app importing a video file from a remote server (drone) over HTTP. After downloading the file to local storage (/Documents/video) I import it to a custom album in the PHPhotLibrary using PHAssetChangeRequest.FromVideo.

I'm able to import the video, and when accessing the Photos app in the simulator the video appears in the correct album. However, if I delete it from the album the file is not deleted from my the apps /Documents/video folder.

I then tried to delete the file from /Documents/video after importing it using PHAssetChangeRequest.FromVideo, but then the video disappears from the Photos app.

How do I ensure there is only one copy of the video file, preferably stored in the shared photo gallery? Can I detect that a file is deleted, so that I can delete it from the /Documents/video folder? Or do I need to "sync" this my self everytime the app starts?

1 Answers

Answers 1

I think you should store the downloaded video to a temporary directory like

NSString *outputPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"output.mov"];

And then you can store it into Photos app.

The benefit of storing it into temporary directory is OS will automatically clear it when required however you can manually delete it whenever you want by creating following class method

+ (void)clearTemporaryDirectoryData {     NSArray* tempDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:NSTemporaryDirectory() error:NULL];     for (NSString *file in tempDirectory) {         [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), file] error:NULL];     } } 

In your case you can delete temp data after saving the downloaded video into Photos app.

Read More

Thursday, June 29, 2017

Modifing metadata from existing phAsset seems not working

Leave a Comment

In my App I want to make it possible, that the user sets an StarRating from 0 to 5 for any Image he has in his PhotoLibrary. My research shows, that there are a couple of ways to get this done:

Save the exif metadata using the new PHPhotoLibrary

Swift: Custom camera save modified metadata with image

Writing a Photo with Metadata using Photokit

Most of these Answers were creating a new Photo. My snippet now looks like this:

let options = PHContentEditingInputRequestOptions() options.isNetworkAccessAllowed = true  self.requestContentEditingInput(with: options, completionHandler: {             (contentEditingInput, _) -> Void in      if contentEditingInput != nil {          if let url = contentEditingInput!.fullSizeImageURL {             if let nsurl = url as? NSURL {                 if let imageSource = CGImageSourceCreateWithURL(nsurl, nil) {                     var imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary?                     if imageProperties != nil {                         imageProperties![kCGImagePropertyIPTCStarRating] = rating as AnyObject                          let imageData = NSMutableData(contentsOf: url)                         let image = UIImage(contentsOfFile: url.path)                          let destination = CGImageDestinationCreateWithData(imageData!, CGImageSourceGetType(imageSource)!, 1, nil)                          CGImageDestinationAddImage(destination!, image!.cgImage!, imageProperties! as CFDictionary)                          var contentEditingOutput : PHContentEditingOutput? = nil                          if CGImageDestinationFinalize(destination!) {                             let archievedData = NSKeyedArchiver.archivedData(withRootObject: rating)                             let identifier = "com.example.starrating"                             let adjustmentData = PHAdjustmentData(formatIdentifier: identifier, formatVersion: "1.0", data: archievedData)                              contentEditingOutput = PHContentEditingOutput(contentEditingInput: contentEditingInput!)                             contentEditingOutput!.adjustmentData = adjustmentData                             if imageData!.write(to: contentEditingOutput!.renderedContentURL, atomically: true) {                                 PHPhotoLibrary.shared().performChanges({                                     let request = PHAssetChangeRequest(for: self)                                     request.contentEditingOutput = contentEditingOutput                                 }, completionHandler: {                                     success, error in                                     if success && error == nil {                                         completion(true)                                     } else {                                         completion(false)                                     }                                 })                             }                         } else {                             completion(false)                         }                      }                 }             }         }     } }) 

Now when I want to read the metadata from the PHAsset I request the ContentEditingInput again and do the following:

if let url = contentEditingInput!.fullSizeImageURL {     if let nsurl = url as? NSURL {         if let imageSource = CGImageSourceCreateWithURL(nsurl, nil) {             if let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary? {                  if let starRating = imageProperties[kCGImagePropertyIPTCStarRating] as? Int {                     rating = starRating                 }             }         }     } } 

But I never get my rating because it says that the value of imageProperties[kCGImagePropertyIPTCStarRating] is nil.

I also tried the examples from the Answers I posted above, but I always get the same result.

I hope anybody knows, what I can do to change the Metadata.

Also, how can I change the Metadata from an PHAsset with the MediaType .video? I tried to achieve that through the AVAssetWriter and AVExportSession Objects, but in both cases it does not work. Here what I tried for Videos:

var exportSession = AVAssetExportSession(asset: asset!, presetName: AVAssetExportPresetPassthrough) exportSession!.outputURL = outputURL exportSession!.outputFileType = AVFileTypeQuickTimeMovie exportSession!.timeRange = CMTimeRange(start: start, duration: duration)  var modifiedMetadata = asset!.metadata  let metadataItem = AVMutableMetadataItem() metadataItem.keySpace = AVMetadataKeySpaceQuickTimeMetadata metadataItem.key = AVMetadataQuickTimeMetadataKeyRatingUser as NSCopying & NSObjectProtocol metadataItem.value = rating as NSCopying & NSObjectProtocol  modifiedMetadata.append(metadataItem)  exportSession!.metadata = modifiedMetadata   exportSession!.exportAsynchronously(completionHandler: {     let status = exportSession?.status     let success = status == AVAssetExportSessionStatus.completed     if success {         do {             let sourceURL = urlAsset.url             let manager = FileManager.default             _ = try manager.removeItem(at: sourceURL)             _ = try manager.moveItem(at: outputURL, to: sourceURL)         } catch {             LogError("\(error)")             completion(false)         }     } else {         LogError("\(exportSession!.error!)")         completion(false)     } }) 

1 Answers

Answers 1

Sorry this isn't a full answer but it covers one part of your question. I noticed you are placing the StarRating in the wrong place. You need to place it in a IPTC dictionary. Also the properties data is stored as strings. Given you have the imageProperties you can add the star rating as follows and read it back using the following two functions

func setIPTCStarRating(imageProperties : NSMutableDictionary, rating : Int) {     if let iptc = imageProperties[kCGImagePropertyIPTCDictionary] as? NSMutableDictionary {         iptc[kCGImagePropertyIPTCStarRating] = String(rating)     } else {         let iptc = NSMutableDictionary()         iptc[kCGImagePropertyIPTCStarRating] = String(rating)         imageProperties[kCGImagePropertyIPTCDictionary] = iptc     } }   func getIPTCStarRating(imageProperties : NSMutableDictionary) -> Int? {     if let iptc = imageProperties[kCGImagePropertyIPTCDictionary] as? NSDictionary {         if let starRating = iptc[kCGImagePropertyIPTCStarRating] as? String {             return Int(starRating)         }     }     return nil } 

One other point you are creating an unnecessary UIImage. If you use CGImageDestinationAddImageFromSource() instead of CGImageDestinationAddImage() you can use the imageSource you created earlier instead of loading the image data into a UIImage.

Read More

Friday, April 8, 2016

Determine largest PHAsset image size available on iOS device

Leave a Comment

How can I determine the largest PHAsset image size that is available on an iOS device, where by "available" I mean currently on the device, not requiring any network download from iCloud Photo Library?

I've been developing an app where a collection of thumbnails are shown in a UICollectionView (much like the Photos app) and then a user can select an image to see it full size. The full size image is downloaded from iCloud if necessary. However, I want to show the user the largest possible version of the image until the full size is available.

The way the Photos Framework works is rather odd:

In the collection view, I'm using a PHCachingImageManager to cache (and later request) images with a targetSize of 186x186 pixels (double the UICollectionViewCell dimensions because of retina scaling).

That works very well, with very fast scrolling and no lag in filling in the cells with images. Checking the size of the images returned, they are all roughly 342x256 pixels. That's on my iPhone 6 with iCloud Photo Library enabled and 'Optimize Storage' enabled, so only small thumbnails are stored on device for most images.

However, the odd part is that if I then request the full size (or much larger) version of any image, either with PHCachingImageManager or PHImageManager.defaultManager() using the PHImageRequestOptionsDeliveryMode.Opportunistic option, the first (low quality) image that is returned is a tiny 60x45 pixel postage stamp that looks horrible on the full screen, until eventually the full size image is downloaded to replace it.

Clearly there are larger thumbnails (342x256) on the device already, as they were just shown in the collection view! So why the hell doesn't it return them as the first 'low quality' image until the full size has been downloaded?? Just to make sure the 342x256 images really were already on the device I ran the code in airplane mode so no network access was happening.

What I found was that if I requested any size up to 256x256 I would get the 342x256 images returned. As soon as I went larger (even 1 pixel) it would first return me 60x45 images and then try to download the full size image.

I've changed my code to make a 256x256 request first, followed by a full size request with PHImageRequestOptionsDeliveryMode.HighQualityFormat (which doesn't return a tiny intermediate image), and that works beautifully. It's also what the iOS Photos app must be doing as it displays relatively high quality thumbnails whilst it's downloading the full image.

So, with that background, how do I know what the largest on device image size is for a PHAsset? Is 256x256 a magic number, or does it depend on how iCloud Photo Library is optimising for space (based on size of library and available device storage)?

Certainly seems like a bug that Photos Framework is not returning the largest image currently available (when asked for a size larger than it currently has), and that to get the largest size that it has, you have to ask for that size or smaller, because if you ask for larger, you'll get a 60x45 miniature! Bizarre.

UPDATE: Bug report submitted to Apple: https://openradar.appspot.com/25181601
Example project that demonstrates the bug: https://github.com/mluisbrown/PhotoKitBug

0 Answers

Read More