ZLVideoManager.swift 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. //
  2. // ZLVideoManager.swift
  3. // ZLPhotoBrowser
  4. //
  5. // Created by long on 2020/9/23.
  6. //
  7. // Copyright (c) 2020 Long Zhang <495181165@qq.com>
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. import UIKit
  27. import AVFoundation
  28. public class ZLVideoManager: NSObject {
  29. class func getVideoExportFilePath() -> String {
  30. let format = ZLPhotoConfiguration.default().videoExportType.format
  31. return NSTemporaryDirectory().appendingFormat("/%@.%@", UUID().uuidString, format)
  32. }
  33. @objc public class func exportEditVideo(for asset: AVAsset, range: CMTimeRange, completion: @escaping ( (URL?, Error?) -> Void )) {
  34. let outputUrl = URL(fileURLWithPath: self.getVideoExportFilePath())
  35. guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetPassthrough) else {
  36. completion(nil, NSError(domain: "", code: -1000, userInfo: [NSLocalizedDescriptionKey: "video export failed"]))
  37. return
  38. }
  39. exportSession.outputURL = outputUrl
  40. exportSession.outputFileType = ZLPhotoConfiguration.default().videoExportType.avFileType
  41. exportSession.timeRange = range
  42. exportSession.exportAsynchronously(completionHandler: {
  43. let suc = exportSession.status == .completed
  44. if exportSession.status == .failed {
  45. zl_debugPrint("ZLPhotoBrowser: video export failed: \(exportSession.error?.localizedDescription ?? "")")
  46. }
  47. DispatchQueue.main.async {
  48. completion(suc ? outputUrl : nil, exportSession.error)
  49. }
  50. })
  51. }
  52. /// 没有针对不同分辨率视频做处理,仅用于处理相机拍照的视频
  53. @objc public class func mergeVideos(fileUrls: [URL], completion: @escaping ( (URL?, Error?) -> Void )) {
  54. let mixComposition = AVMutableComposition()
  55. let assets = fileUrls.map { AVURLAsset(url: $0) }
  56. do {
  57. // video track
  58. let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
  59. var instructions: [AVMutableVideoCompositionInstruction] = []
  60. var videoSize = CGSize.zero
  61. var start: CMTime = .zero
  62. for asset in assets {
  63. let videoTracks = asset.tracks(withMediaType: .video)
  64. if let assetTrack = videoTracks.first, compositionVideoTrack != nil {
  65. try compositionVideoTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: asset.duration), of: assetTrack, at: start)
  66. let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack!)
  67. layerInstruction.setTransform(assetTrack.preferredTransform, at: start)
  68. let instruction = AVMutableVideoCompositionInstruction()
  69. instruction.timeRange = CMTimeRangeMake(start: start, duration: asset.duration)
  70. instruction.layerInstructions = [layerInstruction]
  71. instructions.append(instruction)
  72. start = CMTimeAdd(start, asset.duration)
  73. if videoSize == .zero {
  74. videoSize = assetTrack.naturalSize
  75. let info = self.orientationFromTransform(assetTrack.preferredTransform)
  76. if info.isPortrait {
  77. swap(&videoSize.width, &videoSize.height)
  78. }
  79. }
  80. }
  81. }
  82. let mainComposition = AVMutableVideoComposition()
  83. mainComposition.instructions = instructions
  84. mainComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
  85. mainComposition.renderSize = videoSize
  86. // audio track
  87. let compositionAudioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
  88. start = .zero
  89. for asset in assets {
  90. let audioTracks = asset.tracks(withMediaType: .audio)
  91. if !audioTracks.isEmpty {
  92. try compositionAudioTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: asset.duration), of: audioTracks[0], at: start)
  93. start = CMTimeAdd(start, asset.duration)
  94. }
  95. }
  96. guard let exportSession = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPreset1280x720) else {
  97. completion(nil, NSError(domain: "", code: -1000, userInfo: [NSLocalizedDescriptionKey: "video merge failed"]))
  98. return
  99. }
  100. let outputUrl = URL(fileURLWithPath: ZLVideoManager.getVideoExportFilePath())
  101. exportSession.outputURL = outputUrl
  102. exportSession.shouldOptimizeForNetworkUse = true
  103. exportSession.outputFileType = ZLPhotoConfiguration.default().videoExportType.avFileType
  104. exportSession.videoComposition = mainComposition
  105. exportSession.exportAsynchronously(completionHandler: {
  106. let suc = exportSession.status == .completed
  107. if exportSession.status == .failed {
  108. zl_debugPrint("ZLPhotoBrowser: video merge failed: \(exportSession.error?.localizedDescription ?? "")")
  109. }
  110. DispatchQueue.main.async {
  111. completion(suc ? outputUrl : nil, exportSession.error)
  112. }
  113. })
  114. } catch {
  115. completion(nil, error)
  116. }
  117. }
  118. static func orientationFromTransform(_ transform: CGAffineTransform) -> (orientation: UIImage.Orientation, isPortrait: Bool) {
  119. var assetOrientation = UIImage.Orientation.up
  120. var isPortrait = false
  121. let tfA = transform.a
  122. let tfB = transform.b
  123. let tfC = transform.c
  124. let tfD = transform.d
  125. if tfA == 0 && tfB == 1.0 && tfC == -1.0 && tfD == 0 {
  126. assetOrientation = .right
  127. isPortrait = true
  128. } else if tfA == 0 && tfB == -1.0 && tfC == 1.0 && tfD == 0 {
  129. assetOrientation = .left
  130. isPortrait = true
  131. } else if tfA == 1.0 && tfB == 0 && tfC == 0 && tfD == 1.0 {
  132. assetOrientation = .up
  133. } else if tfA == -1.0 && tfB == 0 && tfC == 0 && tfD == -1.0 {
  134. assetOrientation = .down
  135. }
  136. return (assetOrientation, isPortrait)
  137. }
  138. }