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