@[toc]
1、获取视频时长(秒数)
//MARK: 获取视频时长(秒数)
@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、获取视频文件大小
//MARK: 获取视频文件大小//文件属性
@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、获取指定时间帧图片
//MARK: 获取指定时间帧图片
/// 获取指定时间帧图片
/// - Parameters:
/// - videoUrl: 视频地址
/// - cmtime: 指定的时间
/// - width: 宽度 根据视频的宽高比来计算图片的高度
@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 {
//建立新的AVAsset & AVAssetImageGenerator
let asset = AVAsset.init(url: URL(fileURLWithPath: videoPath))
let imageGenerator = AVAssetImageGenerator.init(asset: asset)
//设置maximumSize 宽为100,高为0 根据视频的宽高比来计算图片的高度 控制图片清晰度
imageGenerator.maximumSize = CGSize(width: width, height: 0)
//捕捉视频缩略图会考虑视频的变化(如视频的方向变化),如果不设置,缩略图的方向可能出错
imageGenerator.appliesPreferredTrackTransform = true
// CMTimeMake第一个参数是时间,第二个参数是 每秒的分数 第一个/第二个 才是秒
let imageRef = try! imageGenerator.copyCGImage(at:cmtime, actualTime: nil)
//将图片转化为UIImage
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("多视频合成 失败")
}
}
}
//MARK: 多视频合成
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))
// 获取AVAsset中的音频
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)
// 获取AVAsset中的视频
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("压缩/转码 失败")
}
}
}
//MARK: 视频压缩//转换格式
/// 视频压缩//转换格式
/// - Parameters:
/// - inputURL: 视频地址
/// - outputURL: 视频压缩后的地址
/// - presetName: 视频预设
/// - completeHandler: <#completeHandler description#>
/// - Returns: <#description#>
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)->()) {
//创建AVAsset实例
let videoAsset = AVURLAsset.init(url: URL(fileURLWithPath: inputPath))
let mixComposition = AVMutableComposition()
//音频轨道
let audioTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)
// 获取AVAsset中的音频
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)
// 获取AVAsset中的视频
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)
//1 AVMutableVideoCompositionInstruction 视频轨道中的一个视频,可以缩放、旋转等
let mainInstruction = AVMutableVideoCompositionInstruction()
mainInstruction.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration)
// 2 AVMutableVideoCompositionLayerInstruction 一个视频轨道,包含了这个轨道上的所有视频素材
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)
// 3 - Add instructions
mainInstruction.layerInstructions = [videolayerInstruction]
//AVMutableVideoComposition:管理所有视频轨道,水印添加就在这上面
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、获取一个新的沙盒存储地址
//MARK: 获取一个新的沙盒存储地址
/// 获取一个新的沙盒存储地址
/// - Returns: <#description#>
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、根据路径删除沙盒中某个文件
//MARK: 根据路径删除沙盒中某个文件
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、保存图片到相册
//MARK: 保存图片到相册
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、保存视频到相册
//MARK:保存视频到相册
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)
}
}
}