【iOS】视频录像相关功能调研(二)

@[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..()) {
        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)
            }
        }
    }

你可能感兴趣的:(【iOS】视频录像相关功能调研(二))