I'm trying to export videos with CIFilters applied using AVAssetExportSession, but sometimes it works and sometimes it doesn't. It's unclear even how to reproduce the error.
I've noticed that there's no problem exporting videos recorded with an iPhone. But if I try to export a video downloaded from WhatsApp or a video I already exported before, I get the following error:
Error Domain=AVFoundationErrorDomain Code=-11841 "Operation Stopped" UserInfo={NSLocalizedFailureReason=The video could not be composed., NSLocalizedDescription=Operation Stopped, NSUnderlyingError=0x2839eaf10 {Error Domain=NSOSStatusErrorDomain Code=-17390 "(null)"}}
This is my code:
var mutableComposition = AVMutableVideoComposition()
let exposureFilter = CIFilter.exposureAdjust()
func updateComposition() {
mutableComposition = AVMutableVideoComposition(asset: player.currentItem!.asset, applyingCIFiltersWithHandler: { request in
self.exposureFilter.inputImage = request.sourceImage.clampedToExtent()
self.exposureFilter.ev = 5
let output = self.exposureFilter.outputImage!.cropped(to: request.sourceImage.extent)
request.finish(with: output, context: nil)
})
player.currentItem?.videoComposition = mutableComposition
}
func exportVideo() {
let sourceAsset = AVURLAsset(url: videoURL)
let composition = AVMutableComposition()
let sourceVideoTrack = sourceAsset.tracks(withMediaType: .video)[0]
let compositionVideoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
try? compositionVideoTrack!.insertTimeRange(CMTimeRangeMake(start: .zero, duration: sourceAsset.duration), of: sourceVideoTrack, at: .zero)
if sourceAsset.tracks(withMediaType: .audio).isEmpty == false {
// Video has sound
let sourceAudioTrack = sourceAsset.tracks(withMediaType: .audio)[0]
let compositionAudioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
try? compositionAudioTrack!.insertTimeRange(CMTimeRangeMake(start: .zero, duration: sourceAsset.duration), of: sourceAudioTrack, at: .zero)
}
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
return
}
let videoID = UUID().uuidString
let videofileName = "\(videoID).mov"
let outputURL = documentsDirectory.appendingPathComponent(videofileName)
if FileManager.default.fileExists(atPath: outputURL.path) {
do {
try FileManager.default.removeItem(at: outputURL)
}
catch {}
}
let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)!
exporter.outputFileType = .mov
exporter.outputURL = outputURL
exporter.videoComposition = mutableComposition
exporter.exportAsynchronously(completionHandler: {
switch exporter.status {
case .failed:
print("Export failed \(exporter.error!)")
case .completed:
UISaveVideoAtPathToSavedPhotosAlbum(outputURL.path, self, #selector(video(_:didFinishSavingWithError:contextInfo:)), nil)
default:
break
}
}
}
Commenting out exporter.videoComposition = mutableComposition or changing the export preset to AVAssetExportPresetPassthrough fixes the error, but of course no CIFilters are applied to the video.
So the problem seems to lie in the AVMutableVideoComposition.
I found a solution that worked for me.
Replaced:
let compositionVideoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
let compositionAudioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
With:
let compositionVideoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: sourceVideoTrack.trackID)
let compositionAudioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: sourceAudioTrack.trackID)