1、获取视频时长(秒数)
@objc func getVideoLength ( ) {
let paths = NSSearchPathForDirectoriesInDomains ( . documentDirectory, . userDomainMask, true )
let documentsDirectory = paths[ 0 ] as String
let videoPath = documentsDirectory + "/" + "1619172935.mp4"
if ! FileManager . default . fileExists ( atPath: videoPath) {
print ( "文件不存在,请先拍照,再修改视频地址" )
return
}
let avUrlAsset = AVURLAsset . init ( url: URL ( fileURLWithPath: videoPath) )
let cmtime = avUrlAsset. duration
let second = Int ( cmtime. seconds)
print ( "视频秒数 == \( second ) " )
}
2、获取视频文件大小
@objc func getVideoSize ( ) {
let paths = NSSearchPathForDirectoriesInDomains ( . documentDirectory, . userDomainMask, true )
let documentsDirectory = paths[ 0 ] as String
let videoPath = documentsDirectory + "/" + "1619172935.mp4"
if ! FileManager . default . fileExists ( atPath: videoPath) {
print ( "文件不存在,请先拍照,再修改视频地址" )
return
}
let fileManager = FileManager . default
if fileManager. fileExists ( atPath: videoPath) {
let fileDic = try ! fileManager. attributesOfItem ( atPath: videoPath)
let size = fileDic[ FileAttributeKey ( rawValue: "NSFileSize" ) ] as ? Int ?? 0
print ( " \( size ) B" )
print ( " \( size/ 1024 ) KB" )
let sizeM = String ( format: "%.2f" , Float ( size) / 1024 / 1024 )
print ( sizeM + "M" )
} else {
print ( "文件不存在" )
}
}
3、获取指定时间帧图片
@objc func getImage ( ) {
let paths = NSSearchPathForDirectoriesInDomains ( . documentDirectory, . userDomainMask, true )
let documentsDirectory = paths[ 0 ] as String
let videoPath = documentsDirectory + "/" + "1619172935.mp4"
if ! FileManager . default . fileExists ( atPath: videoPath) {
print ( "文件不存在,请先拍照,再修改视频地址" )
return
}
let cmtime = CMTimeMake ( value: 1 , timescale: 10 )
let width = 300
DispatchQueue . global ( ) . async {
let asset = AVAsset . init ( url: URL ( fileURLWithPath: videoPath) )
let imageGenerator = AVAssetImageGenerator . init ( asset: asset)
imageGenerator. maximumSize = CGSize ( width: width, height: 0 )
imageGenerator. appliesPreferredTrackTransform = true
let imageRef = try ! imageGenerator. copyCGImage ( at: cmtime, actualTime: nil )
let image = UIImage . init ( cgImage: imageRef)
DispatchQueue . main. async {
self . saveImage ( image: image)
}
}
}
4、多视频合成
@objc func starMerge ( ) {
let videoPaths = [
"1619163716.mp4" ,
"1619163734.mp4" ,
"1619163741.mp4"
]
for i in 0 ..< videoPaths. count {
if ! FileManager . default . fileExists ( atPath: videoPaths[ i] ) {
print ( "文件不存在,请先拍照,再修改视频地址" )
return
}
}
let outputPath = self . getNewPath ( videoTyle: AVFileType . mp4)
self . mergeVideo ( videoPaths: videoPaths, outputPath: outputPath) { ( success) in
if success {
print ( "多视频合成 成功" )
} else {
print ( "多视频合成 失败" )
}
}
}
func mergeVideo ( videoPaths: [ String ] , outputPath: String , completeHandler: @escaping ( Bool ) -> ( ) ) {
if videoPaths. count < 2 {
return
}
let mixComposition = AVMutableComposition ( )
let audioTrack = mixComposition. addMutableTrack ( withMediaType: AVMediaType . audio, preferredTrackID: kCMPersistentTrackID_Invalid )
let videoTrack = mixComposition. addMutableTrack ( withMediaType: AVMediaType . video, preferredTrackID: kCMPersistentTrackID_Invalid )
var totalDuration = CMTime . zero
let paths = NSSearchPathForDirectoriesInDomains ( . documentDirectory, . userDomainMask, true )
let documentsDirectory = paths[ 0 ] as String
for i in 0 ..< videoPaths. count {
let pathUrl = documentsDirectory + "/ \( videoPaths[ i] ) "
if ! FileManager . default . fileExists ( atPath: pathUrl) {
print ( "文件不存在" )
break
}
let asset = AVURLAsset . init ( url: URL ( fileURLWithPath: pathUrl) )
let assetAudioTracks = asset. tracks ( withMediaType: AVMediaType . audio)
if assetAudioTracks. count == 0 {
print ( "未获取到音频" )
break
}
try ! audioTrack? . insertTimeRange ( CMTimeRangeMake ( start: CMTime . zero, duration: asset. duration) , of: assetAudioTracks. first! , at: totalDuration)
let assetVideoTracks = asset. tracks ( withMediaType: AVMediaType . video)
if assetVideoTracks. count == 0 {
print ( "未获取到视频" )
break
}
try ! videoTrack? . insertTimeRange ( CMTimeRangeMake ( start: CMTime . zero, duration: asset. duration) , of: assetVideoTracks. first! , at: totalDuration)
totalDuration = CMTimeAdd ( totalDuration, asset. duration)
}
let outputURL = URL ( fileURLWithPath: outputPath)
let avAssetExportSession = AVAssetExportSession . init ( asset: mixComposition, presetName: AVAssetExportPresetMediumQuality )
avAssetExportSession? . outputURL = outputURL
avAssetExportSession? . outputFileType = . mp4
avAssetExportSession? . shouldOptimizeForNetworkUse = true
avAssetExportSession? . exportAsynchronously {
switch avAssetExportSession? . status {
case . unknown:
print ( "AVAssetExportSessionStatusUnknown" )
break
case . waiting:
print ( "AVAssetExportSessionStatusWaiting" )
break
case . exporting:
print ( "AVAssetExportSessionStatusExporting" )
break
case . completed:
print ( "AVAssetExportSessionStatusCompleted" )
self . getVideoSize ( videoUrl: outputURL)
self . getVideoLength ( videoUrl: outputURL)
completeHandler ( true )
break
case . failed:
print ( "AVAssetExportSessionStatusFailed" )
completeHandler ( false )
break
case . cancelled:
print ( "AVAssetExportSessionStatusCancelled" )
completeHandler ( false )
break
default :
break
}
}
}
5、视频压缩/转码
@objc func starConvert ( ) {
let paths = NSSearchPathForDirectoriesInDomains ( . documentDirectory, . userDomainMask, true )
let documentsDirectory = paths[ 0 ] as String
let videoPath = documentsDirectory + "/" + "1619331826.mp4"
if ! FileManager . default . fileExists ( atPath: videoPath) {
print ( "文件不存在,请先拍照,再修改视频地址" )
return
}
let outputPath = self . getNewPath ( videoTyle: AVFileType . mov)
self . convertVideo ( inputURL: URL ( fileURLWithPath: videoPath) ,
outputURL: URL ( fileURLWithPath: outputPath) ,
presetName: AVAssetExportPresetMediumQuality ) { ( success) in
if success {
print ( "压缩/转码 成功" )
} else {
print ( "压缩/转码 失败" )
}
}
}
func convertVideo ( inputURL: URL , outputURL: URL , presetName: String = AVAssetExportPresetMediumQuality , completeHandler: @escaping ( Bool ) -> ( ) ) {
let avAsset = AVURLAsset . init ( url: inputURL)
let avAssetExportSession = AVAssetExportSession . init ( asset: avAsset, presetName: presetName)
avAssetExportSession? . outputURL = outputURL
avAssetExportSession? . outputFileType = . mp4
avAssetExportSession? . shouldOptimizeForNetworkUse = true
avAssetExportSession? . exportAsynchronously {
switch avAssetExportSession? . status {
case . unknown:
print ( "AVAssetExportSessionStatusUnknown" )
break
case . waiting:
print ( "AVAssetExportSessionStatusWaiting" )
break
case . exporting:
print ( "AVAssetExportSessionStatusExporting" )
break
case . completed:
print ( "AVAssetExportSessionStatusCompleted" )
self . getVideoSize ( videoUrl: outputURL)
self . getVideoLength ( videoUrl: outputURL)
completeHandler ( true )
break
case . failed:
print ( "AVAssetExportSessionStatusFailed" )
completeHandler ( false )
break
case . cancelled:
print ( "AVAssetExportSessionStatusCancelled" )
completeHandler ( false )
break
default :
break
}
}
}
6、添加水印(图片、文字)
@objc func starAddImage ( ) {
let paths = NSSearchPathForDirectoriesInDomains ( . documentDirectory, . userDomainMask, true )
let documentsDirectory = paths[ 0 ] as String
let videoPath = documentsDirectory + "/" + "1619343879.mp4"
if ! FileManager . default . fileExists ( atPath: videoPath) {
print ( "文件不存在,请先拍照,再修改视频地址" )
return
}
let outputPath = self . getNewPath ( videoTyle: AVFileType . mp4)
print ( outputPath)
self . videoAddMark ( imageName: "good" , title: nil , inputPath: videoPath, outputPath: outputPath) { ( success) in
if success {
print ( "添加水印 成功" )
} else {
print ( "添加水印 失败" )
}
}
}
@objc func starAddTitle ( ) {
let paths = NSSearchPathForDirectoriesInDomains ( . documentDirectory, . userDomainMask, true )
let documentsDirectory = paths[ 0 ] as String
let videoPath = documentsDirectory + "/" + "1619343879.mp4"
if ! FileManager . default . fileExists ( atPath: videoPath) {
print ( "文件不存在,请先拍照,再修改视频地址" )
return
}
let outputPath = self . getNewPath ( videoTyle: AVFileType . mp4)
print ( outputPath)
self . videoAddMark ( imageName: nil , title: "啦啦啦" , inputPath: videoPath, outputPath: outputPath) { ( success) in
if success {
print ( "添加水印 成功" )
} else {
print ( "添加水印 失败" )
}
}
}
func videoAddMark ( imageName: String ? , title: String ? , inputPath: String , outputPath: String , completeHandler: @escaping ( Bool ) -> ( ) ) {
let videoAsset = AVURLAsset . init ( url: URL ( fileURLWithPath: inputPath) )
let mixComposition = AVMutableComposition ( )
let audioTrack = mixComposition. addMutableTrack ( withMediaType: AVMediaType . audio, preferredTrackID: kCMPersistentTrackID_Invalid )
let assetAudioTracks = videoAsset. tracks ( withMediaType: AVMediaType . audio)
if assetAudioTracks. count == 0 {
print ( "未获取到音频" )
return
}
try ! audioTrack? . insertTimeRange ( CMTimeRangeMake ( start: CMTime . zero, duration: videoAsset. duration) , of: assetAudioTracks. first! , at: CMTime . zero)
let videoTrack = mixComposition. addMutableTrack ( withMediaType: AVMediaType . video, preferredTrackID: kCMPersistentTrackID_Invalid )
let assetVideoTracks = videoAsset. tracks ( withMediaType: AVMediaType . video)
if assetVideoTracks. count == 0 {
print ( "未获取到视频" )
return
}
try ! videoTrack? . insertTimeRange ( CMTimeRangeMake ( start: CMTime . zero, duration: videoAsset. duration) , of: assetVideoTracks. first! , at: CMTime . zero)
let mainInstruction = AVMutableVideoCompositionInstruction ( )
mainInstruction. timeRange = CMTimeRangeMake ( start: CMTime . zero, duration: videoAsset. duration)
let videolayerInstruction = AVMutableVideoCompositionLayerInstruction . init ( assetTrack: videoTrack! )
let videoTransform = ( assetVideoTracks. first? . preferredTransform) !
var isVideoAssetPortrait = true
var naturalSize = ( assetVideoTracks. first? . naturalSize) !
if videoTransform. a == 0 ,
videoTransform. b == 1 ,
videoTransform. c == - 1 ,
videoTransform. d == 0
{
isVideoAssetPortrait = true
} else if videoTransform. a == 0 ,
videoTransform. b == - 1 ,
videoTransform. c == 1 ,
videoTransform. d == 0 {
isVideoAssetPortrait = true
} else if videoTransform. a == 1 ,
videoTransform. b == 0 ,
videoTransform. c == 0 ,
videoTransform. d == 1 {
isVideoAssetPortrait = false
} else if videoTransform. a == - 1 ,
videoTransform. b == 0 ,
videoTransform. c == 0 ,
videoTransform. d == - 1 {
isVideoAssetPortrait = false
}
videolayerInstruction. setTransform ( videoTransform, at: CMTime . zero)
mainInstruction. layerInstructions = [ videolayerInstruction]
let mainCompositionInst = AVMutableVideoComposition ( )
if isVideoAssetPortrait {
naturalSize = CGSize ( width: naturalSize. height, height: naturalSize. width)
}
let width = naturalSize. width
let height = naturalSize. height
mainCompositionInst. renderSize = CGSize . init ( width: width, height: height)
mainCompositionInst. instructions = [ mainInstruction]
mainCompositionInst. frameDuration = CMTimeMake ( value: 1 , timescale: 30 )
if title == nil && ( imageName == nil || UIImage ( named: imageName! ) == nil ) {
completeHandler ( false )
return
}
let overlayLayer = CALayer ( )
if title != nil {
let subtitle1Text = CATextLayer ( )
subtitle1Text. font = "Helvetica-Bold" as CFTypeRef
subtitle1Text. fontSize = 40
subtitle1Text. frame = CGRect ( x: width/ 2 - 100 , y: height/ 2 - 60 , width: 200 , height: 120 )
subtitle1Text. string = title
subtitle1Text. alignmentMode = . center
subtitle1Text. foregroundColor = UIColor . white. cgColor
overlayLayer. addSublayer ( subtitle1Text)
}
if imageName != nil , let image = UIImage ( named: imageName! ) {
let picLayer = CALayer ( )
picLayer. contents = image. cgImage
picLayer. frame = CGRect ( x: width/ 2 - 80 , y: height/ 2 - 80 , width: 160 , height: 160 )
overlayLayer. addSublayer ( picLayer)
}
overlayLayer. frame = CGRect ( x: 0 , y: 0 , width: width, height: height)
overlayLayer. masksToBounds = true
let parentLayer = CALayer ( )
let videoLayer = CALayer ( )
parentLayer. frame = CGRect ( x: 0 , y: 0 , width: width, height: height)
videoLayer. frame = CGRect ( x: 0 , y: 0 , width: width, height: height)
parentLayer. addSublayer ( videoLayer)
parentLayer. addSublayer ( overlayLayer)
mainCompositionInst. animationTool = AVVideoCompositionCoreAnimationTool . init ( postProcessingAsVideoLayer: videoLayer, in : parentLayer)
let outputURL = URL ( fileURLWithPath: outputPath)
let avAssetExportSession = AVAssetExportSession . init ( asset: mixComposition, presetName: AVAssetExportPresetMediumQuality )
avAssetExportSession? . outputURL = outputURL
avAssetExportSession? . outputFileType = . mp4
avAssetExportSession? . shouldOptimizeForNetworkUse = true
avAssetExportSession? . videoComposition = mainCompositionInst;
avAssetExportSession? . exportAsynchronously {
switch avAssetExportSession? . status {
case . unknown:
print ( "AVAssetExportSessionStatusUnknown" )
break
case . waiting:
print ( "AVAssetExportSessionStatusWaiting" )
break
case . exporting:
print ( "AVAssetExportSessionStatusExporting" )
break
case . completed:
print ( "AVAssetExportSessionStatusCompleted" )
self . getVideoSize ( videoUrl: outputURL)
self . getVideoLength ( videoUrl: outputURL)
completeHandler ( true )
break
case . failed:
print ( "AVAssetExportSessionStatusFailed" )
completeHandler ( false )
break
case . cancelled:
print ( "AVAssetExportSessionStatusCancelled" )
completeHandler ( false )
break
default :
break
}
}
}
7、获取一个新的沙盒存储地址
func getNewPath ( videoTyle: AVFileType ) -> String {
let paths = NSSearchPathForDirectoriesInDomains ( . documentDirectory, . userDomainMask, true )
let documentsDirectory = paths[ 0 ] as String
let timeInterval = Int ( Date ( ) . timeIntervalSince1970)
var filePath = " \( documentsDirectory ) / \( timeInterval ) "
switch videoTyle {
case . mp4:
filePath = filePath + ".mp4"
break
case . mov:
filePath = filePath + ".mov"
break
default :
filePath = filePath + ".mp4"
break
}
return filePath
}
8、根据路径删除沙盒中某个文件
func deleteFile ( path: String ) -> Bool {
if FileManager . default . fileExists ( atPath: path) {
do {
try FileManager . default . removeItem ( atPath: path)
return true
} catch {
return false
}
}
return false
}
9、保存图片到相册
func saveImage ( image: UIImage ) {
UIImageWriteToSavedPhotosAlbum ( image, self , #selector ( self . saveImage ( image: didFinishSavingWithError: contextInfo: ) ) , nil )
}
@objc private func saveImage ( image: UIImage , didFinishSavingWithError error: NSError ? , contextInfo: AnyObject ) {
var info = ""
if error != nil {
info = "保存图片失败"
} else {
info = "保存图片成功"
}
print ( info)
}
10、保存视频到相册
func saveVideoToAlbum ( videoUrl: URL ) {
var info = ""
PHPhotoLibrary . shared ( ) . performChanges ( {
PHAssetChangeRequest . creationRequestForAssetFromVideo ( atFileURL: videoUrl)
} ) { ( success, error) in
if success {
info = "保存成功"
} else {
info = "保存失败,err = \( error. debugDescription ) "
}
DispatchQueue . main. async {
let alertVC = UIAlertController ( title: info, message: nil , preferredStyle: . alert)
alertVC. addAction ( UIAlertAction ( title: "OK" , style: . default , handler: nil ) )
self . present ( alertVC, animated: true , completion: nil )
}
}
}