63 lines
2.2 KiB
Swift
63 lines
2.2 KiB
Swift
import AVFoundation
|
|
import Foundation
|
|
|
|
enum DubbingCompositorError: LocalizedError {
|
|
case noAudioTracks
|
|
case cannotReadDuration
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .noAudioTracks:
|
|
return "录音没有音频轨道"
|
|
case .cannotReadDuration:
|
|
return "无法读取录音时长"
|
|
}
|
|
}
|
|
}
|
|
|
|
enum DubbingCompositor {
|
|
static func merge(urls: [URL], outputURL: URL) async throws {
|
|
try await mergeSync(urls: urls, outputURL: outputURL)
|
|
}
|
|
|
|
private static func mergeSync(urls: [URL], outputURL: URL) async throws {
|
|
try? FileManager.default.removeItem(at: outputURL)
|
|
let composition = AVMutableComposition()
|
|
var currentTime = CMTime.zero
|
|
for url in urls {
|
|
let source = AVURLAsset(url: url)
|
|
let tracks = try await source.loadTracks(withMediaType: .audio)
|
|
guard let track = tracks.first else {
|
|
throw DubbingCompositorError.noAudioTracks
|
|
}
|
|
let duration = try await source.load(.duration)
|
|
guard duration.seconds.isFinite, duration.seconds > 0 else {
|
|
throw DubbingCompositorError.cannotReadDuration
|
|
}
|
|
let timeRange = CMTimeRange(start: .zero, duration: duration)
|
|
let compositionTrack = composition.addMutableTrack(
|
|
withMediaType: .audio,
|
|
preferredTrackID: kCMPersistentTrackID_Invalid
|
|
)
|
|
guard let compositionTrack else {
|
|
throw DubbingCompositorError.noAudioTracks
|
|
}
|
|
try compositionTrack.insertTimeRange(timeRange, of: track, at: currentTime)
|
|
currentTime = currentTime + duration
|
|
}
|
|
|
|
guard let exporter = AVAssetExportSession(
|
|
asset: composition,
|
|
presetName: AVAssetExportPresetAppleM4A
|
|
) else {
|
|
throw DubbingCompositorError.noAudioTracks
|
|
}
|
|
exporter.outputURL = outputURL
|
|
exporter.outputFileType = .m4a
|
|
await exporter.export()
|
|
guard exporter.status == .completed else {
|
|
throw exporter.error ?? DubbingCompositorError.noAudioTracks
|
|
}
|
|
}
|
|
}
|