//
// 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.. 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)
}
}
}
}