iosswiftavassetexportsession

AVAssetExportSession throws error when exporting AVAsset to temporary iOS path


I am trying to trim a local MP3-File picked by the user before for getting an 18 seconds snippet. This snippet should be exported to a temporary file path. This is my code:

guard songUrl.startAccessingSecurityScopedResource() else {
        print("failed to access path")
        return
    }
    
    // Make sure you release the security-scoped resource when you are done.
    defer { songUrl.stopAccessingSecurityScopedResource() }

    // Use file coordination for reading and writing any of the URL’s content.
    var error: NSError? = nil
    NSFileCoordinator().coordinate(readingItemAt: songUrl, error: &error) { (url) in
        
        
        // Set temporary file path
        let temporaryDirectoryUrl: URL = FileManager.default.temporaryDirectory
        let temporaryDirectoryString: String = temporaryDirectoryUrl.absoluteString
        let temporaryFilename = ProcessInfo().globallyUniqueString + ".m4a"
        let temporaryFilepath = URL(string: (temporaryDirectoryString + temporaryFilename))!

        // shorten audio file
        let originalAsset = AVAsset(url: (url))

        if let exporter = AVAssetExportSession(asset: originalAsset, presetName: AVAssetExportPresetAppleM4A) {
            exporter.outputFileType = AVFileType.m4a
            exporter.outputURL = temporaryFilepath

            let originalDuration = Int64(CMTimeGetSeconds(originalAsset.duration))
            let halftime: Int64 = (originalDuration/2)
            let startTime = CMTimeMake(value: (halftime-9), timescale: 1)
            let stopTime = CMTimeMake(value: (halftime+9), timescale: 1)
            exporter.timeRange = CMTimeRangeFromTimeToTime(start: startTime, end: stopTime)
            print(CMTimeGetSeconds(startTime), CMTimeGetSeconds(stopTime))

            //Export audio snippet
            exporter.exportAsynchronously(completionHandler: {
                
                print("export complete \(exporter.status)")
                    
                switch exporter.status {
                case  AVAssetExportSessionStatus.failed:
                    
                    if let e = exporter.error {
                        print("export failed \(e)")
                    }
                    
                case AVAssetExportSessionStatus.cancelled:
                    print("export cancelled \(String(describing: exporter.error))")
                    
                default:
                    print("export complete")
                    self.shortenedExported(temporaryFilePath: temporaryFilepath)
                }
                })
        }
        else {
                print("cannot create AVAssetExportSession for asset \(originalAsset)")
        }
    }

It prints the following:

export complete AVAssetExportSessionStatus

export failed 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=0x282368b40 {Error Domain=NSOSStatusErrorDomain Code=-17508 "(null)"}}

I don't get an error when I use a MP3-file from my bundle's resources using Bundle.main.url(forResource: "sample_song", withExtension: "mp3") instead of the Coordinators's url

Thanks in advance!


Solution

  • For anyone with the same problem: I could solve it using an AVMutableComposition():

    // Access url
        guard songUrl.startAccessingSecurityScopedResource() else {
            print("failed to access path")
            return
        }
    
        // Make sure you release the security-scoped resource when you are done.
        defer { songUrl.stopAccessingSecurityScopedResource() }
    
        // Use file coordination for reading and writing any of the URL’s content.
        var error: NSError? = nil
        NSFileCoordinator().coordinate(readingItemAt: songUrl, error: &error) { (url) in
    
            // Set temporary file's path
            let temporaryDirectoryUrl: URL = FileManager.default.temporaryDirectory
            let temporaryFilename = ProcessInfo().globallyUniqueString
            let temporaryFilepath = temporaryDirectoryUrl.appendingPathComponent("\(temporaryFilename).m4a")
    
            // Prework
            let originalAsset = AVURLAsset(url: url)
            print(originalAsset)
            let composition = AVMutableComposition()
            let audioTrack: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)!
            let originalDuration = Int64(CMTimeGetSeconds(originalAsset.duration))
            let startTime, stopTime: CMTime
    
            // Shorten audio file if longer than 20 seconds
            if originalDuration < 20 {
                startTime = CMTimeMake(value: 0, timescale: 1)
                stopTime = CMTimeMake(value: originalDuration, timescale: 1)
            }
            else {
                let halftime: Int64 = (originalDuration/2)
                startTime = CMTimeMake(value: (halftime-10), timescale: 1)
                stopTime = CMTimeMake(value: (halftime+10), timescale: 1)
            }
    
            // Export shortened file
            do {
                try audioTrack.insertTimeRange(CMTimeRangeFromTimeToTime(start: startTime, end: stopTime), of: originalAsset.tracks(withMediaType: AVMediaType.audio)[0], at: CMTime.zero)
                let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A)!
                if FileManager.default.fileExists(atPath: temporaryFilepath.absoluteString) {
                    try? FileManager.default.removeItem(atPath: temporaryFilepath.absoluteString)
                    print("removed existing file")
                }
                assetExport.outputFileType = AVFileType.m4a
                assetExport.outputURL = temporaryFilepath
                assetExport.shouldOptimizeForNetworkUse = true
                assetExport.exportAsynchronously(completionHandler: {
                    switch assetExport.status {
                    case  AVAssetExportSessionStatus.failed:
    
                        if let e = assetExport.error {
                            print("export failed \(e)")
                        }
                    case AVAssetExportSessionStatus.cancelled:
                        print("export cancelled \(String(describing: assetExport.error))")
    
                    default:
                        print("export completed")
                    }
                })
            }
            catch {
                print("error trying to shorten audio file")
            }