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})
2
Answers
I’ve encountered this error before due to some weird bug where the timescales were different and it caused the composition export to fail with an opaque error.
Try the following: