I am trying to trim a video from photos app. Below is my code. The error seems to be on exportSession.exportAsynchronously
which i am not able to understand why
import UIKit
import AVFoundation
import AVKit
import Photos
import PhotosUI
class ViewController: UIViewController, PHPickerViewControllerDelegate {
func loadVideo(url: URL) -> AVAsset? {
return AVAsset(url: url)
}
func trimVideo(asset: AVAsset, startTime: CMTime, endTime: CMTime, completion: @escaping (URL?, Error?) -> Void) {
// Create an export session with the desired output URL and preset.
guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {
completion(nil, NSError(domain: "com.yourapp.trimVideo", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to create AVAssetExportSession."]))
return
}
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let outputURL = documentsDirectory.appendingPathComponent("trimmedVideo.mp4")
// Remove the existing file if it exists
try? FileManager.default.removeItem(at: outputURL)
exportSession.outputURL = outputURL
exportSession.outputFileType = AVFileType.mp4
exportSession.shouldOptimizeForNetworkUse = true
// Set the time range for trimming
let timeRange = CMTimeRangeFromTimeToTime(start: startTime, end: endTime)
exportSession.timeRange = timeRange
// Perform the export
exportSession.exportAsynchronously {
switch exportSession.status {
case .completed:
completion(exportSession.outputURL, nil)
case .failed:
completion(nil, exportSession.error)
case .cancelled:
completion(nil, NSError(domain: "com.yourapp.trimVideo", code: 2, userInfo: [NSLocalizedDescriptionKey: "Export cancelled"]))
default:
break
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
debugPrint("1")
picker.dismiss(animated: true,completion: nil)
debugPrint("2")
guard let provider = results.first?.itemProvider else { return }
debugPrint("3")
if provider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
debugPrint("4")
provider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { [self] (videoURL, error) in
debugPrint("5")
cropVideo(sourceURL1: videoURL as! URL, statTime: 10, endTime: 20)
}
}
}
@IBAction func btnClicked(_ sender: Any) {
var config = PHPickerConfiguration(photoLibrary: .shared())
config.selectionLimit = 1
config.filter = .videos
let vc = PHPickerViewController(configuration: config)
vc.delegate = self
present(vc, animated: true)
}
func cropVideo(sourceURL1: URL, statTime:Float, endTime:Float)
{
debugPrint("21")
let manager = FileManager.default
debugPrint("22")
guard let documentDirectory = try? manager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else {return}
debugPrint("23")
let mediaType = "mp4"
debugPrint("24")
if mediaType == UTType.movie.identifier || mediaType == "mp4" as String {
debugPrint("25")
let asset = AVAsset(url: sourceURL1 as URL)
let length = Float(asset.duration.value) / Float(asset.duration.timescale)
print("video length: \(length) seconds")
debugPrint("26")
let start = statTime
let end = endTime
debugPrint("27")
var outputURL = documentDirectory.appendingPathComponent("output")
do {
debugPrint("28")
try manager.createDirectory(at: outputURL, withIntermediateDirectories: true, attributes: nil)
outputURL = outputURL.appendingPathComponent("\(UUID().uuidString).\(mediaType)")
}catch let error {
debugPrint("29")
print(error)
}
//Remove existing file
_ = try? manager.removeItem(at: outputURL)
debugPrint("30")
guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {return}
exportSession.outputURL = outputURL
exportSession.outputFileType = .mp4
debugPrint("31")
let startTime = CMTime(seconds: Double(start ), preferredTimescale: 1000)
let endTime = CMTime(seconds: Double(end ), preferredTimescale: 1000)
let timeRange = CMTimeRange(start: startTime, end: endTime)
debugPrint("32")
exportSession.timeRange = timeRange
exportSession.exportAsynchronously{
switch exportSession.status {
case .completed:
print("exported at \(outputURL)")
case .failed:
print("failed \(String(describing: exportSession.error))")
case .cancelled:
print("cancelled \(String(describing: exportSession.error))")
default: break
}
}
}
}
}
Below is the entire error
Optional(Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x2818544b0 {Error Domain=NSOSStatusErrorDomain Code=-16979 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-16979), NSURL=file:///private/var/mobile/Containers/Shared/AppGroup/35D28117-572C-484E-969C-A9515EF42CDF/File%20Provider%20Storage/photospicker/version=1&uuid=6C291C6C-F254-44F5-83C2-C15980750530&mode=compatible&noloc=0.mp4, NSLocalizedDescription=The operation could not be completed})
func trimVideo(videoURL: URL, startTime: Double, endTime: Double, completionHandler: @escaping (URL?) -> Void) {
do { let asset = AVAsset(url: videoURL)
let composition = AVMutableComposition()
let track = asset.tracks(withMediaType: .video)[0]
let timeRange = CMTimeRange(start: CMTime(seconds: startTime, preferredTimescale: asset.duration.timescale), end: CMTime(seconds: endTime, preferredTimescale: asset.duration.timescale))
try composition.addMutableTrack(withMediaType: .video, preferredTrackID: 0)?.insertTimeRange(timeRange, of: track, at: CMTime.zero)
let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)
exporter?.outputURL = URL(fileURLWithPath: NSTemporaryDirectory() + "/output18\(UUID()).mp4")
exporter?.outputFileType = AVFileType.mp4
exporter?.exportAsynchronously {
if exporter?.status == .completed {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: (exporter?.outputURL)!)
}) { saved, error in
if saved {
debugPrint(saved)
let status = PHPhotoLibrary.authorizationStatus()
if (status == PHAuthorizationStatus.authorized) {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let fetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions).firstObject
PHImageManager().requestAVAsset(forVideo: fetchResult!, options: nil, resultHandler: { (avurlAsset, audioMix, dict) in
let newObj = avurlAsset as! AVURLAsset
print(newObj.url)
completionHandler(newObj.url)
})
} else if (status == PHAuthorizationStatus.notDetermined) {
// Access has not been determined.
PHPhotoLibrary.requestAuthorization({ (newStatus) in
if (newStatus == PHAuthorizationStatus.authorized) {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let fetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions).firstObject
PHImageManager().requestAVAsset(forVideo: fetchResult!, options: nil, resultHandler: { (avurlAsset, audioMix, dict) in
let newObj = avurlAsset as! AVURLAsset
print(newObj.url)
completionHandler(newObj.url)
})
}
else {
completionHandler(nil)
}
})
} else {
debugPrint(error)
completionHandler(nil)
}
} else {
debugPrint(error)
completionHandler(nil)
}
}
// completionHandler(exporter?.outputURL)
} else {
completionHandler(nil)
}
}
} catch {
completionHandler(nil)
}
}