BFRecordItemModel.swift 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  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 fetchCoverImgCallBack: ((UIImage) -> Void)?
  21. var fetchAVUrlAssetCallBack: (() -> Void)?
  22. var fetchPlayItemCallBack: ((BFRecordItemModel?) -> 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. materialDuraion = phasset.duration
  43. fetchCoverImage(phasset)
  44. fetchAVUrlAsset(phasset)
  45. if phasset.mediaType == .image {
  46. mediaType = .IMAGE
  47. localPath = "image"
  48. } else if phasset.mediaType == .video {
  49. mediaType = .VIDEO
  50. fetchPlayItem(phasset)
  51. }
  52. }
  53. func fetchCoverImage(_ phasset: PHAsset) {
  54. let option = PHImageRequestOptions()
  55. option.isNetworkAccessAllowed = true // 允许下载iCloud的图片
  56. option.resizeMode = .fast
  57. option.deliveryMode = .highQualityFormat
  58. PHImageManager.default().requestImage(for: phasset,
  59. targetSize: CGSize(width: width, height: height),
  60. contentMode: .aspectFit,
  61. options: option) { [weak self] image, _ in
  62. // 设置首帧/封面
  63. if image != nil {
  64. self?.coverImg = image
  65. self?.fetchCoverImgCallBack?(image!)
  66. }
  67. }
  68. }
  69. func fetchPlayItem(_ phasset: PHAsset) {
  70. PQPHAssetVideoParaseUtil.parasToAVPlayerItem(phAsset: phasset) { [weak self] playerItem, _, _ in
  71. guard let item = playerItem else {
  72. self?.fetchPlayItemCallBack?(self)
  73. return
  74. }
  75. self?.playItem = item
  76. self?.fetchPlayItemCallBack?(self)
  77. }
  78. }
  79. public func fetchAVUrlAsset(_ phasset: PHAsset) {
  80. PQPHAssetVideoParaseUtil.parasToAVAsset(phAsset: phasset) {[weak self] asset, size, _, _ in
  81. guard let sself = self else {
  82. return
  83. }
  84. if let videoAsset = (asset as? AVURLAsset) {
  85. sself.localPath = (videoAsset.url.absoluteString.removingPercentEncoding)?.replacingOccurrences(of: "file://", with: "")
  86. sself.videoAsset = videoAsset
  87. sself.fetchAVUrlAssetCallBack?()
  88. }
  89. }
  90. }
  91. func generationTimeRanges(needSort _: Bool = false) {
  92. dealedDurationRanges.removeAll()
  93. var start: Double = 0
  94. var list: [PQVoiceModel]
  95. list = voiceStickers.sorted { model1, model2 in
  96. model1.startCMTime.seconds < model2.endCMTime.seconds
  97. }
  98. for model in list {
  99. if model.startCMTime.seconds > start {
  100. let range = CMTimeRange(start: CMTime(seconds: start, preferredTimescale: 1000), duration: CMTime(seconds: model.startCMTime.seconds - start, preferredTimescale: 1000))
  101. dealedDurationRanges.append(SplitRecordRange(isRecord: false, range: range, index: -1))
  102. }
  103. let ind = voiceStickers.firstIndex(of: model)
  104. let range = CMTimeRange(start: model.startCMTime, end: model.endCMTime)
  105. dealedDurationRanges.append(SplitRecordRange(isRecord: true, range: range, index: ind ?? -1))
  106. start = model.endCMTime.seconds
  107. }
  108. if start < materialDuraion {
  109. let range = CMTimeRange(start: CMTime(seconds: start, preferredTimescale: 1000), end: CMTime(seconds: materialDuraion, preferredTimescale: 1000))
  110. dealedDurationRanges.append(SplitRecordRange(isRecord: false, range: range, index: -1))
  111. }
  112. }
  113. /// 视频分解成帧
  114. /// - parameter frames : 需要取的帧数
  115. /// - parameter firstImagesCount : 获取首先N张连续视频帧后先返回给调用方使用作为缓冲
  116. /// - parameter splitCompleteClosure : 回调
  117. func splitVideoFileUrlFps(frames: Int, firstImagesCount:Int = 0, maxSize:CGSize = CGSize(width: 200, height: 200), splitCompleteClosure: @escaping ((Bool, [UIImage]) -> Void)) {
  118. guard let urlAsset = videoAsset, urlAsset.duration.seconds > 0 else {
  119. return
  120. }
  121. var splitImages = [UIImage]()
  122. var times = [NSValue]()
  123. // let urlAsset = AVURLAsset(url: URL(fileURLWithPath: localPath))
  124. let start = 0
  125. // let end = Int(urlAsset.duration.seconds * Float64(fps))
  126. let fps = Double(frames) / urlAsset.duration.seconds
  127. for i in start..<frames {
  128. let timeValue = NSValue(time: CMTimeMake(value: Int64(i * 1000), timescale: Int32(fps * 1000)))
  129. times.append(timeValue)
  130. }
  131. let imgGenerator = AVAssetImageGenerator(asset: urlAsset)
  132. imgGenerator.requestedTimeToleranceBefore = CMTime.zero
  133. imgGenerator.requestedTimeToleranceAfter = CMTime.zero
  134. imgGenerator.appliesPreferredTrackTransform = true
  135. imgGenerator.maximumSize = CGSize(width: 200, height: 200)
  136. let timesCount = times.count
  137. var cocu = 0
  138. // 获取每一帧的图片
  139. imgGenerator.generateCGImagesAsynchronously(forTimes: times) { _, image, _, result, _ in
  140. cocu += 1
  141. switch result {
  142. case AVAssetImageGenerator.Result.cancelled:
  143. BFLog(1, message: "splitVideo: cancel")
  144. case AVAssetImageGenerator.Result.failed:
  145. BFLog(1, message: "splitVideo: failed")
  146. case AVAssetImageGenerator.Result.succeeded:
  147. let framImg = UIImage(cgImage: image!)
  148. splitImages.append(framImg)
  149. // BFLog(1, message: "aaa: \(requestedTime.seconds) - \(actualTime.seconds)")
  150. @unknown default:
  151. break
  152. }
  153. if cocu == firstImagesCount {
  154. splitCompleteClosure(false, splitImages)
  155. }
  156. if cocu == timesCount { // 最后一帧时 回调赋值
  157. splitCompleteClosure(true, splitImages)
  158. BFLog(1, message: "splitVideo: complete")
  159. }
  160. }
  161. }
  162. /// 视频分解成帧
  163. /// - parameter fileUrl : 视频地址
  164. /// - parameter fps : 自定义帧数 每秒内取的帧数
  165. /// - parameter splitCompleteClosure : 回调
  166. func getThumbImageAtTime(urlAsset: AVURLAsset, time: CMTime) -> UIImage? {
  167. let imgGenerator = AVAssetImageGenerator(asset: urlAsset)
  168. imgGenerator.requestedTimeToleranceBefore = CMTime.zero
  169. imgGenerator.requestedTimeToleranceAfter = CMTime.zero
  170. var cgImg = try? imgGenerator.copyCGImage(at: time, actualTime: nil)
  171. if cgImg == nil {
  172. imgGenerator.requestedTimeToleranceBefore = CMTime.positiveInfinity
  173. imgGenerator.requestedTimeToleranceAfter = CMTime.positiveInfinity
  174. cgImg = try? imgGenerator.copyCGImage(at: time, actualTime: nil)
  175. }
  176. return cgImg == nil ? nil : UIImage(cgImage: cgImg!)
  177. }
  178. }