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.
0 comments:
Post a Comment