Hello guys.
I'm using Metal and custom video composition instruction and custom video compositor to change each frame of a video. It work perfect with AVPlayer. It works perfect when exporting original AVAsset for the first time but export fail if I use previously exported AVAsset.
For example I've exported original.mov to exported.mov – works perfect. Then I want to use exported.mov as input (original) AVAsset – gives me error "The video could not be composed" and I don't know how to fix that. Maybe someone got same issue and can help?
export function
func export(arguments: Arguments) throws {
let timeRange = CMTimeRange(start: .zero, duration: arguments.asset.duration)
let mixComposition = AVMutableComposition()
guard
let sourceVideoTrack = arguments.asset.tracks(withMediaType: .video).first,
let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: CMPersistentTrackID()) // kCMPersistentTrackID_Invalid
else {
print("composition.addMutableTrack is nil")
return
}
try compositionVideoTrack.insertTimeRange(timeRange, of: sourceVideoTrack, at: .zero)
let audioMix = AVMutableAudioMix()
if
let sourceAudioTrack = arguments.asset.tracks(withMediaType: .audio).first,
let compositionAudioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: CMPersistentTrackID()) // kCMPersistentTrackID_Invalid
{
let param = AVMutableAudioMixInputParameters(track: sourceAudioTrack)
param.trackID = compositionAudioTrack.trackID
try compositionAudioTrack.insertTimeRange(timeRange, of: sourceAudioTrack, at: .zero)
audioMix.inputParameters = [param]
}
let presetName = sourceVideoTrack.hasMediaCharacteristic(.containsHDRVideo) ? AVAssetExportPresetHEVCHighestQuality : AVAssetExportPresetHighestQuality
exportSession = AVAssetExportSession(asset: mixComposition, presetName: presetName)
guard let exportSession = exportSession
else {
print("AVAssetExportSession return nil")
return
}
guard let compositionInstruction = try CustomVideoCompositionInstruction(videoTrack: sourceVideoTrack, usedForExport: true, dependency: arguments.dependency, timeRange: timeRange)
else {
print("CustomVideoCompositionInstruction is nil")
return
}
compositionInstruction.timeRange = CMTimeRange(start: .zero, duration: mixComposition.duration)
compositionInstruction.videoColorStrength = arguments.colorStrength
let mutableComposition = AVMutableVideoComposition()
mutableComposition.renderSize = sourceVideoTrack.orientation.size(for: sourceVideoTrack.naturalSize)
mutableComposition.frameDuration = CMTime(value: 1, timescale: CMTimeScale(sourceVideoTrack.nominalFrameRate))
mutableComposition.customVideoCompositorClass = CustomVideoCompositor.self
mutableComposition.instructions = [compositionInstruction]
exportSession.timeRange = timeRange
// 1. works perfect with original AVAsset that was not exported earlier (original.mov was exported to updated.mov)
// 2. gives error when using updated.mov as original AVAsset if exportSession.videoComposition is not nil
exportSession.videoComposition = mutableComposition
exportSession.outputFileType = .mov
exportSession.outputURL = arguments.url
exportSession.audioMix = audioMix
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self] timer in
arguments.progressChanged(exportSession.progress)
if exportSession.progress == 1 {
timer.invalidate()
self?.timer = nil
}
})
// 1. works perfect with original AVAsset that was not exported earlier (original.mov was exported to updated.mov)
// 2. gives error when using updated.mov as original AVAsset if exportSession.videoComposition is not nil
self.exportSession?.exportAsynchronously { [weak self] in
guard
let self = self,
let session = self.exportSession
else { return }
if let error = session.error {
print("session.error", error) // The video could not be composed when using previously exported AVAsset
}
self.timer?.invalidate()
self.timer = nil
self.exportSession = nil
arguments.finished(session.status, session.error)
}
}
Fixed.
Instead
CustomVideoCompositionInstruction(videoTrack: sourceVideoTrack, usedForExport: true, dependency: arguments.dependency, timeRange: timeRange)
there should be
CustomVideoCompositionInstruction(videoTrack: compositionVideoTrack, usedForExport: true, dependency: arguments.dependency, timeRange: timeRange)