I'm trying to convert mp4
video file to m4a
audio format by AVAssetExportSession
on my iOS app.
This is the conversion code:
let outputUrl = URL(fileURLWithPath: NSTemporaryDirectory() "out.m4a")
if FileManager.default.fileExists(atPath: outputUrl.path) {
try? FileManager.default.removeItem(atPath: outputUrl.path)
}
let asset = AVURLAsset(url: inputUrl)
// tried the `AVAssetExportPresetAppleM4A` preset name but the same result
let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetPassthrough)!
exportSession.outputFileType = AVFileType.m4a
exportSession.outputURL = outputUrl
await exportSession.export()
switch exportSession.status {
case .completed:
return outputUrl
default:
// This becomes `4` which is `.failed`
print("Status: \(exportSession.status)")
throw exportSession.error!
}
Currently, it seems to work on iPhone simulators (confirmed on iOS 16.1/15.5) but it doesn't on my iPhone 7 (iOS 15.7.1) real device. It doesn't seem to work as well on my colleague's iOS 16.1 real device, so it shouldn't be a matter of the iOS version.
The mp4 file is located in the iOS Files app and the inputUrl
in the above code becomes something like this (I get this URL via UIDocumentPickerViewController
):
file:///private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/Downloads/おしゃべりひろゆき.mp4
and the error is:
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x2808f30c0 {Error Domain=NSOSStatusErrorDomain Code=-16979 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-16979), NSLocalizedRecoverySuggestion=XXXXDEFAULTVALUEXXXX, NSURL=file:///private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/Downloads/おしゃべりひろゆき.mp4, NSLocalizedDescription=The operation could not be completed}
CodePudding user response:
It seems to be resolved by calling startAccessingSecurityScopedResource()
to the inputUrl
before exporting.
inputUrl.startAccessingSecurityScopedResource()
Not sure exactly why but that's probably because the inputUrl
is under the file:///private
namespace?
CodePudding user response:
Use this function for extract audio from video :----
func extractAudioFromVideo(videoUrl:URL) {
let mixComposition: AVMutableComposition = AVMutableComposition()
var mutableCompositionAudioVideoTrack: [AVMutableCompositionTrack] = []
let videoAsset: AVAsset = AVAsset(url: videoUrl)
if let audioVideoTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid){
mutableCompositionAudioVideoTrack.append(audioVideoTrack)
if let audioVideoAssetTrack: AVAssetTrack = videoAsset.tracks(withMediaType: .audio).first {
do {
try mutableCompositionAudioVideoTrack.first?.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration), of: audioVideoAssetTrack, at: CMTime.zero)
} catch {
print(error)
}
}
}
if let documentsPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first {
let outputURL = URL(fileURLWithPath: documentsPath).appendingPathComponent(".m4a")
do {
if FileManager.default.fileExists(atPath: outputURL.path) {
try FileManager.default.removeItem(at: outputURL)
}
} catch { }
if let exportSession = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetAppleM4A) {
exportSession.outputURL = outputURL
exportSession.outputFileType = AVFileType.m4a
exportSession.shouldOptimizeForNetworkUse = true
exportSession.exportAsynchronously(completionHandler: {
switch exportSession.status {
case . completed:
DispatchQueue.main.async {
print("audio url :---- \(outputURL)")
// -------- play output audio URL in player ------
}
case .failed:
if let _error = exportSession.error {
print(_error.localizedDescription)
}
case .cancelled:
if let _error = exportSession.error {
print(_error.localizedDescription)
}
default:
print("")
}
})
}
}
}