|
@@ -13,13 +13,13 @@ import GPUImage
|
|
|
import Photos
|
|
|
import UIKit
|
|
|
|
|
|
-public enum ExportError : Int {
|
|
|
- case FileNotExist = -31001
|
|
|
- case DataLost = -31002
|
|
|
- case VoiceLost = -31003
|
|
|
- case TotalDurError = -31004
|
|
|
- case ExportExcept = -31005
|
|
|
- case DiskNoSpace = -31006
|
|
|
+public enum ExportError: Int {
|
|
|
+ case FileNotExist = -31001
|
|
|
+ case DataLost = -31002
|
|
|
+ case VoiceLost = -31003
|
|
|
+ case TotalDurError = -31004
|
|
|
+ case ExportExcept = -31005
|
|
|
+ case DiskNoSpace = -31006
|
|
|
}
|
|
|
|
|
|
let testFor = true
|
|
@@ -27,10 +27,10 @@ let testFor = true
|
|
|
public class BFRecordExport {
|
|
|
public var progress: ((Float) -> Void)?
|
|
|
public var exportCompletion: ((Error?, URL?) -> Void)?
|
|
|
-
|
|
|
- public var originSoundVolumn : Float = 1.0 // 无录音时原声大小
|
|
|
- public var originSoundInRecordVolumn : Float = 0.0 // 录音时原声大小
|
|
|
-
|
|
|
+
|
|
|
+ public var originSoundVolumn: Float = 1.0 // 无录音时原声大小
|
|
|
+ public var originSoundInRecordVolumn: Float = 0.0 // 录音时原声大小
|
|
|
+
|
|
|
public var data: [BFRecordItemModel]? {
|
|
|
didSet {
|
|
|
if data?.count ?? 0 > 0 {
|
|
@@ -69,25 +69,24 @@ public class BFRecordExport {
|
|
|
// 如果需要排序,则排视频的顺序;否则排音频的顺序
|
|
|
let needSort = false
|
|
|
if needSort {
|
|
|
-
|
|
|
- }else{
|
|
|
+ } else {
|
|
|
// 音频排序
|
|
|
itemModel.voiceStickers.sort { m1, m2 in
|
|
|
- m1.startTime < m2.startTime
|
|
|
+ m1.startCMTime.seconds < m2.startCMTime.seconds
|
|
|
}
|
|
|
// 字幕排序
|
|
|
itemModel.titleStickers.sort { model1, model2 in
|
|
|
model1.timelineIn < model2.timelineIn
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if itemModel.mediaType == .IMAGE {
|
|
|
// 图片素材
|
|
|
- if !synthesisAll && itemModel.voiceStickers.count == 0 {
|
|
|
+ if !synthesisAll, itemModel.voiceStickers.count == 0 {
|
|
|
// 图片无录音在保留模式里不合成
|
|
|
continue
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
var duration = itemModel.materialDuraion
|
|
|
if itemModel.voiceStickers.count == 0 {
|
|
|
// 图片无录音保持2s
|
|
@@ -99,7 +98,7 @@ public class BFRecordExport {
|
|
|
// voice.voiceType = VOICETYPT.None.rawValue
|
|
|
voice.volumeGain = 100
|
|
|
voiceList.append(voice)
|
|
|
- }else{
|
|
|
+ } else {
|
|
|
//
|
|
|
for mod in itemModel.voiceStickers {
|
|
|
let sticker = PQEditVisionTrackMaterialsModel()
|
|
@@ -114,14 +113,14 @@ public class BFRecordExport {
|
|
|
voiceList.append(sticker)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
let sticker = splitBaseMaterial(timelineIn: totalDur, model_in: 0, duration: duration)
|
|
|
sticker.originalData = itemModel.coverImg?.pngData()
|
|
|
sticker.volumeGain = 0
|
|
|
sticker.type = StickerType.IMAGE.rawValue
|
|
|
videoStickers.append(sticker)
|
|
|
BFLog(1, message: "image sticker - timIn:\(sticker.timelineIn), modIn:\(sticker.model_in), dur:\(duration)")
|
|
|
-
|
|
|
+
|
|
|
for titleS in itemModel.titleStickers {
|
|
|
// let leng = titleS.timelineOut - titleS.timelineIn
|
|
|
let newTitleSticker = PQEditSubTitleModel()
|
|
@@ -131,11 +130,11 @@ public class BFRecordExport {
|
|
|
newTitleSticker.timelineIn = totalDur + titleS.timelineIn
|
|
|
newTitleSticker.timelineOut = totalDur + titleS.timelineOut
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
totalDur += duration
|
|
|
continue
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 视频处理
|
|
|
if let localPath = itemModel.localPath {
|
|
|
if !FileManager.default.fileExists(atPath: localPath) {
|
|
@@ -143,9 +142,9 @@ public class BFRecordExport {
|
|
|
exportCompletion?(error as Error, nil)
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// voiceList.append(contentsOf: itemModel.voiceStickers)
|
|
|
-
|
|
|
+
|
|
|
if synthesisAll {
|
|
|
var subDur = 0.0
|
|
|
let drangs = itemModel.dealedDurationRanges
|
|
@@ -153,15 +152,15 @@ public class BFRecordExport {
|
|
|
let range = srange.range
|
|
|
let sticker = splitBaseMaterial(timelineIn: totalDur + subDur, model_in: range.start.seconds, duration: range.duration.seconds)
|
|
|
sticker.locationPath = localPath
|
|
|
- sticker.volumeGain = Float64(srange.isRecord ? originSoundInRecordVolumn*100 : originSoundVolumn*100)
|
|
|
+ sticker.volumeGain = Float64(srange.isRecord ? originSoundInRecordVolumn * 100 : originSoundVolumn * 100)
|
|
|
videoStickers.append(sticker)
|
|
|
subDur += range.duration.seconds
|
|
|
-
|
|
|
+
|
|
|
if srange.isRecord {
|
|
|
// 处理voice
|
|
|
if let mod = itemModel.voiceStickers.first(where: { m in
|
|
|
- m.startTime == range.start.seconds
|
|
|
- }){
|
|
|
+ m.startCMTime.seconds == range.start.seconds
|
|
|
+ }) {
|
|
|
let sticker = PQEditVisionTrackMaterialsModel()
|
|
|
sticker.model_in = 0
|
|
|
sticker.out = mod.endCMTime.seconds - mod.startCMTime.seconds
|
|
@@ -175,7 +174,7 @@ public class BFRecordExport {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
for titleS in itemModel.titleStickers {
|
|
|
let newTitleSticker = PQEditSubTitleModel()
|
|
|
titleStickers.append(newTitleSticker)
|
|
@@ -184,7 +183,7 @@ public class BFRecordExport {
|
|
|
newTitleSticker.timelineIn = totalDur + titleS.timelineIn
|
|
|
newTitleSticker.timelineOut = totalDur + titleS.timelineOut
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
totalDur += subDur
|
|
|
} else {
|
|
|
// 只保留录音部分
|
|
@@ -192,20 +191,20 @@ public class BFRecordExport {
|
|
|
var drangs = itemModel.dealedDurationRanges.filter { srange in
|
|
|
srange.isRecord == true
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if needSort {
|
|
|
drangs.sort { range1, range2 in
|
|
|
range1.index < range2.index
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
for (index, srange) in drangs.enumerated() {
|
|
|
let range = srange.range
|
|
|
let sticker = splitBaseMaterial(timelineIn: totalDur + subDur, model_in: range.start.seconds, duration: range.duration.seconds)
|
|
|
sticker.locationPath = localPath
|
|
|
- sticker.volumeGain = Float64(srange.isRecord ? originSoundInRecordVolumn*100 : originSoundVolumn*100)
|
|
|
+ sticker.volumeGain = Float64(srange.isRecord ? originSoundInRecordVolumn * 100 : originSoundVolumn * 100)
|
|
|
videoStickers.append(sticker)
|
|
|
-
|
|
|
+
|
|
|
let voiceSticker = itemModel.voiceStickers[index]
|
|
|
let voice = PQEditVisionTrackMaterialsModel()
|
|
|
voice.model_in = 0
|
|
@@ -217,10 +216,10 @@ public class BFRecordExport {
|
|
|
voice.locationPath = voiceSticker.wavFilePath
|
|
|
voice.volumeGain = 100 // Float64(model.volume)
|
|
|
voiceList.append(voice)
|
|
|
-
|
|
|
- let titleModels = itemModel.titleStickers.filter({ mod in
|
|
|
+
|
|
|
+ let titleModels = itemModel.titleStickers.filter { mod in
|
|
|
mod.audioFilePath == voiceSticker.wavFilePath
|
|
|
- })
|
|
|
+ }
|
|
|
for titleS in titleModels {
|
|
|
let newTitleSticker = PQEditSubTitleModel()
|
|
|
titleStickers.append(newTitleSticker)
|
|
@@ -231,13 +230,12 @@ public class BFRecordExport {
|
|
|
BFLog(1, message: "timein - \(newTitleSticker.timelineIn)")
|
|
|
}
|
|
|
subDur += range.duration.seconds
|
|
|
-
|
|
|
}
|
|
|
totalDur += subDur
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- beginExport(synthesisAll: synthesisAll, videoStickers: videoStickers, voiceList:voiceList, titleStickers:titleStickers)
|
|
|
+ beginExport(synthesisAll: synthesisAll, videoStickers: videoStickers, voiceList: voiceList, titleStickers: titleStickers)
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -319,17 +317,17 @@ public class BFRecordExport {
|
|
|
guard let totalDuration = data?.reduce(0.0, { partialResult, itemModell in
|
|
|
var modelDuraion = 0.0
|
|
|
if itemModell.mediaType == .IMAGE {
|
|
|
- if itemModell.voiceStickers.count == 0 && synthesisAll {
|
|
|
+ if itemModell.voiceStickers.count == 0, synthesisAll {
|
|
|
modelDuraion += 2
|
|
|
- }else {
|
|
|
+ } else {
|
|
|
modelDuraion = itemModell.materialDuraion
|
|
|
}
|
|
|
- }else if itemModell.mediaType == .VIDEO{
|
|
|
+ } else if itemModell.mediaType == .VIDEO {
|
|
|
modelDuraion = itemModell.dealedDurationRanges.reduce(0.0) { partialResult, srange in
|
|
|
// partialResult + (!synthesisAll && srange.isRecord) ?
|
|
|
if synthesisAll {
|
|
|
return partialResult + srange.range.duration.seconds
|
|
|
- }else {
|
|
|
+ } else {
|
|
|
return partialResult + (srange.isRecord ? srange.range.duration.seconds : 0)
|
|
|
}
|
|
|
}
|
|
@@ -340,31 +338,31 @@ public class BFRecordExport {
|
|
|
exportCompletion?(error as Error, nil)
|
|
|
return
|
|
|
}
|
|
|
+
|
|
|
// MARK: - 声音合成
|
|
|
+
|
|
|
// 有录音操作或者多个视频,就会进入合成步骤,否则就是一个没有处理的素材,直接导出就行了
|
|
|
if voiceCount > 0 || videoStickers.count > 1 {
|
|
|
let (audioMix, composition) = mergeAudio(videoStickers: videoStickers, audios: voiceList, synthesisAll: synthesisAll)
|
|
|
|
|
|
- var filters:[PQBaseFilter] = Array.init()
|
|
|
+ var filters: [PQBaseFilter] = Array()
|
|
|
for sticker in videoStickers {
|
|
|
if sticker.type == StickerType.IMAGE.rawValue {
|
|
|
filters.append(PQImageFilter(sticker: sticker))
|
|
|
- }else if sticker.type == StickerType.VIDEO.rawValue {
|
|
|
+ } else if sticker.type == StickerType.VIDEO.rawValue {
|
|
|
filters.append(PQMovieFilter(movieSticker: sticker))
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
- let outputSize:CGSize = CGSize(width: 1080, height: Int(1080 * CGFloat(UIScreen.main.bounds.size.height / UIScreen.main.bounds.size.width)))
|
|
|
+ let outputSize: CGSize = CGSize(width: 1080, height: Int(1080 * CGFloat(UIScreen.main.bounds.size.height / UIScreen.main.bounds.size.width)))
|
|
|
BFLog(message: "输出视频大小:\(outputSize)")
|
|
|
-
|
|
|
- //add by ak 有字幕数据 & 显示字幕开关打开 添加字幕filter
|
|
|
- if(titleStickers.count > 0 && ( titleStickers.first?.setting.subtitleIsShow ?? true)){
|
|
|
- filters.append(PQSubTitleFilter.init(st: titleStickers, inputSize: outputSize))
|
|
|
+
|
|
|
+ // add by ak 有字幕数据 & 显示字幕开关打开 添加字幕filter
|
|
|
+ if titleStickers.count > 0, titleStickers.first?.setting.subtitleIsShow ?? true {
|
|
|
+ filters.append(PQSubTitleFilter(st: titleStickers, inputSize: outputSize))
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
exporter = PQCompositionExporter(asset: composition, videoComposition: nil, audioMix: audioMix, filters: filters, animationTool: nil, exportURL: outPutMP4URL)
|
|
|
-
|
|
|
+
|
|
|
var orgeBitRate = Int(outputSize.width * outputSize.height * 3)
|
|
|
|
|
|
for stick in videoStickers {
|
|
@@ -378,12 +376,12 @@ public class BFRecordExport {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- BFLog(1, message: String(format: "导出设置的码率为:%.3f MB", Double(orgeBitRate)/1024.0/1024.0/8.0))
|
|
|
- let preSize = Double(orgeBitRate) * totalDuration / (1024*1024*8)
|
|
|
+ BFLog(1, message: String(format: "导出设置的码率为:%.3f MB", Double(orgeBitRate) / 1024.0 / 1024.0 / 8.0))
|
|
|
+ let preSize = Double(orgeBitRate) * totalDuration / (1024 * 1024 * 8)
|
|
|
let freeSize = PQBridgeObject.getPhoneDiskFreeSize()
|
|
|
if preSize + 100.0 > freeSize { // 存储完后磁盘剩余至少100M
|
|
|
- let error = NSError(domain: "err", code: ExportError.DiskNoSpace.rawValue, userInfo: ["msg":"需要\(Int(preSize))MB,可用\(Int(freeSize))MB"])
|
|
|
- self.exportCompletion?(error as Error, nil)
|
|
|
+ let error = NSError(domain: "err", code: ExportError.DiskNoSpace.rawValue, userInfo: ["msg": "需要\(Int(preSize))MB,可用\(Int(freeSize))MB"])
|
|
|
+ exportCompletion?(error as Error, nil)
|
|
|
return
|
|
|
}
|
|
|
let tempBeginExport = Date().timeIntervalSince1970
|
|
@@ -412,7 +410,7 @@ public class BFRecordExport {
|
|
|
self?.exportCompletion?(nil, url)
|
|
|
|
|
|
} else {
|
|
|
- let error = NSError(domain: "err", code: ExportError.ExportExcept.rawValue, userInfo: ["msg":"导出异常失败"])
|
|
|
+ let error = NSError(domain: "err", code: ExportError.ExportExcept.rawValue, userInfo: ["msg": "导出异常失败"])
|
|
|
self?.exportCompletion?(error as Error, nil)
|
|
|
cShowHUB(superView: nil, msg: "导出失败")
|
|
|
}
|