Vinner
Vinner

Reputation: 31

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!

Upvotes: 1

Views: 1792

Answers (1)

Vinner
Vinner

Reputation: 31

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")
        }

Upvotes: 1

Related Questions