问题
I try adding/modifying the Metadata from an PHAsset with mediaType == .video I found some Questions refering to a similar problem:
How to change video metadata using AVAssetWriter?
Add custom metadata to video using AVFoundation
Regarding to the Answers in these Questions I build the following snippet which is a extension of a PHAsset:
let options = PHVideoRequestOptions()
options.version = .original
PHImageManager.default().requestAVAsset(forVideo: self, options: options, resultHandler: {
asset, audioMix, info in
if asset != nil && asset!.isKind(of: AVURLAsset.self) {
let urlAsset = asset as! AVURLAsset
let start = CMTimeMakeWithSeconds(0.0, 1)
let duration = asset!.duration
var exportSession = AVAssetExportSession(asset: asset!, presetName: AVAssetExportPresetPassthrough)
exportSession!.outputURL = urlAsset.url
exportSession!.outputFileType = AVFileTypeAppleM4V
exportSession!.timeRange = CMTimeRange(start: start, duration: duration)
var modifiedMetadata = asset!.metadata
let metadataItem = AVMutableMetadataItem()
metadataItem.keySpace = AVMetadataKeySpaceQuickTimeUserData
metadataItem.key = AVMetadataQuickTimeMetadataKeyRatingUser as NSString
metadataItem.value = NSNumber(floatLiteral: Double(rating))
modifiedMetadata.append(metadataItem)
exportSession!.metadata = modifiedMetadata
LogInfo("\(modifiedMetadata)")
exportSession!.exportAsynchronously(completionHandler: {
let status = exportSession?.status
let success = status == AVAssetExportSessionStatus.completed
if success {
completion(true)
} else {
LogError("\(exportSession!.error!)")
completion(false)
}
})
}
})
When I execute this snippet, the exportSession failed an has the following error:
Error Domain=NSURLErrorDomain
Code=-3000 "Cannot create file"
UserInfo={NSLocalizedDescription=Cannot create file,
NSUnderlyingError=0x1702439f0
{Error Domain=NSOSStatusErrorDomain Code=-12124 "(null)"}}
回答1:
I found my mistake. To modify the metadata of an PHAsset with the MediaType MediaType.video you can use the following snippet, where self is the PHAsset:
First you need to create an PHContentEditingOutput you can do that with requesting an PHContentEditingInput from the PHAsset you want to modify. When changing an PHAsset you also have to set the .adjustmentData Value of the PHContentEditingOutput or else the .performChanges() Block will fail.
self.requestContentEditingInput(with: options, completionHandler: {
(contentEditingInput, _) -> Void in
if contentEditingInput != nil {
let adjustmentData = PHAdjustmentData(formatIdentifier: starRatingIdentifier, formatVersion: formatVersion, data: NSKeyedArchiver.archivedData(withRootObject: rating))
let contentEditingOutput = PHContentEditingOutput(contentEditingInput: contentEditingInput!)
contentEditingOutput.adjustmentData = adjustmentData
self.applyRatingToVideo(rating, contentEditingInput, contentEditingOutput, completion: {
output in
if output != nil {
PHPhotoLibrary.shared().performChanged({
let request = PHAssetChangeRequest(for: self)
request.contentEditingOutput = output
}, completionHandler: {
success, error in
if !success {
print("can't edit asset: \(String(describing: error))")
}
})
}
})
}
})
With the snippet above, you change the PHAsset after modifying the PHContentEditingOutput in the following snippet you will see, how to set the Metadata for an User Rating:
private func applyRatingToVideo(_ rating: Int, input: PHContentEditingInput, output: PHContentEditingOutput, completion: @escaping (PHContentEditingOutput?) -> Void) {
guard let avAsset = input.audiovisualAsset else { return }
guard let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough) else { return }
var mutableMetadata = exportSession.asset.metadata
let metadataCopy = mutableMetadata
for item in metadataCopy {
if item.identifier == AVMetadataIdentifierQuickTimeMetadataRatingUser {
mutableMetadata.remove(object: item)
}
}
let metadataItem = AVMutableMetadataItem()
metadataItem.identifier = AVMetadataIdentifierQuickTimeMetadataRatingUser
metadataItem.keySpace = AVMetadataKeySpaceQuickTimeMetadata
metadataItem.key = AVMetadataQuickTimeMetadataKeyRatingUser as NSString
metadataItem.value = NSNumber(floatLiteral: Double(rating))
exportSession.outputURL = output.renderedContentURL
mutableMetadata.append(metadataItem)
exportSession.metadata = mutableMetadata
exportSession.outputFileType = AVFileTypeQuickTimeMovie
exportSession.shouldOptimizeForNetworkUse = true
exportSession.exportAsynchronously(completionHandler: {
if exportSession.status == .completed {
completion(output)
} else if exportSession.error != nil {
completion(nil)
}
})
}
Consider, that if you do not remove the AVMetadataItem with the same Identifier as the one you want to add, the AVAssetExportSession will set multiple Items with the same Identifier for the AVAsset.
NOTE:
When you now access the Video through the PHImageManager-method .requestAVAsset(forVideo:,options:,resultHandler:) you have to pass an PHVideoRequestOptions-object with the .version variable set to .current. It is set as default value of the variable but if you change it to .original you will get the unmodified Video from that method.
来源:https://stackoverflow.com/questions/44824936/modifying-metadata-from-phasset-with-mediatype-video-fails