VideoUtils

2018-03-12  本文已影响98人  devchena
//
//  MediaUtils.swift
//  QingkCloudPhoneBackground
//
//  Created by user on 2018/1/15.
//  Copyright © 2018年 devchena. All rights reserved.
//

import Photos
import AVFoundation
import AssetsLibrary
import AVKit

let defaultFolderName = "文件夹名称"

class VideoUtils {
    
    /* 临时文件夹路径(先创建) */
    class func tempPath() -> String? {
        let tempPath = NSTemporaryDirectory()
        let tempMediaURL = URL(fileURLWithPath: tempPath).appendingPathComponent("tempMedia")
        
        let fileManager = FileManager.default
        let isExisting = fileManager.fileExists(atPath: tempMediaURL.path)
        if !isExisting {
            do {
                try fileManager.createDirectory(at: tempMediaURL, withIntermediateDirectories: true, attributes: nil)
            } catch let error {
                print("TempMedia directory creation failed:\(error)")
                return nil
            }
        }
        
        return tempMediaURL.path
    }
    
    /* 默认文件输出路径 */
    class func defaultOutputURL(with fileType: String = "mov") -> URL? {
        guard let tempPath = tempPath() else {
            return nil
        }
        
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "YYYY-MM-dd-hh:mm:ss:SS"
        let fileName = dateFormatter.string(from: Date()) + String(format: ".%@", fileType)
        return URL(fileURLWithPath: tempPath).appendingPathComponent(fileName)
    }

    /* 删除临时文件夹 */
    class func deleteTempPath() {
        guard let path = tempPath() else {
            return
        }
        
        let fileManager = FileManager.default
        if fileManager.fileExists(atPath: path) {
            do {
                try fileManager.removeItem(atPath: path)
            } catch let error {
                print("Delete tempPath failed:\(error)")
            }
        }
    }

    /* 删除指定路径文件 */
    class func deleteFile(atPath path: String) {
        let fileManager = FileManager.default
        if fileManager.fileExists(atPath: path) {
            do {
                try fileManager.removeItem(atPath: path)
            } catch let error {
                print("Delete File failed:\(error)")
            }
        }
    }
    
    /* 指定路径文件大小 */
    class func fileSize(atPath path: String) -> UInt64 {
        let fileHandle = FileHandle(forReadingAtPath: path)
        return fileHandle?.seekToEndOfFile() ?? 0
    }
    
    /* 获取指定路径下的二进制数据 */
    class func fileData(atPath path: String) -> Data? {
        guard let data = FileManager.default.contents(atPath: path) else {
            return nil
        }
        return data
    }
}

extension VideoUtils {
    
    /* 是否存在相册文件夹 */
    class func isExistFolder(with folderName: String = defaultFolderName) -> Bool {
        var isExisted = false

        let collectonResults = PHCollectionList.fetchTopLevelUserCollections(with: nil)
        collectonResults.enumerateObjects({ (obj, index, stop) -> Void in
            if let assetCollection = obj as? PHAssetCollection, assetCollection.localizedTitle == folderName {
                isExisted = true
            }
        })
        
        return isExisted
    }
    
    /* 创建相册文件夹 */
    class func createFolder(with folderName: String = defaultFolderName) {
        if !isExistFolder(with: folderName) {
            PHPhotoLibrary.shared().performChanges({
                PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: folderName)
            }, completionHandler: { (success, error) in
                if !success {
                    print("创建相册文件夹失败:\(String(describing: error))")
                }
            })
        }
    }
    
    /* 保存视频至指定相册文件夹 */
    class func saveVideoToFolder(videoPathURL: URL,
                                 folderName: String = defaultFolderName,
                                 completion: @escaping (_ localIdentifier: String) -> Void,
                                 failure: ((_ error: Error?) -> Void)? = nil) {
        var localIdentifier: String?
        let collectonResults = PHCollectionList.fetchTopLevelUserCollections(with: nil)
        collectonResults.enumerateObjects({ (obj, index, stop) -> Void in
            if let assetCollection = obj as? PHAssetCollection, assetCollection.localizedTitle == folderName {
                PHPhotoLibrary.shared().performChanges({
                    // 请求创建一个Asset
                    let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoPathURL)
                    // 请求编辑相册
                    let collectonRequest = PHAssetCollectionChangeRequest(for: assetCollection)
                    // 为Asset创建一个占位符,放到相册编辑请求中
                    if let placeHolder = assetRequest?.placeholderForCreatedAsset {
                        // 相册中添加视频
                        collectonRequest?.addAssets([placeHolder] as NSFastEnumeration)
                        localIdentifier = placeHolder.localIdentifier
                    } else {
                        failure?(nil)
                        return
                    }
                    
                }, completionHandler: { (success, error) in
                    if success && localIdentifier != nil {
                        completion(localIdentifier!)
                    } else {
                        failure?(error)
                    }
                })
            }
        })
    }
    
    class func requestVideo(localIdentifier: String,
                             folderName: String = defaultFolderName,
                             completion: @escaping (_ asset: PHAsset) -> Void,
                             failure: (() -> Void)? = nil) {
        DispatchQueue.global().async(execute: { () -> Void in
            let result1 = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options: nil)
            let result2 = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .albumRegular, options: nil)
            let results = [result1, result2]
            
            let fetchOptions = PHFetchOptions()
            fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
            fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.video.rawValue)
            
            var asset: PHAsset?
            for result in results {
                for i in 0..<result.count {
                    let assetCollection = result[i]
                    if assetCollection.localizedTitle == folderName {
                        let fetchResult = PHAsset.fetchAssets(in: assetCollection, options: fetchOptions)
                        fetchResult.enumerateObjects({ (obj, index, stop) -> Void in
                            if obj.localIdentifier == localIdentifier {
                                asset = obj
                            }
                        })
                    }
                }
            }
            
            if asset != nil {
                completion(asset!)
            } else {
                failure?()
            }
        })
    }
    
    class func requestVideoInformation(for asset: PHAsset,
                                       completion: @escaping (_ entity: MTVideoEntity) -> Void,
                                       failure: ((Error?) -> Void)? = nil) {
        let options = PHVideoRequestOptions()
        options.version = .current
        options.deliveryMode = .automatic
        PHImageManager.default().requestAVAsset(forVideo: asset, options: options, resultHandler: { (obj, audioMix, info) in
            guard let avAsset = obj, let avURLAsset = avAsset as? AVURLAsset else {
                failure?(nil)
                return
            }
            
            let url = avURLAsset.url
            let time = avAsset.duration
            let seconds = UInt(ceil(Double(time.value)/Double(time.timescale)))
            
            let entity = MTVideoEntity()
            entity.duration = seconds
            entity.videoURL = url
            entity.size = fileSize(atPath: url.path)
            entity.thumbnail = extractThumbnail(with: avURLAsset)

            completion(entity)
        })
    }
    
    class func requestVideoInformation(localIdentifier: String,
                                       folderName: String = defaultFolderName,
                                       completion: @escaping (_ entity: MTVideoEntity) -> Void,
                                       failure: ((Error?) -> Void)? = nil) {
        requestVideo(localIdentifier: localIdentifier, folderName: folderName, completion: { (asset) in
            requestVideoInformation(for: asset, completion: { (entity) in
                completion(entity)
            }, failure: { (error) in
                failure?(error)
            })
        }, failure: {
            failure?(nil)
        })
    }
}

extension VideoUtils {
    
    /* 请求录制权限 */
    class func requestRecordingPermission(completionHandler handler: @escaping (_ videoAllowed: Bool, _ audioAllowed: Bool) -> Void) {
        AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo) { (videoAllowed) in
            AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeAudio, completionHandler: { (audioAllowed) in
                DispatchQueue.main.async {
                    handler(videoAllowed, audioAllowed)
                }
            })
        }
    }

    // 转换assetURL为路径
    class func convertToVideoURL(with assetURL: URL, completion: @escaping (URL?) -> Void) {
        DispatchQueue.global().async {
            let fetchOptions = PHFetchOptions()
            fetchOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.video.rawValue)
            let fetchResult = PHAsset.fetchAssets(withALAssetURLs: [assetURL], options: fetchOptions)
            guard let phAsset = fetchResult.firstObject else {
                completion(nil)
                return
            }
            
            let options = PHVideoRequestOptions()
            options.version = .current
            options.deliveryMode = .automatic
            PHImageManager.default().requestAVAsset(forVideo: phAsset, options: options, resultHandler: { (avAsset, audioMix, info) in
                guard let url = (avAsset as? AVURLAsset)?.url else {
                    completion(nil)
                    return
                }
                completion(url)
            })
        }
    }

    /* 提取指定路径的本地视频封面图 */
    class func extractThumbnail(with asset: AVURLAsset) -> UIImage? {
        let imageGenerator = AVAssetImageGenerator(asset: asset)
        imageGenerator.appliesPreferredTrackTransform = true
        imageGenerator.apertureMode = AVAssetImageGeneratorApertureModeEncodedPixels
        let time = CMTime(value: 0, timescale: asset.duration.timescale)
        var cgImage: CGImage?
        do {
            try cgImage = imageGenerator.copyCGImage(at: time, actualTime: nil)
        } catch let error {
            print("Copy cgImage failed! Error:\(error)")
            return nil
        }
        return (cgImage != nil) ? UIImage(cgImage: cgImage!) : nil
    }
    
    class func loaclVideoInfo(with fileURL: URL) -> MTVideoEntity {
        let asset = AVURLAsset(url: fileURL)
        let thumbnail = extractThumbnail(with: asset)
        let time = asset.duration
        let seconds = UInt(ceil(Double(time.value)/Double(time.timescale)))
        
        let entity = MTVideoEntity()
        entity.duration = seconds
        entity.thumbnail = thumbnail
        entity.size = fileSize(atPath: fileURL.path)
        entity.videoURL = fileURL
        entity.durationString = self.getFormatPlayTime(secounds: TimeInterval(entity.duration))
        let sizeFloat = Float(entity.size)/1024.0/1024.0
        entity.sizeString = String(format: "%.2fM", sizeFloat)
        return entity
    }
    
    class func getFormatPlayTime(secounds:TimeInterval)->String{
        if secounds.isNaN{
            return "00:00"
        }
        var Min = Int(secounds / 60)
        let Sec = Int(secounds.truncatingRemainder(dividingBy: 60))
        var Hour = 0
        if Min>=60 {
            Hour = Int(Min / 60)
            Min = Min - Hour*60
            return String(format: "%02d:%02d:%02d", Hour, Min, Sec)
        }
        return String(format: "%02d:%02d", Min, Sec)
    }
}

extension VideoUtils {
    
    /* 保存视频至相册 */
    class func saveVideoToPhotosAlbum(videoPathURL: URL,
                                      completion: @escaping (_ videoPathURLInPhotosAlbum: URL) -> Void,
                                      failure: ((_ error: Error?, _ message: String?) -> Void)? = nil) {
        PhotoAssetUtils.authorizationStatus({ (isValid) -> Void in
            if isValid {
                ALAssetsLibrary().writeVideoAtPath(toSavedPhotosAlbum: videoPathURL, completionBlock: { (assetURL, error) in
                    if let url = assetURL, error == nil {
                        convertToVideoURL(with: url, completion: { (fileURL) in
                            if fileURL != nil {
                                completion(fileURL!)
                            } else {
                                failure?(nil, "获取视频路径异常")
                            }
                        })
                    } else {
                        failure?(error, "保存视频至相册失败")
                    }
                })
            } else {
                failure?(nil, nil)
                let message = "请在【设置】-【隐私】-【照片】里打开允许访问相册的权限"
                UIApplication.shared.keyWindow?.showAlert(type: .Alert, title: "您没有开启相册权限", message: message, sourceView: nil, actions: [
                    AlertAction(title: "取消", type: .Cancel, handler: nil),
                    AlertAction(title: "去设置", type: .Default, handler: { () -> Void in
                        if let url = URL(string: UIApplicationOpenSettingsURLString) {
                            UIApplication.shared.openURL(url)
                        }
                    })
                ])
            }
        })
    }

    /*!
     @abstract                      写入视频文件的二进制数据到指定路径
     @param        toFile           写入的视频路径
     @param        completion       完成回调
     @param        failure          失败回调
     @discussion                    不影响源文件
     */
    class func writeVideo(for asset: PHAsset,
                          toFile fileURL: URL,
                          completion: @escaping (_ fileURL: URL) -> Void,
                          failure: ((Error?) -> Void)? = nil) {
        guard asset.mediaType == .video else {
            failure?(nil)
            return
        }
        
        if #available(iOS 9.0, *) {
            var assetResource: PHAssetResource?
            let assetResources = PHAssetResource.assetResources(for: asset)
            for assetRes in assetResources {
                if assetRes.type == .video { assetResource = assetRes }
            }
            
            if let assetRes = assetResource {
                let fileManager = FileManager.default
                if fileManager.fileExists(atPath: fileURL.path) {
                    do {
                        try fileManager.removeItem(atPath: fileURL.path)
                    } catch let error {
                        print("Remove the existing item failure:\(error)")
                        failure?(error)
                        return
                    }
                }
                
                PHAssetResourceManager.default().writeData(for: assetRes, toFile: fileURL, options: nil, completionHandler: { (error) in
                    error == nil ? completion(fileURL) : failure?(error)
                })
            } else { failure?(nil) }
        } else {
            let options = PHVideoRequestOptions()
            options.version = .current
            options.deliveryMode = .automatic
            PHImageManager.default().requestAVAsset(forVideo: asset, options: options, resultHandler: { (avAsset, audioMix, info) in
                guard let url = (avAsset as? AVURLAsset)?.url else {
                    failure?(nil)
                    return
                }
            
                var data: Data?
                do {
                    try data = Data(contentsOf: url)
                } catch let error {
                    failure?(error)
                    return
                }
                
                guard let videoData = data else {
                    failure?(nil)
                    return
                }
                do {
                    try videoData.write(to: fileURL)
                } catch let error {
                    failure?(error)
                    return
                }
                completion(fileURL)
            })
        }
    }
    
    /*!
     @abstract                      转换视频质量
     @param        inputURL         源视频文件地址(二进制数据地址,非PHAssetURL)
     @param        outputURL        转换后视频文件输出地址
     @param        fileLengthLimit  转换后的文件大小限制
     @param        fileLengthLimit  转换进度
     @param        completion       完成回调
     @param        failure          失败回调
     @discussion                    默认将原视频文件转换为中质量MP4格式并输出到指定路径(不影响源文件)
     */
    class func convertVideoQuailty(with inputURL: URL,
                                   outputURL: URL? = nil,
                                   fileLengthLimit: Int64? = nil,
                                   progress: ((Float) -> Void)? = nil,
                                   completion: @escaping (_ exportSession: AVAssetExportSession, _ compressedOutputURL: URL) -> Void,
                                   failure: ((Error?) -> Void)? = nil) {
        guard let compressedOutputURL = (outputURL ?? defaultOutputURL(with: "mp4")) else {
            failure?(nil)
            return
        }
        
        let fileManager = FileManager.default
        if fileManager.fileExists(atPath: compressedOutputURL.path) {
            do {
                try fileManager.removeItem(atPath: compressedOutputURL.path)
            } catch let error {
                print("Remove the existing item failure:\(error)")
                failure?(error)
                return
            }
        }
        
        let asset = AVURLAsset(url: inputURL, options: nil)
        guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPreset960x540) else {
            failure?(nil)
            return
        }
        exportSession.outputURL = compressedOutputURL
        exportSession.shouldOptimizeForNetworkUse = true
        exportSession.outputFileType = AVFileTypeMPEG4
        if fileLengthLimit != nil {
            exportSession.fileLengthLimit = fileLengthLimit!
        }
        exportSession.exportAsynchronously {
            progress?(exportSession.progress)
            let exportStatus = exportSession.status
            switch exportStatus {
            case .unknown:
                break
            case .waiting:
                break
            case .exporting:
                break
            case .cancelled:
                break
            case .failed:
                print("AVAssetExportSessionStatusFailed:\(String(describing: exportSession.error))!")
                failure?(exportSession.error)
            case .completed:
                completion(exportSession, compressedOutputURL)
            }
        }
    }
}



上一篇下一篇

猜你喜欢

热点阅读