从现有 phAsset 修改元数据似乎不起作用
Modifing metadata from existing phAsset seems not working
在我的应用程序中,我想让它成为可能,用户可以为他在他的照片库中的任何图像设置 StarRating 从 0 到 5。我的研究表明,有几种方法可以完成此任务:
Save the exif metadata using the new PHPhotoLibrary
Writing a Photo with Metadata using Photokit
这些答案中的大部分都在创建新照片。我的代码片段现在看起来像这样:
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)
}
}
}
}
}
}
})
现在,当我想从 PHAsset 读取元数据时,我再次请求 ContentEditingInput 并执行以下操作:
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
}
}
}
}
}
但我从来没有得到我的评分,因为它说 imageProperties[kCGImagePropertyIPTCStarRating]
的值为 nil。
我也尝试了上面发布的答案中的示例,但我总是得到相同的结果。
我希望有人知道,我可以做些什么来更改元数据。
此外,如何更改媒体类型 .video
的 PHAsset
的元数据?我试图通过 AVAssetWriter
和 AVExportSession
对象来实现这一点,但在这两种情况下它都不起作用。这是我尝试的视频:
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)
}
})
抱歉,这不是完整的答案,但它涵盖了您问题的一部分。我注意到您将 StarRating 放在了错误的位置。您需要将其放入 IPTC 词典中。属性数据也存储为字符串。鉴于您拥有 imageProperties,您可以按如下方式添加星级,并使用以下两个函数读回它
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
}
由于您从图像中获得的 imageProperties 是不可变的,因此您需要先创建这些属性的可变副本,然后才能调用上述函数。当您创建要保存的图像时,请在调用 CGImageDestinationAddImage()
时使用可变属性
if let mutableProperties = imageProperties.mutableCopy() as? NSMutableDictionary {
setIPTCStarRating(imageProperties:mutableProperties, rating:rating)
}
另外一点,您正在创建一个不必要的 UIImage。如果您使用 CGImageDestinationAddImageFromSource() 而不是 CGImageDestinationAddImage() 您可以使用您之前创建的 imageSource 而不是将图像数据加载到 UIImage 中。
在我的应用程序中,我想让它成为可能,用户可以为他在他的照片库中的任何图像设置 StarRating 从 0 到 5。我的研究表明,有几种方法可以完成此任务:
Save the exif metadata using the new PHPhotoLibrary
Writing a Photo with Metadata using Photokit
这些答案中的大部分都在创建新照片。我的代码片段现在看起来像这样:
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)
}
}
}
}
}
}
})
现在,当我想从 PHAsset 读取元数据时,我再次请求 ContentEditingInput 并执行以下操作:
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
}
}
}
}
}
但我从来没有得到我的评分,因为它说 imageProperties[kCGImagePropertyIPTCStarRating]
的值为 nil。
我也尝试了上面发布的答案中的示例,但我总是得到相同的结果。
我希望有人知道,我可以做些什么来更改元数据。
此外,如何更改媒体类型 .video
的 PHAsset
的元数据?我试图通过 AVAssetWriter
和 AVExportSession
对象来实现这一点,但在这两种情况下它都不起作用。这是我尝试的视频:
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)
}
})
抱歉,这不是完整的答案,但它涵盖了您问题的一部分。我注意到您将 StarRating 放在了错误的位置。您需要将其放入 IPTC 词典中。属性数据也存储为字符串。鉴于您拥有 imageProperties,您可以按如下方式添加星级,并使用以下两个函数读回它
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
}
由于您从图像中获得的 imageProperties 是不可变的,因此您需要先创建这些属性的可变副本,然后才能调用上述函数。当您创建要保存的图像时,请在调用 CGImageDestinationAddImage()
时使用可变属性if let mutableProperties = imageProperties.mutableCopy() as? NSMutableDictionary {
setIPTCStarRating(imageProperties:mutableProperties, rating:rating)
}
另外一点,您正在创建一个不必要的 UIImage。如果您使用 CGImageDestinationAddImageFromSource() 而不是 CGImageDestinationAddImage() 您可以使用您之前创建的 imageSource 而不是将图像数据加载到 UIImage 中。