将 AVAsset 导出到临时 iOS 路径时 AVAssetExportSession 抛出错误

AVAssetExportSession throws error when exporting AVAsset to temporary iOS path

我正在尝试 trim 用户之前选择的本地 MP3 文件以获得 18 秒的片段。此代码段应导出到临时文件路径。这是我的代码:

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

它打印以下内容:

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

当我使用 Bundle.main.url(forResource: "sample_song", withExtension: "mp3") 而不是 Coordinators 的 url

从我的包的资源中使用 MP3 文件时,我没有收到错误

提前致谢!

对于遇到相同问题的任何人:我可以使用 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")
        }