问题
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