BFVideoCompositionManager.swift 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  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(movieURL: URL(fileURLWithPath: exportVideosDirectory+"test.mov"), size: UIScreen.main.bounds.size, fileType: "com.apple.quicktime-movie", outputSettings: [
  16. AVVideoCodecKey: AVVideoCodecH264,
  17. AVVideoWidthKey: 1080,
  18. AVVideoHeightKey: 1920,
  19. AVVideoCompressionPropertiesKey: [AVVideoAverageBitRateKey: 6*1024*1024 ]
  20. ] as [String : Any])
  21. func export(avsss:AVURLAsset){
  22. let start = Date()
  23. saveMovie = GPUImageMovie(asset: avsss)
  24. if let saveWrite = write{
  25. let filter = GPUImageFilter()
  26. saveMovie.addTarget(filter)
  27. // saveWrite.shouldPassthroughAudio = false
  28. saveWrite.encodingLiveVideo = false
  29. filter.addTarget(saveWrite)
  30. // saveMovie.audioEncodingTarget = saveWrite;
  31. saveMovie.enableSynchronizedEncoding(using: saveWrite)
  32. saveMovie.startProcessing()
  33. saveWrite.startRecording()
  34. DispatchQueue.global().asyncAfter(deadline: .now() + 2) { [weak self] in
  35. saveWrite.finishRecording()
  36. self?.saveMovie.endProcessing()
  37. }
  38. saveWrite.completionBlock = {[weak self] in
  39. BFLog(1, message: "导出完毕:\(Date().timeIntervalSince(start))")
  40. self?.mix(date: start, index: 0)
  41. }
  42. }
  43. }
  44. func mix(date:Date, index:Double){
  45. guard let asset = itemModels[currItemModelIndex].videoAsset else {
  46. return
  47. }
  48. if 2*index > asset.duration.seconds {
  49. return
  50. }
  51. let comp = AVMutableComposition()
  52. let audio = comp.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
  53. let video = comp.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
  54. // [AVURLAssetPreferPreciseDurationAndTimingKey:@YES]
  55. let bsset = AVURLAsset(url: URL(fileURLWithPath: exportVideosDirectory+"test.mov"))
  56. if let assetVideoTrack = bsset.tracks(withMediaType: .video).first {
  57. try? video?.insertTimeRange(CMTimeRange(start: CMTime(seconds: 2*index, preferredTimescale: 1000), end: CMTime(seconds: 2*index+2, preferredTimescale: 1000)), of: assetVideoTrack, at: .zero)
  58. }
  59. if let asset = itemModels[currItemModelIndex].videoAsset {
  60. if let assetAudioTrack = asset.tracks(withMediaType: .audio).first {
  61. try? audio?.insertTimeRange(CMTimeRange(start: .zero, end: bsset.duration), of: assetAudioTrack, at: .zero)
  62. }
  63. }
  64. // AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition
  65. // presetName:AVAssetExportPreset1280x720];
  66. // exporter.videoComposition = videoComp;
  67. try? FileManager.default.removeItem(at: URL(fileURLWithPath: exportVideosDirectory+"export.mov"))
  68. let export = AVAssetExportSession(asset: comp, presetName: AVAssetExportPreset1920x1080)
  69. export?.outputURL = URL(fileURLWithPath: exportVideosDirectory+"export.mov")
  70. export?.outputFileType = .mov
  71. export?.exportAsynchronously(completionHandler: {
  72. BFLog(1, message: "合成完毕:\(Date().timeIntervalSince(date))")
  73. PHPhotoLibrary.shared().performChanges {
  74. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: exportVideosDirectory+"export.mov"))
  75. } completionHandler: {[weak self] isFinished, err in
  76. BFLog(1, message: "save \(Date().timeIntervalSince(date)) , \(err)")
  77. }
  78. })
  79. }
  80. }