BFVideoCompositionManager.swift 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. //
  2. // BFVideoCompositionManager.swift
  3. // BFRecordScreenKit
  4. //
  5. // Created by 胡志强 on 2021/12/20.
  6. //
  7. import Foundation
  8. import GPUImage
  9. import BFCommonKit
  10. import Photos
  11. class BFVideoCompositionManager {
  12. var saveMovie = GPUImageMovie()
  13. var itemModels = [BFRecordItemModel]()
  14. var currItemModelIndex = 0
  15. var write : GPUImageMovieWriter?
  16. func getVideoSize(asset: AVURLAsset) -> CGSize {
  17. var size = CGSize.zero
  18. asset.tracks.forEach { track in
  19. if track.mediaType == .video {
  20. let realSize = __CGSizeApplyAffineTransform(track.naturalSize, track.preferredTransform)
  21. size = CGSize(width: ceil(abs(realSize.width)), height: ceil(abs(realSize.height)))
  22. }
  23. }
  24. return size
  25. }
  26. func export(avsss:AVURLAsset){
  27. let start = Date()
  28. saveMovie = GPUImageMovie(asset: avsss)
  29. let size = getVideoSize(asset: avsss)
  30. write = GPUImageMovieWriter(movieURL: URL(fileURLWithPath: exportVideosDirectory+"test.mov"), size: CGSize(width: 1080, height: 1920), fileType: "com.apple.quicktime-movie", outputSettings: [
  31. AVVideoCodecKey: AVVideoCodecH264,
  32. AVVideoWidthKey: 1080.0,
  33. AVVideoHeightKey: size.height * 1080.0 / size.width ,
  34. AVVideoCompressionPropertiesKey: [AVVideoAverageBitRateKey: 6*1024*1024 ]
  35. ] as [String : Any])
  36. if let saveWrite = write{
  37. let filter = GPUImageFilter()
  38. saveMovie.addTarget(filter)
  39. // saveWrite.shouldPassthroughAudio = false
  40. saveWrite.encodingLiveVideo = false
  41. filter.addTarget(saveWrite)
  42. // saveMovie.audioEncodingTarget = saveWrite;
  43. saveMovie.enableSynchronizedEncoding(using: saveWrite)
  44. saveMovie.startProcessing()
  45. saveWrite.startRecording()
  46. // DispatchQueue.global().asyncAfter(deadline: .now() + 2) { [weak self] in
  47. // saveWrite.finishRecording()
  48. // self?.saveMovie.endProcessing()
  49. // }
  50. saveWrite.completionBlock = {[weak self] in
  51. BFLog(1, message: "导出完毕:\(Date().timeIntervalSince(start))")
  52. self?.mix(date: start, index: 0)
  53. }
  54. }
  55. }
  56. func mix(date:Date, index:Double){
  57. guard let asset = itemModels[currItemModelIndex].videoAsset else {
  58. return
  59. }
  60. if 2*index > asset.duration.seconds {
  61. return
  62. }
  63. let comp = AVMutableComposition()
  64. let audio = comp.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
  65. let video = comp.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
  66. // [AVURLAssetPreferPreciseDurationAndTimingKey:@YES]
  67. let bsset = AVURLAsset(url: URL(fileURLWithPath: exportVideosDirectory+"test.mov"))
  68. if let assetVideoTrack = bsset.tracks(withMediaType: .video).first {
  69. try? video?.insertTimeRange(CMTimeRange(start: CMTime(seconds: 2*index, preferredTimescale: 1000), end: CMTime(seconds: 2*index+2, preferredTimescale: 1000)), of: assetVideoTrack, at: .zero)
  70. }
  71. if let asset = itemModels[currItemModelIndex].videoAsset {
  72. if let assetAudioTrack = asset.tracks(withMediaType: .audio).first {
  73. try? audio?.insertTimeRange(CMTimeRange(start: .zero, end: bsset.duration), of: assetAudioTrack, at: .zero)
  74. }
  75. }
  76. // AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition
  77. // presetName:AVAssetExportPreset1280x720];
  78. // exporter.videoComposition = videoComp;
  79. try? FileManager.default.removeItem(at: URL(fileURLWithPath: exportVideosDirectory+"export.mov"))
  80. let export = AVAssetExportSession(asset: comp, presetName: AVAssetExportPreset1920x1080)
  81. export?.outputURL = URL(fileURLWithPath: exportVideosDirectory+"export.mov")
  82. export?.outputFileType = .mov
  83. export?.exportAsynchronously(completionHandler: {
  84. BFLog(1, message: "合成完毕:\(Date().timeIntervalSince(date))")
  85. PHPhotoLibrary.shared().performChanges {
  86. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: exportVideosDirectory+"export.mov"))
  87. } completionHandler: {[weak self] isFinished, err in
  88. BFLog(1, message: "save \(Date().timeIntervalSince(date)) , \(err)")
  89. }
  90. })
  91. }
  92. }