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.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment