|
@@ -8,48 +8,41 @@
|
|
|
import Foundation
|
|
|
import AVFoundation
|
|
|
import BFFramework
|
|
|
+import BFVideoEditKit
|
|
|
+import Photos
|
|
|
+import GPUImage
|
|
|
|
|
|
public class BFRecordExport {
|
|
|
public var progress : ((Float)->Void)?
|
|
|
- var count = 0
|
|
|
- var timerr : Timer?
|
|
|
-
|
|
|
- var asset:AVAsset?
|
|
|
- var voiceList:[PQVoiceModel]? {
|
|
|
+ public var exportCompletion : ((Error?, URL?)->Void)?
|
|
|
+ public var asset:AVURLAsset?
|
|
|
+ public var voiceList:[PQVoiceModel]? {
|
|
|
didSet {
|
|
|
- audioAsset = voiceList?.map({ model in
|
|
|
+ audioAssets = voiceList?.map({ model in
|
|
|
AVURLAsset(url: URL(fileURLWithPath: model.wavFilePath))
|
|
|
})
|
|
|
|
|
|
}
|
|
|
}
|
|
|
- var audioAsset : [AVURLAsset]?
|
|
|
+
|
|
|
+ var count = 0
|
|
|
+
|
|
|
+ var audioAssets : [AVURLAsset]?
|
|
|
|
|
|
var exporter : PQCompositionExporter?
|
|
|
var mStickers = [PQEditVisionTrackMaterialsModel]()
|
|
|
|
|
|
deinit {
|
|
|
- timerr?.invalidate()
|
|
|
- timerr = nil
|
|
|
}
|
|
|
public init(){}
|
|
|
|
|
|
|
|
|
//MARK: -
|
|
|
public func start(){
|
|
|
- guard timerr == nil else {
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- timerr = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCounter), userInfo: nil, repeats: true)
|
|
|
- RunLoop.current.add(timerr!, forMode: .common)
|
|
|
- timerr?.fire()
|
|
|
-
|
|
|
-
|
|
|
// 1,背景视频素材
|
|
|
let bgMovieInfo: PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel()
|
|
|
bgMovieInfo.type = StickerType.VIDEO.rawValue
|
|
|
- bgMovieInfo.locationPath = (asset as? AVURLAsset)?.url.absoluteString ?? ""
|
|
|
+ bgMovieInfo.locationPath = (asset?.url.absoluteString)?.removingPercentEncoding ?? ""
|
|
|
bgMovieInfo.timelineIn = 0
|
|
|
bgMovieInfo.timelineOut = CMTimeGetSeconds(asset?.duration ?? CMTime.zero)
|
|
|
bgMovieInfo.model_in = bgMovieInfo.timelineIn
|
|
@@ -57,12 +50,123 @@ public class BFRecordExport {
|
|
|
bgMovieInfo.canvasFillType = stickerContentMode.aspectFitStr.rawValue
|
|
|
mStickers.append(bgMovieInfo)
|
|
|
|
|
|
- beginExport(videoStickers: mStickers, audioAsset: [(self.asset as! AVURLAsset)])
|
|
|
+ beginExport(videoStickers: mStickers, audioAsset: self.audioAssets)
|
|
|
}
|
|
|
|
|
|
- func beginExport(videoStickers:[PQEditVisionTrackMaterialsModel], audioAsset: [AVURLAsset]) {
|
|
|
-
|
|
|
+ enum DispatchError: Error {
|
|
|
+ case timeout
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ func beginExport(videoStickers:[PQEditVisionTrackMaterialsModel], audioAsset: [AVURLAsset]?) {
|
|
|
+ // 输出视频地址
|
|
|
+ var outPutMP4Path = exportVideosDirectory
|
|
|
+ if !directoryIsExists(dicPath: outPutMP4Path) {
|
|
|
+ createDirectory(path: outPutMP4Path)
|
|
|
+ }
|
|
|
+ outPutMP4Path.append("video_\(String.qe.timestamp()).mp4")
|
|
|
+ let outPutMP4URL = URL(fileURLWithPath: outPutMP4Path)
|
|
|
+ BFLog(1, message: "导出视频地址 \(outPutMP4URL)")
|
|
|
|
|
|
+ // 处理导出
|
|
|
+ if (audioAsset?.count ?? 0 ) > 0 || videoStickers.count > 1 {
|
|
|
+ var audioUrl:URL?
|
|
|
+ if audioAsset?.count ?? 0 > 0 {
|
|
|
+ // 多音频合成
|
|
|
+ if let list = voiceList?.map({ model in
|
|
|
+ URL(fileURLWithPath: model.wavFilePath)
|
|
|
+ }){
|
|
|
+ if list.count == 1 {
|
|
|
+ audioUrl = list.first
|
|
|
+ }else {
|
|
|
+ let semaphore = DispatchSemaphore(value: 0)
|
|
|
+ PQPlayerViewModel.mergeAudios(urls: list) { completURL in
|
|
|
+ audioUrl = completURL
|
|
|
+ BFLog(1, message: "异步做同步")
|
|
|
+ semaphore.signal()
|
|
|
+ }
|
|
|
+ _ = semaphore.wait(timeout: .now() + 5)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 每次初始化的时候设置初始值 为 nIl
|
|
|
+ var audioMix: AVMutableAudioMix?
|
|
|
+ var composition: AVMutableComposition?
|
|
|
+
|
|
|
+ let filter = mStickers.map { sticker in
|
|
|
+ PQMovieFilter(movieSticker: sticker)
|
|
|
+ }
|
|
|
+ // 有
|
|
|
+ if let completURL = audioUrl {
|
|
|
+ let inputAsset = AVURLAsset(url: completURL, options: avAssertOptions)
|
|
|
+ (audioMix, composition) = PQVideoEditViewModel.setupAudioMix(originAsset: inputAsset, bgmData: nil, videoStickers: videoStickers)
|
|
|
+
|
|
|
+ if composition != nil {
|
|
|
+ exporter = PQCompositionExporter(asset: composition!, videoComposition: nil, audioMix: audioMix, filters: filter, animationTool: nil, exportURL: outPutMP4URL)
|
|
|
+ }else {
|
|
|
+ exporter = PQCompositionExporter(asset: inputAsset, videoComposition: nil, audioMix: nil, filters: filter, animationTool: nil, exportURL: outPutMP4URL)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let size = getVideoSize()
|
|
|
+ var orgeBitRate = Int(size.width * size.height * 3)
|
|
|
+
|
|
|
+ for stick in mStickers {
|
|
|
+ if stick.type == StickerType.VIDEO.rawValue {
|
|
|
+ let asset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + stick.locationPath), options: avAssertOptions)
|
|
|
+
|
|
|
+ let cbr = asset.tracks(withMediaType: .video).first?.estimatedDataRate
|
|
|
+ if Int(cbr ?? 0) > orgeBitRate {
|
|
|
+ orgeBitRate = Int(cbr ?? 0)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ BFLog(message: "导出设置的码率为:\(orgeBitRate)")
|
|
|
+ if exporter!.prepare(videoSize: size, videoAverageBitRate: orgeBitRate) {
|
|
|
+ exporter!.start(playeTimeRange: CMTimeRange(start: CMTime.zero, end: asset?.duration ?? CMTime.zero))
|
|
|
+ }
|
|
|
+ exporter?.progressClosure = { [weak self] _, _, progress in
|
|
|
+ // BFLog(message: "正片合成进度 \(progress * 100)%")
|
|
|
+ let useProgress = progress > 1 ? 1 : progress
|
|
|
+ if progress > 0 { // 更新进度
|
|
|
+ self?.progress?(useProgress)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ exporter?.completion = { [weak self] url in
|
|
|
+ // 输出视频时长
|
|
|
+ if let url = url {
|
|
|
+ let outSeconds = CMTimeGetSeconds(AVAsset(url: url).duration)
|
|
|
+
|
|
|
+ BFLog(1, message: "无水印的视频导出完成: \(String(describing: url)) 生成视频时长为:\(outSeconds)")
|
|
|
+ cShowHUB(superView: nil, msg: ( outSeconds == 0) ? "合成失败请重试。" : "合成成功")
|
|
|
+ self?.saveVideoToPhoto(url: url)
|
|
|
+ self?.exportCompletion?(nil, url)
|
|
|
+
|
|
|
+ }else{
|
|
|
+ let error = NSError(domain: "err", code: -1, userInfo: nil)
|
|
|
+ self?.exportCompletion?(error as Error, nil)
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // 导出完成后取消导出
|
|
|
+ self?.exporter?.cancel()
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 没有处理,直接copy原文件
|
|
|
+ self.exportCompletion?(nil, self.asset?.url)
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ func saveVideoToPhoto(url:URL){
|
|
|
+ PHPhotoLibrary.shared().performChanges {
|
|
|
+ PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)
|
|
|
+ } completionHandler: { isFinished, _ in
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ cShowHUB(superView: nil, msg: "保存成功")
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
func dealAsset(){
|
|
@@ -73,16 +177,6 @@ public class BFRecordExport {
|
|
|
// })
|
|
|
}
|
|
|
|
|
|
- @objc func updateCounter() {
|
|
|
- if self.count == 100 {
|
|
|
- timerr?.invalidate()
|
|
|
- timerr = nil
|
|
|
- return
|
|
|
- }
|
|
|
- self.count += 1
|
|
|
- self.progress?(Float(self.count)/100.0)
|
|
|
- }
|
|
|
-
|
|
|
func getVideoSize() -> CGSize{
|
|
|
var size = CGSize.zero
|
|
|
self.asset?.tracks.forEach({ track in
|