skip to Main Content

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


  1. Chosen as BEST ANSWER
    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)
            }
        }
    

  2. 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:

    extension CMTime {
        func scaled(to timescale: CMTimeScale) -> CMTime {
            guard isValid && isNumeric && !isIndefinite else {
                return self
            }
    
            return .init(seconds: seconds, preferredTimescale: timescale)
        }
    }
    
    extension CMTimeRange {
        func scaled(to track: AVAssetTrack) -> CMTimeRange {
            guard isValid && !isIndefinite else {
                return self
            }
    
            return .init(
                start: start.scaled(to: track.timeRange.start.timescale),
                end: end.scaled(to: track.timeRange.end.timescale)
            )
        }
    }
    
    // ... in your function:
    
    exportSession.timeRange = timeRange.scaled(to: asset.tracks(withMediaType: .video)[0])
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search