BFRecordExport.swift 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. //
  2. // BFRecordExport.swift
  3. // BFRecordScreenKit
  4. //
  5. // Created by 胡志强 on 2021/11/25.
  6. // 录屏视频导出
  7. import AVFoundation
  8. import BFCommonKit
  9. import BFMediaKit
  10. import Foundation
  11. import GPUImage
  12. import Photos
  13. import UIKit
  14. public enum ExportError: Int {
  15. case FileNotExist = -31001
  16. case DataLost = -31002
  17. case VoiceLost = -31003
  18. case TotalDurError = -31004
  19. case ExportExcept = -31005
  20. case DiskNoSpace = -31006
  21. }
  22. public class BFRecordExport {
  23. public var progress: ((Float) -> Void)?
  24. public var exportCompletion: ((Error?, URL?) -> Void)?
  25. public var originSoundVolumn: Float = 1.0 // 无录音时原声大小
  26. public var originSoundInRecordVolumn: Float = 0.0 // 录音时原声大小
  27. var titleStickers = [PQEditSubTitleModel]()
  28. var voiceList = [PQEditVisionTrackMaterialsModel]()
  29. var videoStickers = [PQEditVisionTrackMaterialsModel]()
  30. public var data: [BFRecordItemModel]? {
  31. didSet {
  32. if data?.count ?? 0 > 0 {
  33. for item in data! {
  34. item.generationTimeRanges()
  35. }
  36. }
  37. }
  38. }
  39. //选择的背景音乐信息
  40. public var bgmModel:PQVoiceModel?
  41. var count = 0
  42. var stickerRanges = [CMTimeRange]()
  43. var exporter: PQCompositionExporter?
  44. // var mStickers = [PQEditVisionTrackMaterialsModel]()
  45. deinit {}
  46. public init() {}
  47. // MARK: -
  48. /// synthesisAll: 合成所有还是只合成录音部分
  49. public func startExprot(synthesisAll: Bool) {
  50. // 1,背景视频素材
  51. if let itemModels = data {
  52. var totalDur = 0.0
  53. titleStickers.removeAll()
  54. voiceList.removeAll()
  55. videoStickers.removeAll()
  56. // 切割视频素材
  57. for (_, itemModel) in itemModels.enumerated() {
  58. // 保留录音部分时,是否按录音顺序合成最终视频;
  59. // 如果需要排序,则排视频的顺序;否则排音频的顺序
  60. let needSort = false
  61. if needSort {
  62. } else {
  63. // 音频排序
  64. var useVoices = itemModel.getUsedVoices()
  65. useVoices.sort { m1, m2 in
  66. m1.startCMTime.seconds < m2.startCMTime.seconds
  67. }
  68. // 字幕排序
  69. itemModel.titleStickers.sort { model1, model2 in
  70. model1.timelineIn < model2.timelineIn
  71. }
  72. }
  73. switch itemModel.mediaType {
  74. case .Image:
  75. totalDur += parseImage(synthesisAll: synthesisAll, itemModel: itemModel, totalDur: totalDur)
  76. case .some(.Video):
  77. let dur = parseVideo(synthesisAll: synthesisAll, itemModel: itemModel, totalDur: totalDur)
  78. if dur >= 0 {
  79. totalDur += dur
  80. }else{
  81. // -1 时出错
  82. return
  83. }
  84. case .some(.Camera):
  85. totalDur += parseCamera(synthesisAll: synthesisAll, itemModel: itemModel, totalDur: totalDur)
  86. case .none:
  87. break
  88. }
  89. // 视频处理
  90. }
  91. beginExport(synthesisAll: synthesisAll)
  92. }
  93. }
  94. func parseImage(synthesisAll: Bool, itemModel: BFRecordItemModel, totalDur:Double) -> Double{
  95. if !synthesisAll, itemModel.getUsedVoices().count == 0 {
  96. // 图片无录音在保留模式里不合成
  97. return 0
  98. }
  99. var duration = itemModel.materialDuraion.seconds
  100. if itemModel.getUsedVoices().count == 0 {
  101. // 图片无录音保持2s
  102. duration = 2
  103. let voice = PQEditVisionTrackMaterialsModel()
  104. voice.model_in = 0.0
  105. voice.out = 2.0
  106. voice.aptDuration = 2.0
  107. // voice.voiceType = VOICETYPT.None.rawValue
  108. voice.volumeGain = 100
  109. voiceList.append(voice)
  110. } else {
  111. //
  112. for mod in itemModel.getUsedVoices() {
  113. let sticker = PQEditVisionTrackMaterialsModel()
  114. sticker.model_in = 0
  115. sticker.out = mod.endCMTime.seconds - mod.startCMTime.seconds
  116. sticker.timelineIn = totalDur + mod.startCMTime.seconds
  117. sticker.timelineOut = totalDur + mod.endCMTime.seconds
  118. sticker.aptDuration = sticker.out
  119. sticker.duration = sticker.out
  120. sticker.locationPath = mod.wavFilePath
  121. sticker.volumeGain = 100 // Float64(model.volume)
  122. voiceList.append(sticker)
  123. }
  124. }
  125. let sticker = splitBaseMaterial(timelineIn: totalDur, model_in: 0, duration: duration)
  126. sticker.originalUImage = itemModel.coverImg
  127. sticker.volumeGain = 0
  128. sticker.type = StickerType.IMAGE.rawValue
  129. videoStickers.append(sticker)
  130. BFLog(1, message: "image sticker - timIn:\(sticker.timelineIn), modIn:\(sticker.model_in), dur:\(duration)")
  131. for titleS in itemModel.titleStickers {
  132. // let leng = titleS.timelineOut - titleS.timelineIn
  133. let newTitleSticker = PQEditSubTitleModel()
  134. titleStickers.append(newTitleSticker)
  135. newTitleSticker.text = titleS.text
  136. newTitleSticker.setting = titleS.setting
  137. newTitleSticker.timelineIn = titleS.timelineIn + CMTime(seconds: totalDur, preferredTimescale: 1000)
  138. newTitleSticker.timelineOut = titleS.timelineOut + CMTime(seconds: totalDur, preferredTimescale: 1000)
  139. }
  140. return duration
  141. }
  142. func parseVideo(synthesisAll: Bool, itemModel: BFRecordItemModel, totalDur:Double) -> Double {
  143. var subDur = 0.0
  144. if let localPath = itemModel.localPath {
  145. if !FileManager.default.fileExists(atPath: localPath) {
  146. let error = NSError(domain: "err", code: ExportError.FileNotExist.rawValue, userInfo: ["msg": "file not exist"])
  147. exportCompletion?(error as Error, nil)
  148. return subDur
  149. }
  150. if synthesisAll {
  151. let drangs = itemModel.dealedDurationRanges
  152. for srange in drangs {
  153. let range = srange.range
  154. let sticker = splitBaseMaterial(timelineIn: totalDur + subDur, model_in: range.start.seconds, duration: range.duration.seconds)
  155. sticker.locationPath = localPath
  156. sticker.volumeGain = Float64(srange.isRecord ? originSoundInRecordVolumn * 100 : originSoundVolumn * 100)
  157. videoStickers.append(sticker)
  158. subDur += range.duration.seconds
  159. if srange.isRecord {
  160. itemModel.getUsedVoices().forEach { mod in
  161. let sticker = PQEditVisionTrackMaterialsModel()
  162. sticker.model_in = 0
  163. sticker.out = mod.endCMTime.seconds - mod.startCMTime.seconds
  164. sticker.timelineIn = totalDur + mod.startCMTime.seconds
  165. sticker.timelineOut = totalDur + mod.endCMTime.seconds
  166. sticker.aptDuration = sticker.out
  167. sticker.duration = sticker.out
  168. sticker.locationPath = mod.wavFilePath
  169. sticker.volumeGain = 100 // Float64(model.volume)
  170. voiceList.append(sticker)
  171. let ttsAudioAsset = AVURLAsset(url: URL(fileURLWithPath: sticker.locationPath), options: avAssertOptions)
  172. BFLog(message: "导出使用的音频文件 timelineIn:\(sticker.timelineIn) timelineOut\(sticker.timelineOut) sticker.locationPath\( sticker.locationPath) tss语音时长\(ttsAudioAsset.duration.seconds)")
  173. }
  174. }
  175. }
  176. for titleS in itemModel.titleStickers {
  177. let newTitleSticker = PQEditSubTitleModel()
  178. titleStickers.append(newTitleSticker)
  179. newTitleSticker.text = titleS.text
  180. newTitleSticker.setting = titleS.setting
  181. newTitleSticker.timelineIn = titleS.timelineIn + CMTime(seconds: totalDur, preferredTimescale: 1000)
  182. newTitleSticker.timelineOut = titleS.timelineOut + CMTime(seconds: totalDur, preferredTimescale: 1000)
  183. }
  184. } else {
  185. // 只保留录音部分
  186. var drangs = itemModel.dealedDurationRanges.filter { srange in
  187. srange.isRecord == true
  188. }
  189. let needSort = false
  190. if needSort {
  191. drangs.sort { range1, range2 in
  192. range1.index < range2.index
  193. }
  194. }
  195. for (index, srange) in drangs.enumerated() {
  196. let range = srange.range
  197. let sticker = splitBaseMaterial(timelineIn: totalDur + subDur, model_in: range.start.seconds, duration: range.duration.seconds)
  198. sticker.locationPath = localPath
  199. sticker.volumeGain = Float64(srange.isRecord ? originSoundInRecordVolumn * 100 : originSoundVolumn * 100)
  200. videoStickers.append(sticker)
  201. let voiceSticker = itemModel.voiceStickers[index]
  202. // mdf by ak
  203. if(itemModel.voiceChangeStickers.count > 0){//有变音
  204. let voiceChangeModels = itemModel.voiceChangeStickers.filter { mod in
  205. mod.recordId == voiceSticker.recordId
  206. }
  207. for voice in voiceChangeModels{
  208. BFLog(2, message: "当前变音所属:\(voiceSticker.recordId ?? "") text:\(voice.wavFilePath ?? "")")
  209. let voiceChangeSt = PQEditVisionTrackMaterialsModel()
  210. voiceChangeSt.model_in = 0
  211. voiceChangeSt.out = voice.endCMTime.seconds - voice.startCMTime.seconds
  212. voiceChangeSt.timelineIn = totalDur + subDur + (voice.startCMTime.seconds - voiceSticker.startCMTime.seconds)
  213. voiceChangeSt.timelineOut = voiceChangeSt.timelineIn + voiceChangeSt.out
  214. voiceChangeSt.aptDuration = voiceChangeSt.out
  215. voiceChangeSt.duration = voiceChangeSt.out
  216. voiceChangeSt.locationPath = voice.wavFilePath
  217. voiceChangeSt.volumeGain = 100 // Float64(model.volume)
  218. voiceList.append(voiceChangeSt)
  219. }
  220. }else{//无变音
  221. let voice = PQEditVisionTrackMaterialsModel()
  222. voice.model_in = 0
  223. voice.out = voiceSticker.endCMTime.seconds - voiceSticker.startCMTime.seconds
  224. voice.timelineIn = totalDur + subDur
  225. voice.timelineOut = totalDur + subDur + voice.out
  226. voice.aptDuration = voice.out
  227. voice.duration = voice.out
  228. voice.locationPath = voiceSticker.wavFilePath
  229. voice.volumeGain = 100 // Float64(model.volume)
  230. voiceList.append(voice)
  231. }
  232. let titleModels = itemModel.titleStickers.filter { mod in
  233. mod.recordId == voiceSticker.recordId
  234. }
  235. // 字幕的时间点是以相关录音的原点为坐标计算的。
  236. for titleS in titleModels {
  237. let newTitleSticker = PQEditSubTitleModel()
  238. titleStickers.append(newTitleSticker)
  239. newTitleSticker.text = titleS.text
  240. newTitleSticker.setting = titleS.setting
  241. newTitleSticker.timelineIn = titleS.timelineIn + CMTime(seconds: totalDur + subDur , preferredTimescale: 1000) - voiceSticker.startCMTime
  242. newTitleSticker.timelineOut = titleS.timelineOut + CMTime(seconds: totalDur + subDur, preferredTimescale: 1000) - voiceSticker.startCMTime
  243. BFLog(1, message: "timein - \(newTitleSticker.timelineIn), out - \(newTitleSticker.timelineOut)")
  244. }
  245. subDur += range.duration.seconds
  246. }
  247. }
  248. }
  249. return subDur
  250. }
  251. func parseCamera(synthesisAll: Bool, itemModel: BFRecordItemModel, totalDur:Double) -> Double {
  252. var duration = itemModel.materialDuraion.seconds
  253. // 视频分解
  254. for mod in itemModel.videoStickers {
  255. let sticker = PQEditVisionTrackMaterialsModel()
  256. sticker.type = StickerType.VIDEO.rawValue
  257. sticker.canvasFillType = stickerContentMode.aspectFitStr.rawValue
  258. sticker.model_in = 0
  259. sticker.out = (mod.timelineCMOut - mod.timelineCMIn).seconds
  260. sticker.timelineIn = totalDur + mod.timelineCMIn.seconds
  261. sticker.timelineOut = totalDur + mod.timelineCMOut.seconds
  262. sticker.aptDuration = sticker.out
  263. sticker.duration = sticker.out
  264. sticker.locationPath = mod.locationPath.replacingOccurrences(of: documensDirectory, with: "")
  265. sticker.volumeGain = 1 // Float64(model.volume)
  266. videoStickers.append(sticker)
  267. }
  268. // 音频分解
  269. for mod in itemModel.getUsedVoices() {
  270. let sticker = PQEditVisionTrackMaterialsModel()
  271. sticker.model_in = 0
  272. sticker.type = StickerType.VOICE.rawValue
  273. sticker.out = mod.endCMTime.seconds - mod.startCMTime.seconds
  274. sticker.timelineIn = totalDur + mod.startCMTime.seconds
  275. sticker.timelineOut = totalDur + mod.endCMTime.seconds
  276. sticker.aptDuration = sticker.out
  277. sticker.duration = sticker.out
  278. sticker.locationPath = mod.wavFilePath
  279. sticker.volumeGain = 100 // Float64(model.volume)
  280. voiceList.append(sticker)
  281. }
  282. // 字幕分解
  283. for titleS in itemModel.titleStickers {
  284. let newTitleSticker = PQEditSubTitleModel()
  285. titleStickers.append(newTitleSticker)
  286. newTitleSticker.text = titleS.text
  287. newTitleSticker.setting = titleS.setting
  288. newTitleSticker.timelineIn = titleS.timelineIn + CMTime(seconds: totalDur, preferredTimescale: 1000)
  289. newTitleSticker.timelineOut = titleS.timelineOut + CMTime(seconds: totalDur, preferredTimescale: 1000)
  290. }
  291. return duration
  292. }
  293. public func cancelExport() {
  294. exporter?.cancel()
  295. }
  296. public func clearFileCache() {
  297. data?.forEach { itemModel in
  298. itemModel.getUsedVoices().forEach { model in
  299. if let localPath = model.wavFilePath {
  300. try? FileManager.default.removeItem(atPath: localPath)
  301. }
  302. }
  303. }
  304. }
  305. enum DispatchError: Error {
  306. case timeout
  307. }
  308. func getOutputFilePath() -> URL {
  309. var outPutMP4Path = exportVideosDirectory
  310. if !directoryIsExists(dicPath: outPutMP4Path) {
  311. createDirectory(path: outPutMP4Path)
  312. }
  313. outPutMP4Path.append("video_\(String.qe.timestamp()).mp4")
  314. return URL(fileURLWithPath: outPutMP4Path)
  315. }
  316. // 切割素材
  317. func splitBaseMaterial(timelineIn: Double, model_in: Double, duration: Double) -> PQEditVisionTrackMaterialsModel {
  318. let bgMovieInfo: PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel()
  319. bgMovieInfo.type = StickerType.VIDEO.rawValue
  320. bgMovieInfo.timelineIn = timelineIn
  321. bgMovieInfo.timelineOut = timelineIn + duration
  322. bgMovieInfo.model_in = model_in
  323. bgMovieInfo.out = model_in + duration
  324. bgMovieInfo.canvasFillType = stickerContentMode.aspectFitStr.rawValue
  325. bgMovieInfo.volumeGain = 1
  326. bgMovieInfo.aptDuration = bgMovieInfo.timelineOut
  327. bgMovieInfo.duration = bgMovieInfo.timelineOut
  328. BFLog(1, message: "hhh- timIn:\(timelineIn), modIn:\(model_in), dur:\(duration)")
  329. return bgMovieInfo
  330. }
  331. // 因为titleStickers 是传递过来的,会修改timelinein,需要重新生成,以免影响原来的数据
  332. // voiceList是考虑到图片有时候没有录音,在保留全部时,需要添加一个2秒的空sticker
  333. func beginExport(synthesisAll: Bool) {
  334. // 输出视频地址
  335. // exprotVideo()
  336. // return;
  337. var outPutMP4Path = exportVideosDirectory
  338. if !directoryIsExists(dicPath: outPutMP4Path) {
  339. createDirectory(path: outPutMP4Path)
  340. }
  341. outPutMP4Path.append("video_\(String.qe.timestamp()).mp4")
  342. let outPutMP4URL = URL(fileURLWithPath: outPutMP4Path)
  343. BFLog(1, message: "导出视频地址 \(outPutMP4URL)")
  344. guard let itemData = data else {
  345. let error = NSError(domain: "err", code: ExportError.DataLost.rawValue, userInfo: ["msg": "compose_fail_lost".BFLocale])
  346. exportCompletion?(error as Error, nil)
  347. return
  348. }
  349. // 处理导出
  350. guard let voiceCount = data?.reduce(0, { partialResult, itemModell in
  351. itemModell.getUsedVoices().count + partialResult
  352. }) else {
  353. BFLog(1, message: "getUsedVoices() count += nil")
  354. let error = NSError(domain: "err", code: ExportError.VoiceLost.rawValue, userInfo: ["msg": "getUsedVoices() count += nil"])
  355. exportCompletion?(error as Error, nil)
  356. return
  357. }
  358. guard let totalDuration = data?.reduce(0.0, { partialResult, itemModell in
  359. var modelDuraion = 0.0
  360. if itemModell.mediaType == .Image {
  361. if itemModell.getUsedVoices().count == 0, synthesisAll {
  362. modelDuraion += 2
  363. } else {
  364. modelDuraion = itemModell.materialDuraion.seconds
  365. }
  366. } else if itemModell.mediaType == .Video {
  367. modelDuraion = itemModell.dealedDurationRanges.reduce(0.0) { partialResult, srange in
  368. // partialResult + (!synthesisAll && srange.isRecord) ?
  369. if synthesisAll {
  370. return partialResult + srange.range.duration.seconds
  371. } else {
  372. return partialResult + (srange.isRecord ? srange.range.duration.seconds : 0)
  373. }
  374. }
  375. }else if itemModell.mediaType == .Camera {
  376. modelDuraion = itemModell.materialDuraion.seconds
  377. }
  378. return (partialResult ?? 0) + modelDuraion
  379. }) else {
  380. let error = NSError(domain: "err", code: ExportError.TotalDurError.rawValue, userInfo: ["msg": "时长计算出错"])
  381. exportCompletion?(error as Error, nil)
  382. return
  383. }
  384. // MARK: - 声音合成
  385. // 有录音操作或者多个视频,就会进入合成步骤,否则就是一个没有处理的素材,直接导出就行了
  386. if voiceCount > 0 || videoStickers.count > 1 {
  387. let (audioMix, composition) = mergeAudio(videoStickers: videoStickers, audios: voiceList, synthesisAll: synthesisAll)
  388. let outputSize: CGSize = CGSize(width: 1080, height: Int(1080 * CGFloat(UIScreen.main.bounds.size.height / UIScreen.main.bounds.size.width)))
  389. BFLog(message: "输出视频大小:\(outputSize)")
  390. // add by ak 有字幕数据 & 显示字幕开关打开 添加字幕filter
  391. var subtitleFilter:PQSubTitleFilter?
  392. if titleStickers.count > 0,titleStickers.first?.setting.subtitleIsShow ?? true {
  393. subtitleFilter = PQSubTitleFilter(st: titleStickers, inputSize: outputSize)
  394. }
  395. exporter = PQCompositionExporter(asset: composition, videoComposition: nil, audioMix: audioMix, filters:nil,stickers: videoStickers, animationTool: nil, exportURL: outPutMP4URL,subTitleFilter:subtitleFilter)
  396. var orgeBitRate = Int(outputSize.width * outputSize.height * 3)
  397. for stick in videoStickers {
  398. if stick.type == StickerType.VIDEO.rawValue, stick.locationPath.count > 0 {
  399. let asset = AVURLAsset(url: URL(fileURLWithPath: stick.locationPath), options: avAssertOptions)
  400. let cbr = asset.tracks(withMediaType: .video).first?.estimatedDataRate
  401. if Int(cbr ?? 0) > orgeBitRate {
  402. orgeBitRate = Int(cbr ?? 0)
  403. }
  404. }
  405. }
  406. BFLog(1, message: String(format: "导出设置的码率为:%.3f MB", Double(orgeBitRate) / 1024.0 / 1024.0 / 8.0))
  407. let preSize = Double(orgeBitRate) * totalDuration / (1024 * 1024 * 8)
  408. let freeSize = PQBridgeObject.getPhoneDiskFreeSize()
  409. if preSize + 100.0 > freeSize { // 存储完后磁盘剩余至少100M
  410. let error = NSError(domain: "err", code: ExportError.DiskNoSpace.rawValue, userInfo: ["msg": "\("option_need".BFLocale)\(Int(preSize))MB,\("option_available".BFLocale)\(Int(freeSize))MB"])
  411. exportCompletion?(error as Error, nil)
  412. return
  413. }
  414. let tempBeginExport = Date().timeIntervalSince1970
  415. if exporter!.prepare(videoSize: outputSize, videoAverageBitRate: orgeBitRate) {
  416. exporter!.start(playeTimeRange: CMTimeRange(start: CMTime.zero, end: CMTime(seconds: totalDuration, preferredTimescale: 1000)))
  417. }
  418. exporter?.progressClosure = { [weak self] _, _, progress in
  419. // BFLog(message: "正片合成进度 \(progress * 100)%")
  420. let useProgress = progress > 1 ? 1 : progress
  421. if progress > 0 { // 更新进度
  422. self?.progress?(useProgress)
  423. }
  424. }
  425. exporter?.completion = { [weak self] url in
  426. // 输出视频时长
  427. if let url = url {
  428. let outSeconds = CMTimeGetSeconds(AVAsset(url: url).duration)
  429. let exportEndTime = Date().timeIntervalSince1970
  430. BFLog(1, message: "生成视频时长为:\(outSeconds) 总用时:\(exportEndTime - tempBeginExport)")
  431. if(PQENVUtil.shared.channel == "Development"){
  432. cShowHUB(superView: nil, msg: (outSeconds == 0) ? "compose_retry3".BFLocale : String(format: "总用时: %.2f", exportEndTime - tempBeginExport))
  433. }
  434. self?.exportCompletion?(nil, url)
  435. } else {
  436. let error = NSError(domain: "err", code: ExportError.ExportExcept.rawValue, userInfo: ["msg": "compose_fail_export".BFLocale])
  437. self?.exportCompletion?(error as Error, nil)
  438. cShowHUB(superView: nil, msg: "compose_fail_export".BFLocale)
  439. }
  440. // 导出完成后取消导出
  441. self?.exporter?.cancel()
  442. }
  443. } else {
  444. // 没有处理,直接copy原文件
  445. if let localPath = data?.first?.localPath {
  446. exportCompletion?(nil, URL(fileURLWithPath: localPath))
  447. }
  448. }
  449. }
  450. func dealAsset() {
  451. // asset?.tracks.first(where: { track in
  452. // if track.mediaType == .audio{
  453. //
  454. // }
  455. // })
  456. }
  457. func getVideoSize(asset: AVURLAsset) -> CGSize {
  458. var size = CGSize.zero
  459. asset.tracks.forEach { track in
  460. if track.mediaType == .video {
  461. let realSize = __CGSizeApplyAffineTransform(track.naturalSize, track.preferredTransform)
  462. size = CGSize(width: ceil(abs(realSize.width)), height: ceil(abs(realSize.height)))
  463. }
  464. }
  465. return size
  466. }
  467. }
  468. extension BFRecordExport {
  469. func mergeAudio(videoStickers: [PQEditVisionTrackMaterialsModel], audios: [PQEditVisionTrackMaterialsModel]?, synthesisAll: Bool) -> (AVMutableAudioMix, AVMutableComposition) {
  470. let composition = AVMutableComposition()
  471. let audioMix = AVMutableAudioMix()
  472. var tempParameters = [AVMutableAudioMixInputParameters]()
  473. var totalDuration: Float64 = 0
  474. for sticker in videoStickers {
  475. if sticker.volumeGain == 0 {
  476. // 如果添加了会有刺啦音
  477. BFLog(message: "音频音量 为0 不添加")
  478. continue
  479. }
  480. // sticker.volumeGain = 50
  481. totalDuration = max(totalDuration, sticker.duration)
  482. tempParameters += PQPlayerViewModel.dealWithMaterialTrack(stickerModel: sticker, composition: composition)
  483. }
  484. if let voices = audios {
  485. if synthesisAll {
  486. tempParameters += mergeRecordVoiceAll(voices: voices, composition)
  487. } else {
  488. tempParameters += mergeRecordVoiceOnly(voices: voices, composition)
  489. }
  490. }
  491. //add by ak 处理背景音乐 如果选择了背景音乐添加背景音乐音轨
  492. BFLog(message: "合成视频总时长:\(composition.duration.seconds)")
  493. if bgmModel != nil, bgmModel?.wavFilePath != nil {
  494. bgmModel?.startTime = bgmModel?.currentTime ?? 0.0
  495. let bgmParameters = PQPlayerViewModel.dealWithBGMTrack(bgmData: bgmModel!, totalDuration: composition.duration.seconds, composition: composition)
  496. if bgmParameters.count > 0 {
  497. tempParameters += bgmParameters
  498. }
  499. }
  500. audioMix.inputParameters = tempParameters
  501. return (audioMix, composition)
  502. }
  503. func mergeRecordVoiceOnly(voices: [PQEditVisionTrackMaterialsModel], _ composition: AVMutableComposition) -> [AVMutableAudioMixInputParameters] {
  504. var tempParameters = [AVMutableAudioMixInputParameters]()
  505. var totalDur: Double = 0.0
  506. for model in voices {
  507. if model.volumeGain == 0 {
  508. // 如果添加了会有刺啦音
  509. BFLog(message: "音频音量 为0 不添加")
  510. continue
  511. }
  512. tempParameters += PQPlayerViewModel.dealWithMaterialTrack(stickerModel: model, composition: composition)
  513. totalDur += model.aptDuration
  514. }
  515. return tempParameters
  516. }
  517. func mergeRecordVoiceAll(voices: [PQEditVisionTrackMaterialsModel], _ composition: AVMutableComposition) -> [AVMutableAudioMixInputParameters] {
  518. var tempParameters = [AVMutableAudioMixInputParameters]()
  519. var totalDur = 0.0
  520. for model in voices {
  521. if model.volumeGain == 0 {
  522. // 如果添加了会有刺啦音
  523. BFLog(message: "音频音量 为0 不添加")
  524. continue
  525. }
  526. tempParameters += PQPlayerViewModel.dealWithMaterialTrack(stickerModel: model, composition: composition)
  527. totalDur += model.aptDuration
  528. }
  529. return tempParameters
  530. }
  531. }