// // DubbingComposer.swift // dubit // // Created by zack on 16/12/20. // Copyright © 2016年 Chengdu Aitu Education Technology Ltd. All rights reserved. // import Foundation import AVFoundation class DubbingComposer { var timeline: [Double] var videoUrl: URL? var musicUrl: URL var recordsUrl: [String] var preTime: Double = 0 init(timeline: [Double], videoUrl: URL?, musicUrl: URL, recordsUrl: [String]){ self.timeline = timeline self.videoUrl = videoUrl self.musicUrl = musicUrl self.recordsUrl = recordsUrl } func compose(_ output: URL, onSuccess successBlock: (()->())?, onFail failBlock:((_ message: String)->())? ) { //初始化合成器 let composition = AVMutableComposition() var videoTimeRange:CMTimeRange? //为合成器添加视频轨道 if((videoUrl) != nil){ let videoTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid) let videoAsset = AVURLAsset(url: videoUrl!, options: nil) videoTimeRange = CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration) do { try videoTrack?.insertTimeRange(videoTimeRange!, of: videoAsset.tracks(withMediaType: AVMediaType.video).first!, at: CMTime.zero) } catch { DispatchQueue.main.async(execute: { failBlock?("Fail on load video") }) return } } //添加音乐轨道 let musicTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid) let musicAsset = AVURLAsset(url: musicUrl, options: nil) let musicTimeRange = videoTimeRange ?? CMTimeRangeMake(start: CMTime.zero, duration: musicAsset.duration) do { try musicTrack?.insertTimeRange(musicTimeRange, of: musicAsset.tracks(withMediaType: AVMediaType.audio).first!, at: CMTime.zero) print("创建音乐音轨成功") } catch { print("创建音乐音轨失败") } //添加两条配音轨道 let recordTrackA = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid) let recordTrackB = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid) //将配音交替放入两条配音轨道 for (i, url) in recordsUrl.enumerated() { let recordUrl = URL(fileURLWithPath: url) let recordAsset = AVURLAsset(url: recordUrl, options: nil) let recordRange = CMTimeRangeMake(start: CMTime.zero, duration: recordAsset.duration) let currentSec = timeline[i] let beginSec = currentSec > preTime ? currentSec - preTime : currentSec let beginTime = CMTimeMakeWithSeconds(beginSec, preferredTimescale: 100) if(recordAsset.tracks(withMediaType: AVMediaType.audio).count > 0){ let recordAssetTrack = recordAsset.tracks(withMediaType: AVMediaType.audio).first! let recordTrack = i % 2 == 0 ? recordTrackA : recordTrackB do { try recordTrack?.insertTimeRange(recordRange, of: recordAssetTrack, at: beginTime) } catch{ print("Composer error on record: \(i)") } } } let manager = FileManager.default do { try manager.removeItem(at: output) print("删除旧输出成功") } catch { print("删除旧输出失败") } //输出到文件 if let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetMediumQuality) { assetExport.outputFileType = AVFileType.mp4 assetExport.outputURL = output assetExport.shouldOptimizeForNetworkUse = true assetExport.exportAsynchronously(completionHandler: { if((assetExport.error) != nil){ print(assetExport.error!) DispatchQueue.main.async(execute: { failBlock?("Something wrong on composition") }) }else{ successBlock?() } return }) } else { DispatchQueue.main.async(execute: { failBlock?("Something wrong on composition") }) } } }