initial
This commit is contained in:
233
Pods/ZLPhotoBrowser/Sources/General/ZLVideoManager.swift
generated
Normal file
233
Pods/ZLPhotoBrowser/Sources/General/ZLVideoManager.swift
generated
Normal file
@@ -0,0 +1,233 @@
|
||||
//
|
||||
// ZLVideoManager.swift
|
||||
// ZLPhotoBrowser
|
||||
//
|
||||
// Created by long on 2020/9/23.
|
||||
//
|
||||
// Copyright (c) 2020 Long Zhang <495181165@qq.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
import Photos
|
||||
|
||||
public class ZLVideoManager: NSObject {
|
||||
class func getVideoExportFilePath(format: String? = nil) -> String {
|
||||
let format = format ?? ZLPhotoConfiguration.default().cameraConfiguration.videoExportType.format
|
||||
return NSTemporaryDirectory().appendingFormat("%@.%@", UUID().uuidString, format)
|
||||
}
|
||||
|
||||
class func exportEditVideo(for asset: AVAsset, range: CMTimeRange, complete: @escaping ((URL?, Error?) -> Void)) {
|
||||
let type: ZLVideoManager.ExportType = ZLPhotoConfiguration.default().cameraConfiguration.videoExportType == .mov ? .mov : .mp4
|
||||
exportVideo(for: asset, range: range, exportType: type, presetName: AVAssetExportPresetPassthrough) { url, error in
|
||||
if url != nil {
|
||||
complete(url!, error)
|
||||
} else {
|
||||
complete(nil, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 没有针对不同分辨率视频做处理,仅用于处理相机拍照的视频
|
||||
@objc public class func mergeVideos(fileUrls: [URL], completion: @escaping ((URL?, Error?) -> Void)) {
|
||||
let composition = AVMutableComposition()
|
||||
let assets = fileUrls.map { AVURLAsset(url: $0) }
|
||||
|
||||
var insertTime: CMTime = .zero
|
||||
var assetVideoTracks: [AVAssetTrack] = []
|
||||
|
||||
let compositionVideoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: CMPersistentTrackID())!
|
||||
let compositionAudioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: CMPersistentTrackID())!
|
||||
|
||||
for asset in assets {
|
||||
do {
|
||||
let timeRange = CMTimeRangeMake(start: .zero, duration: asset.duration)
|
||||
if let videoTrack = asset.tracks(withMediaType: .video).first {
|
||||
try compositionVideoTrack.insertTimeRange(
|
||||
timeRange,
|
||||
of: videoTrack,
|
||||
at: insertTime
|
||||
)
|
||||
|
||||
assetVideoTracks.append(videoTrack)
|
||||
}
|
||||
|
||||
if let audioTrack = asset.tracks(withMediaType: .audio).first {
|
||||
try compositionAudioTrack.insertTimeRange(
|
||||
timeRange,
|
||||
of: audioTrack,
|
||||
at: insertTime
|
||||
)
|
||||
}
|
||||
|
||||
insertTime = CMTimeAdd(insertTime, asset.duration)
|
||||
} catch {
|
||||
completion(nil, NSError.videoMergeError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
guard assetVideoTracks.count == assets.count else {
|
||||
completion(nil, NSError.videoMergeError)
|
||||
return
|
||||
}
|
||||
|
||||
let renderSize = getNaturalSize(videoTrack: assetVideoTracks[0])
|
||||
|
||||
let videoComposition = AVMutableVideoComposition()
|
||||
videoComposition.instructions = getInstructions(compositionTrack: compositionVideoTrack, assetVideoTracks: assetVideoTracks, assets: assets)
|
||||
videoComposition.frameDuration = assetVideoTracks[0].minFrameDuration
|
||||
videoComposition.renderSize = renderSize
|
||||
videoComposition.renderScale = 1
|
||||
|
||||
guard let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPreset1280x720) else {
|
||||
completion(nil, NSError.videoMergeError)
|
||||
return
|
||||
}
|
||||
|
||||
let outputUrl = URL(fileURLWithPath: ZLVideoManager.getVideoExportFilePath())
|
||||
exportSession.outputURL = outputUrl
|
||||
exportSession.shouldOptimizeForNetworkUse = true
|
||||
exportSession.outputFileType = ZLPhotoConfiguration.default().cameraConfiguration.videoExportType.avFileType
|
||||
exportSession.videoComposition = videoComposition
|
||||
exportSession.exportAsynchronously(completionHandler: {
|
||||
let suc = exportSession.status == .completed
|
||||
if exportSession.status == .failed {
|
||||
zl_debugPrint("ZLPhotoBrowser: video merge failed: \(exportSession.error?.localizedDescription ?? "")")
|
||||
}
|
||||
ZLMainAsync {
|
||||
completion(suc ? outputUrl : nil, exportSession.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private static func getNaturalSize(videoTrack: AVAssetTrack) -> CGSize {
|
||||
var size = videoTrack.naturalSize
|
||||
if isPortraitVideoTrack(videoTrack) {
|
||||
swap(&size.width, &size.height)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
private static func getInstructions(
|
||||
compositionTrack: AVMutableCompositionTrack,
|
||||
assetVideoTracks: [AVAssetTrack],
|
||||
assets: [AVURLAsset]
|
||||
) -> [AVMutableVideoCompositionInstruction] {
|
||||
var instructions: [AVMutableVideoCompositionInstruction] = []
|
||||
|
||||
var start: CMTime = .zero
|
||||
for (index, videoTrack) in assetVideoTracks.enumerated() {
|
||||
let asset = assets[index]
|
||||
let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack)
|
||||
layerInstruction.setTransform(videoTrack.preferredTransform, at: .zero)
|
||||
|
||||
let instruction = AVMutableVideoCompositionInstruction()
|
||||
instruction.timeRange = CMTimeRangeMake(start: start, duration: asset.duration)
|
||||
instruction.layerInstructions = [layerInstruction]
|
||||
instructions.append(instruction)
|
||||
|
||||
start = CMTimeAdd(start, asset.duration)
|
||||
}
|
||||
|
||||
return instructions
|
||||
}
|
||||
|
||||
private static func isPortraitVideoTrack(_ track: AVAssetTrack) -> Bool {
|
||||
let transform = track.preferredTransform
|
||||
let tfA = transform.a
|
||||
let tfB = transform.b
|
||||
let tfC = transform.c
|
||||
let tfD = transform.d
|
||||
|
||||
if (tfA == 0 && tfB == 1 && tfC == -1 && tfD == 0) ||
|
||||
(tfA == 0 && tfB == 1 && tfC == 1 && tfD == 0) ||
|
||||
(tfA == 0 && tfB == -1 && tfC == 1 && tfD == 0) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: export methods
|
||||
|
||||
public extension ZLVideoManager {
|
||||
@objc class func exportVideo(for asset: PHAsset, exportType: ZLVideoManager.ExportType = .mov, presetName: String = AVAssetExportPresetMediumQuality, complete: @escaping ((URL?, Error?) -> Void)) {
|
||||
guard asset.mediaType == .video else {
|
||||
complete(nil, NSError.videoExportTypeError)
|
||||
return
|
||||
}
|
||||
|
||||
_ = ZLPhotoManager.fetchAVAsset(forVideo: asset) { avAsset, _ in
|
||||
if let set = avAsset {
|
||||
self.exportVideo(for: set, exportType: exportType, presetName: presetName, complete: complete)
|
||||
} else {
|
||||
complete(nil, NSError.videoExportError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc class func exportVideo(for asset: AVAsset, range: CMTimeRange = CMTimeRange(start: .zero, duration: .positiveInfinity), exportType: ZLVideoManager.ExportType = .mov, presetName: String = AVAssetExportPresetMediumQuality, complete: @escaping ((URL?, Error?) -> Void)) {
|
||||
let outputUrl = URL(fileURLWithPath: getVideoExportFilePath(format: exportType.format))
|
||||
guard let exportSession = AVAssetExportSession(asset: asset, presetName: presetName) else {
|
||||
complete(nil, NSError.videoExportError)
|
||||
return
|
||||
}
|
||||
exportSession.outputURL = outputUrl
|
||||
exportSession.outputFileType = exportType.avFileType
|
||||
exportSession.timeRange = range
|
||||
|
||||
exportSession.exportAsynchronously(completionHandler: {
|
||||
let suc = exportSession.status == .completed
|
||||
if exportSession.status == .failed {
|
||||
zl_debugPrint("ZLPhotoBrowser: video export failed: \(exportSession.error?.localizedDescription ?? "")")
|
||||
}
|
||||
ZLMainAsync {
|
||||
complete(suc ? outputUrl : nil, exportSession.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public extension ZLVideoManager {
|
||||
@objc enum ExportType: Int {
|
||||
var format: String {
|
||||
switch self {
|
||||
case .mov:
|
||||
return "mov"
|
||||
case .mp4:
|
||||
return "mp4"
|
||||
}
|
||||
}
|
||||
|
||||
var avFileType: AVFileType {
|
||||
switch self {
|
||||
case .mov:
|
||||
return .mov
|
||||
case .mp4:
|
||||
return .mp4
|
||||
}
|
||||
}
|
||||
|
||||
case mov
|
||||
case mp4
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user