|  | @@ -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: "导出失败")
 | 
	
		
			
				|  |  |                  }
 |