|
@@ -567,7 +567,7 @@ class PQStuckPointPublicController: PQBaseViewController {
|
|
|
exportSession.cancelExport()
|
|
|
}
|
|
|
// 开始导出
|
|
|
- beginExport()
|
|
|
+ appendAudio()
|
|
|
/// 保存草稿
|
|
|
saveDraftbox()
|
|
|
// 曝光上报:窗口曝光
|
|
@@ -631,7 +631,150 @@ class PQStuckPointPublicController: PQBaseViewController {
|
|
|
extension PQStuckPointPublicController {
|
|
|
/// fp1 - 导出视频
|
|
|
/// 开始导出视频
|
|
|
- func beginExport() {
|
|
|
+
|
|
|
+ /// 合并声音
|
|
|
+ /// - Parameter urls: 所有音频的URL 是全路径方便复用
|
|
|
+ /// - Parameter completeHander: 返回的 URL 全路径的 URL 如果要保存替换掉前缀
|
|
|
+ func mergeAudios(originAsset: AVURLAsset, mTotalDuration: Float,clipAudioRange: CMTimeRange = CMTimeRange.zero,mStartTime:CMTime = .zero , completeHander: @escaping (_ fileURL: URL?) -> Void) {
|
|
|
+
|
|
|
+
|
|
|
+ let timeInterval: TimeInterval = Date().timeIntervalSince1970
|
|
|
+ let composition = AVMutableComposition()
|
|
|
+
|
|
|
+ let originaDuration = CMTimeGetSeconds(clipAudioRange.duration)
|
|
|
+ BFLog(message: "处理主音频 原始时长startTime = \(originaDuration) 要显示时长totalDuration = \(mTotalDuration)")
|
|
|
+
|
|
|
+ if(originaDuration <= CMTimeGetSeconds(clipAudioRange.duration)){
|
|
|
+
|
|
|
+ BFLog(message: "不用拼接音频文件 \(originAsset.url) 时长is \(CMTimeGetSeconds(originAsset.duration))")
|
|
|
+ completeHander(originAsset.url)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ //整倍数
|
|
|
+ let count = Int(mTotalDuration) / Int(originaDuration)
|
|
|
+
|
|
|
+ //有余数多 clip 一整段
|
|
|
+ let row = mTotalDuration - Float(count) * Float(originaDuration)
|
|
|
+ //已经拼接的总时长
|
|
|
+ var totalDuration: CMTime = .zero
|
|
|
+ //第一段的时长
|
|
|
+ var duration: CMTime = .zero
|
|
|
+ //第一段的区间
|
|
|
+ var timeRange:CMTimeRange = CMTimeRange.zero
|
|
|
+ if count > 0 {
|
|
|
+ for index in 0 ... count {
|
|
|
+
|
|
|
+ duration = CMTime(value: CMTimeValue((CMTimeGetSeconds(clipAudioRange.end) - CMTimeGetSeconds(mStartTime)) * Double(playerTimescaleInt)), timescale: playerTimescaleInt)
|
|
|
+ BFLog(message: "每一个文件的 duration \(CMTimeGetSeconds(duration))")
|
|
|
+ var timeRange = CMTimeRangeMake(start: mStartTime, duration: duration)
|
|
|
+
|
|
|
+ if(index != 0){
|
|
|
+ //(CMTimeGetSeconds(clipAudioRange.end) - CMTimeGetSeconds(mStartTime))为用户选择的第一段时长
|
|
|
+ duration = CMTime(value: CMTimeValue((CMTimeGetSeconds( clipAudioRange.duration) * Double(index) + (CMTimeGetSeconds(clipAudioRange.end) - CMTimeGetSeconds(mStartTime))) * Float64(playerTimescaleInt)), timescale: playerTimescaleInt)
|
|
|
+ BFLog(message: "每一个文件的 duration \(CMTimeGetSeconds(duration))")
|
|
|
+ timeRange = clipAudioRange
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ BFLog(message: "合并的文件地址: \(originAsset.url)")
|
|
|
+ let audioAsset = originAsset
|
|
|
+ let tracks = audioAsset.tracks(withMediaType: .audio)
|
|
|
+ if tracks.count == 0 {
|
|
|
+ BFLog(message: "音频数据无效不进行合并,所有任务结束要确保输入的数据都正常! \(originAsset.url)")
|
|
|
+ break
|
|
|
+ }
|
|
|
+ let assetTrack: AVAssetTrack = tracks[0]
|
|
|
+
|
|
|
+ let compositionAudioTrack: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: CMPersistentTrackID())!
|
|
|
+
|
|
|
+ do {
|
|
|
+ //
|
|
|
+ try compositionAudioTrack.insertTimeRange(timeRange, of: assetTrack, at: totalDuration)
|
|
|
+
|
|
|
+ } catch {
|
|
|
+ BFLog(message: "error is \(error)")
|
|
|
+ }
|
|
|
+
|
|
|
+ totalDuration = CMTimeAdd(totalDuration, timeRange.duration)
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(row > 0){
|
|
|
+ duration = CMTime(value: CMTimeValue(Float(CMTimeGetSeconds(totalDuration)) * Float(playerTimescaleInt)), timescale: playerTimescaleInt)
|
|
|
+ timeRange = CMTimeRange(start: duration, end: CMTime(value: CMTimeValue((CMTimeGetSeconds(duration) + Double(row)) * Double(playerTimescaleInt)), timescale: playerTimescaleInt))
|
|
|
+
|
|
|
+ BFLog(message: "合并的文件地址: \(originAsset.url)")
|
|
|
+ let audioAsset = originAsset
|
|
|
+ let tracks = audioAsset.tracks(withMediaType: .audio)
|
|
|
+ if tracks.count == 0 {
|
|
|
+ BFLog(message: "音频数据无效不进行合并,所有任务结束要确保输入的数据都正常! \(originAsset.url)")
|
|
|
+
|
|
|
+ }
|
|
|
+ let assetTrack: AVAssetTrack = tracks[0]
|
|
|
+
|
|
|
+ let compositionAudioTrack: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: CMPersistentTrackID())!
|
|
|
+
|
|
|
+ do {
|
|
|
+ //
|
|
|
+ try compositionAudioTrack.insertTimeRange(timeRange, of: assetTrack, at: totalDuration)
|
|
|
+
|
|
|
+ } catch {
|
|
|
+ BFLog(message: "error is \(error)")
|
|
|
+ }
|
|
|
+
|
|
|
+ totalDuration = CMTimeAdd(totalDuration, audioAsset.duration)
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A)
|
|
|
+ BFLog(message: "assetExport.supportedFileTypes is \(String(describing: assetExport?.supportedFileTypes))")
|
|
|
+
|
|
|
+ assetExport?.outputFileType = .m4a
|
|
|
+ // XXXX 注意文件名的后缀要和outputFileType 一致 否则会导出失败
|
|
|
+ var audioFilePath = exportAudiosDirectory
|
|
|
+
|
|
|
+ if !directoryIsExists(dicPath: audioFilePath) {
|
|
|
+ BFLog(message: "文件夹不存在")
|
|
|
+ createDirectory(path: audioFilePath)
|
|
|
+ }
|
|
|
+ audioFilePath.append("merge_\(timeInterval).m4a")
|
|
|
+
|
|
|
+ let fileUrl = URL(fileURLWithPath: audioFilePath)
|
|
|
+
|
|
|
+ assetExport?.outputURL = fileUrl
|
|
|
+ assetExport?.exportAsynchronously {
|
|
|
+ if assetExport!.status == .completed {
|
|
|
+ // 85.819125
|
|
|
+ let audioAsset = AVURLAsset(url: fileUrl, options: avAssertOptions)
|
|
|
+
|
|
|
+ BFLog(message: "拼接声音文件 完成 \(fileUrl) 时长is \(CMTimeGetSeconds(audioAsset.duration))")
|
|
|
+ completeHander(fileUrl)
|
|
|
+
|
|
|
+ } else {
|
|
|
+ print("拼接出错 \(String(describing: assetExport?.error))")
|
|
|
+ completeHander(URL(string: ""))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ func appendAudio() {
|
|
|
+
|
|
|
+ let inputAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + (audioMixModel?.localPath ?? "")), options: nil)
|
|
|
+ mergeAudios(originAsset: inputAsset, mTotalDuration: finallyUserAudioTime,clipAudioRange: clipAudioRange,mStartTime: CMTime(value: CMTimeValue((mStickers?.first?.timelineIn ?? 0.0) * Float64(playerTimescaleInt)), timescale: playerTimescaleInt)) { [weak self] completURL in
|
|
|
+
|
|
|
+ if(completURL != nil){
|
|
|
+ let asset = AVURLAsset(url: completURL!, options: nil)
|
|
|
+ FilterLog(message: "拼接后音频时长\(asset.duration.seconds) url is \(String(describing: completURL))")
|
|
|
+ self?.beginExport(inputAsset: asset)
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ func beginExport(inputAsset:AVURLAsset!) {
|
|
|
if !(editProjectModel?.sData?.sections != nil && (editProjectModel?.sData?.sections.count ?? 0) > 0) {
|
|
|
BFLog(message: "项目段落错误❌")
|
|
|
return
|
|
@@ -645,13 +788,14 @@ extension PQStuckPointPublicController {
|
|
|
outPutMP4Path.append("video_\(String.qe.timestamp()).mp4")
|
|
|
let outPutMP4URL = URL(fileURLWithPath: outPutMP4Path)
|
|
|
BFLog(message: "导出视频地址 \(outPutMP4URL)")
|
|
|
- let inputAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + (audioMixModel?.localPath ?? "")), options: nil)
|
|
|
+// let inputAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + (audioMixModel?.localPath ?? "")), options: nil)
|
|
|
+
|
|
|
// 每次初始化的时候设置初始值 为 nIl
|
|
|
var audioMix: AVMutableAudioMix?
|
|
|
var composition: AVMutableComposition?
|
|
|
- if(finallyUserAudioTime != 0 && finallyUserAudioTime > Float(inputAsset.duration.seconds)){
|
|
|
- (audioMix, composition) = PQPlayerViewModel.setupAudioMix(originAsset: inputAsset, bgmData:nil, videoStickers: nil,originMusicDuration:finallyUserAudioTime,clipAudioRange: clipAudioRange)
|
|
|
- }
|
|
|
+// if(finallyUserAudioTime != 0 && finallyUserAudioTime > Float(inputAsset.duration.seconds)){
|
|
|
+// (audioMix, composition) = PQPlayerViewModel.setupAudioMix(originAsset: inputAsset, bgmData:nil, videoStickers: nil,originMusicDuration:finallyUserAudioTime,clipAudioRange: clipAudioRange,startTime:CMTime(value: CMTimeValue((mStickers?.first?.timelineIn ?? 0.0) * Float64(playerTimescaleInt)), timescale: playerTimescaleInt))
|
|
|
+// }
|
|
|
exporter = PQCompositionExporter(asset: inputAsset, videoComposition: nil, audioMix: audioMix, filters: nil, stickers: mStickers, animationTool: nil, exportURL: outPutMP4URL)
|
|
|
|
|
|
var orgeBitRate = (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 3
|
|
@@ -1217,7 +1361,7 @@ extension PQStuckPointPublicController {
|
|
|
@objc func willEnterForeground() {
|
|
|
BFLog(message: "进入到前台")
|
|
|
if !isExportSuccess {
|
|
|
- beginExport()
|
|
|
+ appendAudio()
|
|
|
}
|
|
|
|
|
|
playBtn.isHidden = true
|