Swift Export MP4 using AVAssetExportSession - swift

I am trying to export an mp4 file within the documents directory. I am downloading that file from a remote URL and them attempting the trim it and create a new mp4 from the existing. Below is the code I am using the the exception that I am getting. This approach is working for .mov files and remote videos but not mp4.
func trimVideo(sourceURL: URL, startTime: Double, endTime: Double) {
let fileManager = FileManager.default
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let asset = AVAsset(url: sourceURL)
let fileName = UUID().uuidString + ".mp4"
var outputURL = documentDirectory.appendingPathComponent("output")
do {
try fileManager.createDirectory(at: outputURL, withIntermediateDirectories: true, attributes: nil)
outputURL = outputURL.appendingPathComponent(fileName)
}catch let error {
print(error)
}
//Remove existing file
try? fileManager.removeItem(at: outputURL)
guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPreset640x480) else { return }
exportSession.outputURL = outputURL
exportSession.outputFileType = AVFileType.mp4
let timeRange = CMTimeRange(start: CMTime(seconds: startTime, preferredTimescale: 1000),
end: CMTime(seconds: endTime, preferredTimescale: 1000))
exportSession.timeRange = timeRange
exportSession.exportAsynchronously {
switch exportSession.status {
case .completed:
print(outputURL.absoluteString)
case .failed:
print("failed \(exportSession.error.debugDescription)")
case .cancelled:
print("cancelled \(exportSession.error.debugDescription)")
default: break
}
}
}
failed Optional(Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x600003f432a0 {Error Domain=NSOSStatusErrorDomain Code=-16979 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-16979), NSURL=PATH_TO_MY_FILE.mp4, NSLocalizedDescription=The operation could not be completed})

Surfacing Victor's own answer from his comment:
the problem was that I was not creating the url that I passed in with fileURLWithPath as follows. let fileURL = URL(fileURLWithPath: filePath)

Related

Swift - AVAssetExportSession exportSession.exportAsynchronously completion handler not called

I used this link and following code in my project but AVAssetExportSession - exportAsynchronously method completion handler doesn't called in my project:
StackLink
func encodeVideo(at videoURL: URL, completionHandler: ((URL?, Error?) -> ())?) {
let avAsset = AVURLAsset(url: videoURL, options: nil)
let startDate = Date()
//Create Export session
guard let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough) else {
completionHandler?(nil, nil)
return
}
//Creating temp path to save the converted video
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL
let filePath = documentsDirectory.appendingPathComponent("rendered-Video.mp4")
//Check if the file already exists then remove the previous file
if FileManager.default.fileExists(atPath: filePath.path) {
do {
try FileManager.default.removeItem(at: filePath)
} catch {
completionHandler?(nil, error)
}
}
exportSession.outputURL = filePath
exportSession.outputFileType = .mp4
exportSession.shouldOptimizeForNetworkUse = true
let start = CMTimeMakeWithSeconds(0.0, preferredTimescale: 0)
let range = CMTimeRangeMake(start: start, duration: avAsset.duration)
exportSession.timeRange = range
exportSession.exportAsynchronously {
switch exportSession.status {
case .failed:
print(exportSession.error ?? "NO ERROR")
completionHandler?(nil, exportSession.error)
case .cancelled:
print("Export canceled")
completionHandler?(nil, nil)
case .completed:
//Video conversion finished
let endDate = Date()
let time = endDate.timeIntervalSince(startDate)
print(time)
print("Successful!")
print(exportSession.outputURL ?? "NO OUTPUT URL")
completionHandler?(exportSession.outputURL, nil)
case .unknown:
print("Export Unknown Error")
default: break
}
}
}
I also share my project on GitHub that you can check it,
thanks
GitRepo
I use Xcode 12.3
it was iOS bug even screen recording on my iOS device doesn't work, after i know this bug, I restart my phone and everything goes fine but this takes me some times to understand the solution.
I'm using iOS 14.3

AVURLAsset returning empty array-Trying to Concatenate two files

I'm trying to concatenate two (multiple) audio files. I found a relevant post and solution at Concatenate Two Audio Files Swift
Here's the solution:
func mergeAudioFiles(audioFileUrls: NSArray) {
let composition = AVMutableComposition()
for i in 0 ..< audioFileUrls.count {
let compositionAudioTrack :AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID())
let asset = AVURLAsset(url: (audioFileUrls[i] as! NSURL) as URL)
let track = asset.tracks(withMediaType: AVMediaTypeAudio)[0]
let timeRange = CMTimeRange(start: CMTimeMake(0, 600), duration: track.timeRange.duration)
try! compositionAudioTrack.insertTimeRange(timeRange, of: track, at: composition.duration)
}
let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! as NSURL
self.mergeAudioURL = documentDirectoryURL.appendingPathComponent("FinalAudio.m4a")! as URL as NSURL
let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A)
assetExport?.outputFileType = AVFileTypeAppleM4A
assetExport?.outputURL = mergeAudioURL as URL
assetExport?.exportAsynchronously(completionHandler:
{
switch assetExport!.status
{
case AVAssetExportSessionStatus.failed:
print("failed \(assetExport?.error)")
case AVAssetExportSessionStatus.cancelled:
print("cancelled \(assetExport?.error)")
case AVAssetExportSessionStatus.unknown:
print("unknown\(assetExport?.error)")
case AVAssetExportSessionStatus.waiting:
print("waiting\(assetExport?.error)")
case AVAssetExportSessionStatus.exporting:
print("exporting\(assetExport?.error)")
default:
print("Audio Concatenation Complete")
}
})
}
Some parameters are out of date and I used the suggested fixes from Xcode, resulting in :
let composition = AVMutableComposition()
for i in 0 ..< audioFileUrls.count {
let compositionAudioTrack :AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: CMPersistentTrackID())!
let asset : AVURLAsset = AVURLAsset(url: (audioFileUrls[i] as! NSURL) as URL)
let track : AVAssetTrack = asset.tracks(withMediaType: AVMediaType.audio)[0]
let timeRange = CMTimeRange(start: CMTimeMake(value: 0, timescale: 600), duration: track.timeRange.duration)
try! compositionAudioTrack.insertTimeRange(timeRange, of: track, at: composition.duration)
}
let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! as NSURL
self.mergeAudioURL = documentDirectoryURL.appendingPathComponent("FinalAudio.m4a")! as URL as NSURL
let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A)
assetExport?.outputFileType = AVFileType.m4a
assetExport?.outputURL = mergeAudioURL as URL
assetExport?.exportAsynchronously(completionHandler:
{
switch assetExport!.status
{
case AVAssetExportSessionStatus.failed:
print("failed \(assetExport?.error)")
case AVAssetExportSessionStatus.cancelled:
print("cancelled \(assetExport?.error)")
case AVAssetExportSessionStatus.unknown:
print("unknown\(assetExport?.error)")
case AVAssetExportSessionStatus.waiting:
print("waiting\(assetExport?.error)")
case AVAssetExportSessionStatus.exporting:
print("exporting\(assetExport?.error)")
default:
print("Audio Concatenation Complete")
}
})
print("asset url \(mergeAudioURL)")
}
when I used the solution I get
2020-05-11 13:09:14.771381-0600 TimeCapsule[88538:12022916] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty NSArray'
The error occurs on the let track = asset.tracks(withMediaType: AVMediaTypeAudio)[0] line. Before that line, print outs show that asset.tracks.count = 0 asset.trackGroups.count = 0. When I remove the [0] index at the end of the line, the following line throws an error that "track has no member 'timeRange'". I can't figure out how to add a track to my newly created asset so that I can use the assets audio duration. Any help would be greatly appreciated, Im assuming it's just outdated syntax for the new Swift.
The error is clear
let tracks = asset.tracks(withMediaType: AVMediaTypeAudio)
guard !tracks.isEmpty else { return }
let track = tracks.first!
the file you try to concatenate has no audio tracks , hence array is empty
Please note that the sandbox environment will change every time 'url', I have encountered this problem before!

AVAssetExportSession gives me AVFoundationErrorDomain Code=-11800

I am facing the same issues in ios 13.3 in real device it is working in ios 13.2 simulator but gives below error.
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could
not be completed" UserInfo={NSLocalizedFailureReason=An unknown error
occurred (-17508), NSLocalizedDescription=The operation could not be
completed, NSUnderlyingError=0x2816d11d0 {Error
Domain=NSOSStatusErrorDomain Code=-17508 "(null)"}}
Here is my code I want to convert .mov file to mp4.
class func encodeVideo(at videoURL: String, completionHandler: ((URL?, Error?) -> Void)?) {
let avAsset = AVURLAsset(url: URL.init(fileURLWithPath: videoURL), options: nil)
let startDate = Date()
//Create Export session
guard let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough) else {
completionHandler?(nil, nil)
return
}
//Creating temp path to save the converted video
let filename = "Video_\(Date().timeIntervalSince1970).mp4"
// Below Folder Path used tor getting directory path
let strfilePath = (FolderPath.temporaryDirectory.getDirectoryPath as NSString).appendingPathComponent(filename)
let filePath = URL.init(fileURLWithPath: strfilePath)
//Check if the file already exists then remove the previous file
if FileManager.default.fileExists(atPath: filePath.path) {
do {
try FileManager.default.removeItem(at: filePath)
} catch {
completionHandler?(nil, error)
}
}
exportSession.outputURL = filePath
exportSession.outputFileType = AVFileType.mp4
exportSession.shouldOptimizeForNetworkUse = true
let start = CMTimeMakeWithSeconds(0.0, preferredTimescale: 0)
let range = CMTimeRangeMake(start: start, duration: avAsset.duration)
exportSession.timeRange = range
exportSession.exportAsynchronously(completionHandler: {() -> Void in
switch exportSession.status {
case .failed:
print(exportSession.error ?? "NO ERROR")
completionHandler?(nil, exportSession.error)
case .cancelled:
print("Export canceled")
completionHandler?(nil, nil)
case .completed:
//Video conversion finished
let endDate = Date()
let time = endDate.timeIntervalSince(startDate)
print(time)
print("Successful!")
print(exportSession.outputURL ?? "NO OUTPUT URL")
completionHandler?(exportSession.outputURL, nil)
default: break
}
})
}
let strfilePath = (FolderPath.temporaryDirectory.getDirectoryPath as NSString).appendingPathComponent(filename)
you cannot store in that folder directly, but you need to store your file in a subfolder, e.g. like this:
let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! as NSURL
let strfilePath = documentDirectoryURL.appendingPathComponent("Subfolder/filename.mp4") as URL
Further you can read this article
Finally, I solve my issues by using AVMutableComposition not directly using AVURL asset. I adding audio and video track in AVMutableComposition.
This is the code I use to convert .mov to .mp4
var outputURL: URL!
func exportVideo(key:String, inputurl: URL, presetName: String, outputFileType: AVFileType = .mp4, fileExtension: String = "mp4", then completion: #escaping (URL?) -> Void) {
let asset = AVAsset(url: inputurl)
outputURL = FileManager.default.temporaryDirectory.appendingPathComponent(key)
if let session = AVAssetExportSession(asset: asset, presetName: presetName) {
session.outputURL = outputURL
session.outputFileType = outputFileType
session.shouldOptimizeForNetworkUse = true
session.exportAsynchronously {
switch session.status {
case .completed:
completion(self.outputURL)
case .cancelled:
debugPrint("Video export cancelled.")
completion(nil)
case .failed:
let errorMessage = session.error?.localizedDescription ?? "n/a"
debugPrint("Video export failed with error: \(errorMessage)")
completion(nil)
default:
break
}
}
} else {
completion(nil)
}
}
And then I call this function to get the output URL of the converted file and use it
exportVideo(key: key, inputurl: path, presetName: AVAssetExportPresetHighestQuality, outputFileType: .mp4, fileExtension: "mp4") { (outputURL) in
// do whatever with the file here
}
For anyone stuck in this frustrating error. Use a AVmutablecomposition as export asset.
Sample code for trimming a video:
let manager = FileManager.default
guard let documentDirectory = try? manager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else { print("TRIM Failed to access directory")
return}
let mediaType = "mp4"
if mediaType == kUTTypeMovie as String || mediaType == "mp4" as String {
let asset = AVAsset(url:videoURL!)
let length = Float(asset.duration.value) / Float(asset.duration.timescale)
print("TRIM video length: \(length) seconds")
let composition = AVMutableComposition()
let audioTrack: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)!
let videoTrack: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)!
var outURL_speed=documentDirectory.appendingPathComponent("output")
var outputURL = documentDirectory.appendingPathComponent("output")
do {
try audioTrack.insertTimeRange(CMTimeRangeFromTimeToTime(start: self.startTime, end: self.endTime), of: asset.tracks(withMediaType: AVMediaType.audio)[0], at: CMTime.zero)
try videoTrack.insertTimeRange(CMTimeRangeFromTimeToTime(start: self.startTime, end: self.endTime), of: asset.tracks(withMediaType: AVMediaType.video)[0], at: CMTime.zero)
try manager.createDirectory(at: outputURL, withIntermediateDirectories: true, attributes: nil)
outputURL = outputURL.appendingPathComponent("preVideo-\(self.postID).\(mediaType)")
outURL_speed = outURL_speed.appendingPathComponent("Video-\(self.postID).\(mediaType)")
print("TRIM output dir: \(outputURL)")
}catch let error {
print(error)
}
_ = try? manager.removeItem(at: outputURL)
guard let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {return}
exportSession.outputURL = outputURL
exportSession.shouldOptimizeForNetworkUse = true
exportSession.outputFileType = .mp4
let timeRange = CMTimeRange(start: self.startTime, end: self.endTime)
exportSession.timeRange = timeRange
exportSession.exportAsynchronously{
switch exportSession.status {
case .completed:
print("TRIM exported at \(outputURL)")
self.changeSpeed(url: outputURL,outUrl:outURL_speed)
case .failed:
print("TRIM failed \(exportSession.error)")
case .cancelled:
print("TRIM cancelled \(exportSession.error)")
default: break
}
}

Error: The operation is not supported for this media - Swift

I am trying to save a video with this code on an Apple TV:
func playerItemDidReachEnd(_ notification: Notification) {
if notification.object as? AVPlayerItem == player.currentItem {
player.seek(to: kCMTimeZero)
let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality)
let filename = "video.mp4"
let documentsDirectory = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).last!
let outputURL = documentsDirectory.appendingPathComponent(filename)
exporter?.outputURL = NSURL.fileURL(withPath: outputURL.path)
exporter?.outputFileType = AVFileTypeMPEG4
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let filePath = url.appendingPathComponent(filename)?.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath!) {
} else {
exporter?.exportAsynchronously(completionHandler: {
print(exporter?.status.rawValue)
print(exporter?.error)
})
}
}
}
I am also using this as the asset and do not want to change the asset:
lazy var asset: AVURLAsset = {
var asset: AVURLAsset = AVURLAsset(url: self.url)
asset.resourceLoader.setDelegate(self as? AVAssetResourceLoaderDelegate, queue: DispatchQueue.main)
return asset
}()
But I am getting this error. What does it mean, and how can I fix it? Thanks :)
Optional(Error Domain=AVFoundationErrorDomain Code=-11838 "Operation Stopped" UserInfo={NSUnderlyingError=0x608000443300 {Error Domain=NSOSStatusErrorDomain Code=-12109 "(null)"}, NSLocalizedFailureReason=The operation is not supported for this media., NSLocalizedDescription=Operation Stopped})

Video Trimming failed with block AVAssetExportSessionStatus.Failed

I have converted this code to Swift language but i am getting this
Error: Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo=0x174278600 {NSUnderlyingError=0x170241d10 "The operation couldn’t be completed. (OSStatus error -12780.)", NSLocalizedFailureReason=An unknown error occurred (-12780), NSLocalizedDescription=The operation could not be completed} in (case AVAssetExportSessionStatus.Failed).
Kindly help me to resolved this
func cropVideo(sourceURL: NSURL)
{
let asset = AVURLAsset(URL: sourceURL, options: nil)
let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality)
var error : NSError?
let file = "Finaloutput.mp4"
/* let paths : AnyObject = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask,true)[0]
let outputURL1 = paths[0] as? String*/
let nsDocumentDirectory = NSSearchPathDirectory.DocumentDirectory
let nsUserDomainMask = NSSearchPathDomainMask.UserDomainMask
let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
let outputURL1 = paths[0] as? String
let filemgr = NSFileManager.defaultManager()
filemgr.createDirectoryAtPath(outputURL1!, withIntermediateDirectories: true, attributes: nil, error: &error)
var outputURL = outputURL1!.stringByAppendingPathComponent(file)
filemgr.removeItemAtPath(outputURL, error: &error)
let FinalUrlTosave = NSURL(string: outputURL)
exportSession.outputURL=FinalUrlTosave
exportSession.shouldOptimizeForNetworkUse = true
// exportSession.outputFileType = AVFileTypeQuickTimeMovie
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
let start:CMTime
let duration:CMTime
start = CMTimeMakeWithSeconds(1.0, 600)
duration = CMTimeMakeWithSeconds(19.0, 600)
// let timeRangeForCurrentSlice = CMTimeRangeMake(start, duration)
let range = CMTimeRangeMake(start, duration);
exportSession.timeRange = range
let destinationURL1 = NSURL(string: outputURL)
exportSession.exportAsynchronouslyWithCompletionHandler({
switch exportSession.status{
case AVAssetExportSessionStatus.Failed:
println("failed \(exportSession.error)")
case AVAssetExportSessionStatus.Cancelled:
println("cancelled \(exportSession.error)")
default:
println("complete....complete")
self.SaveVideoToPhotoLibrary(destinationURL1!)
}
})
}
func SaveVideoToPhotoLibrary(outputFileURL: NSURL)
{
assetsLibrary = ALAssetsLibrary()
let videoURL = outputFileURL as NSURL?
if let library = assetsLibrary{
if let url = videoURL{
library.writeVideoAtPathToSavedPhotosAlbum(url,
completionBlock: {(url: NSURL!, error: NSError!) in
print(url)
if let theError = error{
print("Error happened while saving the video")
print("The error is = \(theError)")
} else {
print("no errors happened")
}
})
} else {
print("Could not find the video in the app bundle")
}
}
}
Found Solution :
I have change this line and it works for me
let FinalUrlTosave = NSURL(fileURLWithPath: outputURL)
instead of
let FinalUrlTosave = NSURL(string: outputURL)
I was not getting exact path.