BFRecordItemModel.swift 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. //
  2. // BFRecordItemModel.swift
  3. // BFRecordScreenKit
  4. //
  5. // Created by 胡志强 on 2021/12/6.
  6. //
  7. import BFCommonKit
  8. import BFMediaKit
  9. import Foundation
  10. import Photos
  11. struct SplitRecordRange {
  12. var isRecord: Bool = false
  13. var range: CMTimeRange
  14. var index: Int
  15. }
  16. public class BFRecordItemModel: NSObject {
  17. // var baseMaterial : AVURLAsset?
  18. var localPath: String?
  19. var materialDuraion: Double = 0.0
  20. var fetchCoverImg: ((UIImage) -> Void)?
  21. var fetchAVUrlAsset: ((AVURLAsset) -> Void)?
  22. var fetchPlayItem: ((AVPlayerItem) -> Void)?
  23. var dealedDurationRanges = [SplitRecordRange]() // 录音切割的时间区间,合成导出时计算
  24. public var voiceStickers = [PQVoiceModel]() //
  25. public var videoStickers = [PQEditVisionTrackMaterialsModel]() // 合成导出时计算
  26. public var imageStickers = [PQEditVisionTrackMaterialsModel]() //
  27. public var titleStickers = [PQEditSubTitleModel]() // 字幕贴纸
  28. var events = [WithDrawModel]() // 行为记录,方便撤销
  29. public var coverImg: UIImage? // 封面图
  30. public var thumbImgs = [UIImage]() // 缩略图集合
  31. public var playItem: AVPlayerItem? // 视频playerItem
  32. public var videoAsset: AVURLAsset? // 视频Asset
  33. public var mediaType: StickerType? // 素材类型
  34. public var progress: Double = 0 // 更新进度
  35. public var index = 0 // 素材index
  36. public var width = 0 // 素材宽
  37. public var height = 0 // 素材高
  38. public var videoDegress : UInt = 0 // 视频拍摄角度 90,270为横屏,180,0为竖屏
  39. func initOriginData(phasset: PHAsset) {
  40. width = phasset.pixelWidth
  41. height = phasset.pixelHeight
  42. if phasset.mediaType == .image {
  43. mediaType = .IMAGE
  44. localPath = "image"
  45. } else if phasset.mediaType == .video {
  46. mediaType = .VIDEO
  47. fetchPlayItem(phasset)
  48. }
  49. fetchCoverImage(phasset)
  50. fetchAVUrlAsset(phasset)
  51. }
  52. func fetchCoverImage(_ phasset: PHAsset) {
  53. let option = PHImageRequestOptions()
  54. option.isNetworkAccessAllowed = true // 允许下载iCloud的图片
  55. option.resizeMode = .fast
  56. option.deliveryMode = .highQualityFormat
  57. PHImageManager.default().requestImage(for: phasset,
  58. targetSize: CGSize(width: width, height: height),
  59. contentMode: .aspectFit,
  60. options: option) { [weak self] image, _ in
  61. // 设置首帧/封面
  62. if image != nil {
  63. self?.coverImg = image
  64. self?.fetchCoverImg?(image!)
  65. }
  66. }
  67. }
  68. func fetchPlayItem(_ phasset: PHAsset) {
  69. let options = PHVideoRequestOptions()
  70. options.isNetworkAccessAllowed = true
  71. options.deliveryMode = .automatic
  72. PHImageManager.default().requestPlayerItem(forVideo: phasset, options: options, resultHandler: { [weak self] playerItem, _ in
  73. guard let item = playerItem else {
  74. cShowHUB(superView: nil, msg: "视频获取失败:\(self?.index ?? 0)")
  75. return
  76. }
  77. self?.playItem = item
  78. self?.fetchPlayItem?(item)
  79. })
  80. }
  81. func fetchAVUrlAsset(_ phasset: PHAsset) {
  82. let options = PHVideoRequestOptions()
  83. options.isNetworkAccessAllowed = true
  84. options.deliveryMode = .automatic
  85. PHCachingImageManager().requestAVAsset(forVideo: phasset, options: options, resultHandler: { [weak self] (asset: AVAsset?, _: AVAudioMix?, _) in
  86. if let videoAsset = asset as? AVURLAsset {
  87. self?.materialDuraion = videoAsset.duration.seconds
  88. self?.localPath = (videoAsset.url.absoluteString.removingPercentEncoding)?.replacingOccurrences(of: "file://", with: "")
  89. self?.videoAsset = videoAsset
  90. self?.fetchAVUrlAsset?(videoAsset)
  91. }
  92. })
  93. }
  94. func generationTimeRanges(needSort _: Bool = false) {
  95. dealedDurationRanges.removeAll()
  96. var start: Double = 0
  97. var list: [PQVoiceModel]
  98. list = voiceStickers.sorted { model1, model2 in
  99. model1.startTime < model2.startTime
  100. }
  101. for model in list {
  102. if model.startTime > start {
  103. //
  104. let range = CMTimeRange(start: CMTime(seconds: start, preferredTimescale: 1000), duration: CMTime(seconds: model.startTime - start, preferredTimescale: 1000))
  105. dealedDurationRanges.append(SplitRecordRange(isRecord: false, range: range, index: -1))
  106. }
  107. let ind = voiceStickers.firstIndex(of: model)
  108. let range = CMTimeRange(start: CMTime(seconds: model.startTime, preferredTimescale: 1000), end: CMTime(seconds: model.endTime, preferredTimescale: 1000))
  109. dealedDurationRanges.append(SplitRecordRange(isRecord: true, range: range, index: ind ?? -1))
  110. start = model.endTime
  111. }
  112. if start < materialDuraion {
  113. let range = CMTimeRange(start: CMTime(seconds: start, preferredTimescale: 1000), end: CMTime(seconds: materialDuraion, preferredTimescale: 1000))
  114. dealedDurationRanges.append(SplitRecordRange(isRecord: false, range: range, index: -1))
  115. }
  116. }
  117. /// 视频分解成帧
  118. /// - parameter frames : 需要取的帧数
  119. /// - parameter firstImagesCount : 获取首先N张连续视频帧后先返回给调用方使用作为缓冲
  120. /// - parameter splitCompleteClosure : 回调
  121. func splitVideoFileUrlFps(frames: Int, firstImagesCount:Int = 0, splitCompleteClosure: @escaping ((Bool, [UIImage]) -> Void)) {
  122. guard let urlAsset = videoAsset, urlAsset.duration.seconds > 0 else {
  123. return
  124. }
  125. var splitImages = [UIImage]()
  126. var times = [NSValue]()
  127. // let urlAsset = AVURLAsset(url: URL(fileURLWithPath: localPath))
  128. let start = 0
  129. // let end = Int(urlAsset.duration.seconds * Float64(fps))
  130. let fps = Double(frames) / urlAsset.duration.seconds
  131. for i in start..<frames {
  132. let timeValue = NSValue(time: CMTimeMake(value: Int64(i * 1000), timescale: Int32(fps * 1000)))
  133. times.append(timeValue)
  134. }
  135. let imgGenerator = AVAssetImageGenerator(asset: urlAsset)
  136. imgGenerator.requestedTimeToleranceBefore = CMTime.zero
  137. imgGenerator.requestedTimeToleranceAfter = CMTime.zero
  138. imgGenerator.appliesPreferredTrackTransform = true
  139. let timesCount = times.count
  140. var cocu = 0
  141. // 获取每一帧的图片
  142. imgGenerator.generateCGImagesAsynchronously(forTimes: times) { _, image, _, result, _ in
  143. cocu += 1
  144. switch result {
  145. case AVAssetImageGenerator.Result.cancelled:
  146. BFLog(1, message: "splitVideo: cancel")
  147. case AVAssetImageGenerator.Result.failed:
  148. BFLog(1, message: "splitVideo: failed")
  149. case AVAssetImageGenerator.Result.succeeded:
  150. let framImg = UIImage(cgImage: image!)
  151. splitImages.append(framImg)
  152. // BFLog(1, message: "aaa: \(requestedTime.seconds) - \(actualTime.seconds)")
  153. @unknown default:
  154. break
  155. }
  156. if cocu == firstImagesCount {
  157. splitCompleteClosure(false, splitImages)
  158. }
  159. if cocu == timesCount { // 最后一帧时 回调赋值
  160. splitCompleteClosure(true, splitImages)
  161. BFLog(1, message: "splitVideo: complete")
  162. }
  163. }
  164. }
  165. /// 视频分解成帧
  166. /// - parameter fileUrl : 视频地址
  167. /// - parameter fps : 自定义帧数 每秒内取的帧数
  168. /// - parameter splitCompleteClosure : 回调
  169. func getThumbImageAtTime(urlAsset: AVURLAsset, time: CMTime) -> UIImage? {
  170. let imgGenerator = AVAssetImageGenerator(asset: urlAsset)
  171. imgGenerator.requestedTimeToleranceBefore = CMTime.zero
  172. imgGenerator.requestedTimeToleranceAfter = CMTime.zero
  173. var cgImg = try? imgGenerator.copyCGImage(at: time, actualTime: nil)
  174. if cgImg == nil {
  175. imgGenerator.requestedTimeToleranceBefore = CMTime.positiveInfinity
  176. imgGenerator.requestedTimeToleranceAfter = CMTime.positiveInfinity
  177. cgImg = try? imgGenerator.copyCGImage(at: time, actualTime: nil)
  178. }
  179. return cgImg == nil ? nil : UIImage(cgImage: cgImg!)
  180. }
  181. }