|
@@ -1,778 +0,0 @@
|
|
|
-//
|
|
|
-// PQPHAssetVideoParaseUtil.swift
|
|
|
-// PQSpeed
|
|
|
-//
|
|
|
-// Created by SanW on 2020/8/3.
|
|
|
-// Copyright © 2020 BytesFlow. All rights reserved.
|
|
|
-//
|
|
|
-
|
|
|
-import CoreServices
|
|
|
-import Photos
|
|
|
-import UIKit
|
|
|
-
|
|
|
-var currentExportSession: AVAssetExportSession?
|
|
|
-open class PQPHAssetVideoParaseUtil: NSObject {
|
|
|
- static var imagesOptions: PHImageRequestOptions = {
|
|
|
- let imagesOptions = PHImageRequestOptions()
|
|
|
- imagesOptions.isSynchronous = false
|
|
|
- imagesOptions.deliveryMode = .fastFormat
|
|
|
- imagesOptions.resizeMode = .fast
|
|
|
- imagesOptions.version = .current
|
|
|
- return imagesOptions
|
|
|
- }()
|
|
|
-
|
|
|
- static var singleImageOptions: PHImageRequestOptions = {
|
|
|
- let singleImageOptions = PHImageRequestOptions()
|
|
|
- singleImageOptions.isSynchronous = true
|
|
|
- singleImageOptions.isNetworkAccessAllowed = true
|
|
|
- singleImageOptions.deliveryMode = .highQualityFormat
|
|
|
- singleImageOptions.resizeMode = .none
|
|
|
- singleImageOptions.version = .current
|
|
|
- return singleImageOptions
|
|
|
- }()
|
|
|
-
|
|
|
- static var videoRequestOptions: PHVideoRequestOptions = {
|
|
|
- let videoRequestOptions = PHVideoRequestOptions()
|
|
|
- // 解决慢动作视频返回AVComposition而不是AVURLAsset
|
|
|
-// videoRequestOptions.version = .original
|
|
|
- videoRequestOptions.version = .current
|
|
|
- // 下载iCloud视频
|
|
|
- videoRequestOptions.isNetworkAccessAllowed = true
|
|
|
- videoRequestOptions.deliveryMode = .mediumQualityFormat
|
|
|
- return videoRequestOptions
|
|
|
- }()
|
|
|
-
|
|
|
- /// PHAsset解析为AVPlayerItem
|
|
|
- /// - Parameters:
|
|
|
- /// - asset: <#asset description#>
|
|
|
- /// - resultHandler: <#resultHandler description#>
|
|
|
- /// - Returns: <#description#>
|
|
|
- public class func parasToAVPlayerItem(phAsset: PHAsset, isHighQuality: Bool = false, resultHandler: @escaping (AVPlayerItem?, Float64, [AnyHashable: Any]?) -> Void) {
|
|
|
- PHImageManager().requestPlayerItem(forVideo: phAsset, options: videoRequestOptions) { playerItem, info in
|
|
|
- if isHighQuality, (playerItem?.asset as? AVURLAsset)?.url.absoluteString.components(separatedBy: "/").last?.contains(".medium.") ?? false {
|
|
|
- let tempVideoOptions = PHVideoRequestOptions()
|
|
|
- tempVideoOptions.version = .original
|
|
|
- // 下载iCloud视频
|
|
|
- tempVideoOptions.isNetworkAccessAllowed = true
|
|
|
- tempVideoOptions.deliveryMode = .highQualityFormat
|
|
|
- tempVideoOptions.progressHandler = { progress, error, pointer, info in
|
|
|
- BFLog(message: "导出playerItem-progress = \(progress),error = \(String(describing: error)),pointer = \(pointer),info = \(String(describing: info))")
|
|
|
- }
|
|
|
- PHImageManager().requestPlayerItem(forVideo: phAsset, options: tempVideoOptions) { playerItem, info in
|
|
|
- let size = try! (playerItem?.asset as? AVURLAsset)?.url.resourceValues(forKeys: [.fileSizeKey])
|
|
|
- BFLog(message: "size = \(String(describing: size))")
|
|
|
- resultHandler(playerItem, Float64(size?.fileSize ?? 0), info)
|
|
|
- }
|
|
|
- } else {
|
|
|
- let size = try! (playerItem?.asset as? AVURLAsset)?.url.resourceValues(forKeys: [.fileSizeKey])
|
|
|
- BFLog(message: "size = \(String(describing: size))")
|
|
|
- resultHandler(playerItem, Float64(size?.fileSize ?? 0), info)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// PHAsset解析为AVAsset
|
|
|
- /// - Parameters:
|
|
|
- /// - asset: <#asset description#>
|
|
|
- /// - resultHandler: <#resultHandler description#>
|
|
|
- /// - Returns: <#description#>
|
|
|
- public class func parasToAVAsset(phAsset: PHAsset, isHighQuality: Bool = true, resultHandler: @escaping (AVAsset?, Int, AVAudioMix?, [AnyHashable: Any]?) -> Void) {
|
|
|
- PHImageManager.default().requestAVAsset(forVideo: phAsset, options: videoRequestOptions) { avAsset, audioMix, info in
|
|
|
- if isHighQuality, (avAsset as? AVURLAsset)?.url.absoluteString.components(separatedBy: "/").last?.contains(".medium.") ?? false {
|
|
|
- let tempVideoOptions = PHVideoRequestOptions()
|
|
|
- tempVideoOptions.version = .original
|
|
|
- // 下载iCloud视频
|
|
|
- tempVideoOptions.isNetworkAccessAllowed = true
|
|
|
- tempVideoOptions.deliveryMode = .highQualityFormat
|
|
|
- tempVideoOptions.progressHandler = { progress, error, pointer, info in
|
|
|
- BFLog(message: "导出playerItem-progress = \(progress),error = \(String(describing: error)),pointer = \(pointer),info = \(String(describing: info))")
|
|
|
- }
|
|
|
- PHImageManager.default().requestAVAsset(forVideo: phAsset, options: tempVideoOptions) { tempAvAsset, tempAudioMix, tempInfo in
|
|
|
- let size = try! (tempAvAsset as? AVURLAsset)?.url.resourceValues(forKeys: [.fileSizeKey])
|
|
|
- BFLog(message: "size = \(String(describing: size))")
|
|
|
- resultHandler(tempAvAsset, size?.fileSize ?? 0, tempAudioMix, tempInfo)
|
|
|
- }
|
|
|
- } else {
|
|
|
- let size = try! (avAsset as? AVURLAsset)?.url.resourceValues(forKeys: [.fileSizeKey])
|
|
|
- resultHandler(avAsset, size?.fileSize ?? 0, audioMix, info)
|
|
|
- BFLog(message: "size = \(String(describing: size))")
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// PHAsset 转码为.mp4保存本地
|
|
|
- /// - Parameters:
|
|
|
- /// - phAsset: <#phAsset description#>
|
|
|
- /// - isAdjustRotationAngle: 是否调整旋转角度
|
|
|
- /// - resultHandler: <#resultHandler description#>
|
|
|
- /// - Returns: <#description#>
|
|
|
- public class func exportPHAssetToMP4(phAsset: PHAsset, isAdjustRotationAngle: Bool = true, isCancelCurrentExport: Bool = false, deliveryMode: PHVideoRequestOptionsDeliveryMode? = .automatic, resultHandler: @escaping (_ phAsset: PHAsset, _ aVAsset: AVAsset?, _ filePath: String?, _ errorMsg: String?) -> Void) {
|
|
|
- BFLog(message: "导出相册视频-开始导出:phAsset = \(phAsset)")
|
|
|
- if isCancelCurrentExport {
|
|
|
- currentExportSession?.cancelExport()
|
|
|
- }
|
|
|
- PQPHAssetVideoParaseUtil.parasToAVAsset(phAsset: phAsset) { avAsset, fileSize, _, _ in
|
|
|
- if avAsset is AVURLAsset {
|
|
|
- // 创建目录
|
|
|
- createDirectory(path: photoLibraryDirectory)
|
|
|
- let fileName = (avAsset as! AVURLAsset).url.absoluteString
|
|
|
- let filePath = photoLibraryDirectory + fileName.md5.md5 + ".mp4"
|
|
|
- let data = try? Data(contentsOf: NSURL.fileURL(withPath: filePath))
|
|
|
- if FileManager.default.fileExists(atPath: filePath) && (data?.count ?? 0) > fileSize / 40 {
|
|
|
- BFLog(message: "导出相册视频-已经导出完成:\(filePath)")
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, filePath, nil)
|
|
|
- }
|
|
|
- } else {
|
|
|
- let tempExportSession = PQSingletoMemoryUtil.shared.allExportSession[phAsset]
|
|
|
- if tempExportSession != nil {
|
|
|
- BFLog(message: "导出相册视频-正在导出")
|
|
|
- return
|
|
|
- }
|
|
|
- BFLog(message: "导出相册视频-未导出视频过,开始导出:phAsset = \(phAsset)")
|
|
|
- // 删除以创建地址
|
|
|
- if FileManager.default.fileExists(atPath: filePath) {
|
|
|
- do {
|
|
|
- try FileManager.default.removeItem(at: NSURL.fileURL(withPath: filePath))
|
|
|
- } catch {
|
|
|
- BFLog(message: "导出相册视频-error == \(error)")
|
|
|
- }
|
|
|
- }
|
|
|
- let requestOptions = PHVideoRequestOptions()
|
|
|
- // 解决慢动作视频返回AVComposition而不是AVURLAsset
|
|
|
- // videoRequestOptions.version = .original
|
|
|
- requestOptions.version = .current
|
|
|
- // 下载iCloud视频
|
|
|
- requestOptions.isNetworkAccessAllowed = false
|
|
|
- requestOptions.progressHandler = { progress, error, pointer, info in
|
|
|
- BFLog(message: "导出相册视频-progress = \(progress),error = \(String(describing: error)),pointer = \(pointer),info = \(String(describing: info))")
|
|
|
- }
|
|
|
- requestOptions.deliveryMode = deliveryMode ?? .automatic
|
|
|
- PHImageManager.default().requestExportSession(forVideo: phAsset, options: requestOptions, exportPreset: (deliveryMode == .automatic || deliveryMode == .mediumQualityFormat) ? AVAssetExportPresetMediumQuality : (deliveryMode == .highQualityFormat ? AVAssetExportPresetHighestQuality : AVAssetExportPresetLowQuality), resultHandler: { avAssetExportSession, _ in
|
|
|
- BFLog(message: "导出相册视频-请求到导出 avAssetExportSession = \(String(describing: avAssetExportSession))")
|
|
|
- currentExportSession = avAssetExportSession
|
|
|
- if avAssetExportSession != nil {
|
|
|
- PQSingletoMemoryUtil.shared.allExportSession[phAsset] = avAssetExportSession!
|
|
|
- }
|
|
|
- avAssetExportSession?.outputURL = NSURL(fileURLWithPath: filePath) as URL
|
|
|
- avAssetExportSession?.shouldOptimizeForNetworkUse = true
|
|
|
- avAssetExportSession?.outputFileType = .mp4
|
|
|
- if isAdjustRotationAngle {
|
|
|
- let rotationAngle = PQPHAssetVideoParaseUtil.videoRotationAngle(assert: avAsset!)
|
|
|
- // mdf by ak 统一导出的视频为30FPS
|
|
|
- var centerTranslate: CGAffineTransform = CGAffineTransform(translationX: 0, y: 0)
|
|
|
- var mixedTransform: CGAffineTransform = CGAffineTransform()
|
|
|
- let videoComposition = AVMutableVideoComposition()
|
|
|
- videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
|
|
|
- let tracks = avAsset?.tracks(withMediaType: .video)
|
|
|
- let firstTrack = tracks?.first
|
|
|
-
|
|
|
- videoComposition.renderSize = CGSize(width: firstTrack?.naturalSize.width ?? 0, height: firstTrack?.naturalSize.height ?? 0)
|
|
|
-
|
|
|
- mixedTransform = centerTranslate.rotated(by: 0)
|
|
|
-
|
|
|
- if rotationAngle == 90 {
|
|
|
- centerTranslate = CGAffineTransform(translationX: firstTrack?.naturalSize.height ?? 0, y: 0)
|
|
|
- mixedTransform = centerTranslate.rotated(by: .pi / 2)
|
|
|
- videoComposition.renderSize = CGSize(width: firstTrack?.naturalSize.height ?? 0, height: firstTrack?.naturalSize.width ?? 0)
|
|
|
- } else if rotationAngle == 180 {
|
|
|
- centerTranslate = CGAffineTransform(translationX: firstTrack?.naturalSize.width ?? 0, y: firstTrack?.naturalSize.height ?? 0)
|
|
|
- mixedTransform = centerTranslate.rotated(by: .pi)
|
|
|
- videoComposition.renderSize = CGSize(width: firstTrack?.naturalSize.width ?? 0, height: firstTrack?.naturalSize.height ?? 0)
|
|
|
- } else if rotationAngle == 270 {
|
|
|
- centerTranslate = CGAffineTransform(translationX: 0, y: firstTrack?.naturalSize.width ?? 0)
|
|
|
- mixedTransform = centerTranslate.rotated(by: .pi / 2 * 3)
|
|
|
- videoComposition.renderSize = CGSize(width: firstTrack?.naturalSize.height ?? 0, height: firstTrack?.naturalSize.width ?? 0)
|
|
|
- }
|
|
|
- let roateInstruction = AVMutableVideoCompositionInstruction()
|
|
|
- roateInstruction.timeRange = CMTimeRange(start: CMTime.zero, end: avAsset?.duration ?? CMTime.zero)
|
|
|
- if firstTrack != nil {
|
|
|
- let layRoateInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: firstTrack!)
|
|
|
- layRoateInstruction.setTransform(mixedTransform, at: CMTime.zero)
|
|
|
- roateInstruction.layerInstructions = [layRoateInstruction]
|
|
|
- videoComposition.instructions = [roateInstruction]
|
|
|
- avAssetExportSession?.videoComposition = videoComposition
|
|
|
- } else {
|
|
|
- BFLog(message: "firstTrack is error !!!")
|
|
|
- }
|
|
|
- }
|
|
|
- avAssetExportSession?.exportAsynchronously(completionHandler: {
|
|
|
- BFLog(message: "导出相册视频-progress = \(avAssetExportSession?.progress ?? 0),status = \(String(describing: avAssetExportSession?.status))")
|
|
|
- switch avAssetExportSession?.status {
|
|
|
- case .unknown:
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, nil, avAssetExportSession?.error?.localizedDescription)
|
|
|
- }
|
|
|
- avAssetExportSession?.cancelExport()
|
|
|
- PQSingletoMemoryUtil.shared.allExportSession.removeValue(forKey: phAsset)
|
|
|
- BFLog(message: "导出相册视频-发生未知错误:\(filePath),\(avAssetExportSession?.error?.localizedDescription ?? "")")
|
|
|
- case .waiting:
|
|
|
- BFLog(message: "导出相册视频-等待导出mp4:\(filePath)")
|
|
|
- case .exporting:
|
|
|
- BFLog(message: "导出相册视频-导出相册视频中...:\(filePath)")
|
|
|
- case .completed:
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, filePath, nil)
|
|
|
- }
|
|
|
- avAssetExportSession?.cancelExport()
|
|
|
- PQSingletoMemoryUtil.shared.allExportSession.removeValue(forKey: phAsset)
|
|
|
- BFLog(message: "导出相册视频-导出完成:\(filePath)")
|
|
|
- case .failed:
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, nil, avAssetExportSession?.error?.localizedDescription)
|
|
|
- }
|
|
|
- avAssetExportSession?.cancelExport()
|
|
|
- PQSingletoMemoryUtil.shared.allExportSession.removeValue(forKey: phAsset)
|
|
|
- BFLog(message: "导出相册视频-导出失败:\(filePath),\(avAssetExportSession?.error?.localizedDescription ?? "")")
|
|
|
- case .cancelled:
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, nil, avAssetExportSession?.error?.localizedDescription)
|
|
|
- }
|
|
|
- avAssetExportSession?.cancelExport()
|
|
|
- PQSingletoMemoryUtil.shared.allExportSession.removeValue(forKey: phAsset)
|
|
|
- BFLog(message: "导出相册视频-取消导出:\(filePath),\(avAssetExportSession?.error?.localizedDescription ?? "")")
|
|
|
- default:
|
|
|
- break
|
|
|
- }
|
|
|
- })
|
|
|
- })
|
|
|
- }
|
|
|
- } else if avAsset is AVComposition {
|
|
|
- BFLog(message: "导出相册视频-是AVComposition = \(String(describing: avAsset))")
|
|
|
- let assetResources = PHAssetResource.assetResources(for: phAsset)
|
|
|
- var resource: PHAssetResource?
|
|
|
- for assetRes in assetResources {
|
|
|
- if assetRes.type == .video || assetRes.type == .pairedVideo {
|
|
|
- resource = assetRes
|
|
|
- }
|
|
|
- }
|
|
|
- if phAsset.mediaType == .video, resource != nil {
|
|
|
- let fileName = (resource?.originalFilename ?? "") + (resource?.assetLocalIdentifier ?? "") + (resource?.uniformTypeIdentifier ?? "")
|
|
|
- let filePath = photoLibraryDirectory + fileName.md5 + ".mp4"
|
|
|
- let data = try? Data(contentsOf: NSURL.fileURL(withPath: filePath))
|
|
|
- if FileManager.default.fileExists(atPath: filePath) && (data?.count ?? 0) > fileSize / 40 {
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, filePath, nil)
|
|
|
- }
|
|
|
- } else {
|
|
|
- PHAssetResourceManager.default().writeData(for: resource!, toFile: URL(fileURLWithPath: filePath), options: nil) { error in
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, error == nil ? filePath : nil, nil)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, nil, nil)
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, nil, nil)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// PHAsset 转码为.mp4保存本地
|
|
|
- /// - Parameters:
|
|
|
- /// - phAsset: <#phAsset description#>
|
|
|
- /// - isAdjustRotationAngle: 是否调整旋转角度
|
|
|
- /// - resultHandler: <#resultHandler description#>
|
|
|
- /// - Returns: <#description#>
|
|
|
- public class func writePHAssetDataToMP4(phAsset: PHAsset, isAdjustRotationAngle _: Bool = true, isCancelCurrentExport: Bool = false, deliveryMode _: PHVideoRequestOptionsDeliveryMode? = .automatic, resultHandler: @escaping (_ phAsset: PHAsset, _ aVAsset: AVAsset?, _ filePath: String?, _ errorMsg: String?) -> Void) {
|
|
|
- BFLog(message: "导出相册视频-开始导出:phAsset = \(phAsset)")
|
|
|
- if isCancelCurrentExport {
|
|
|
- currentExportSession?.cancelExport()
|
|
|
- }
|
|
|
- PQPHAssetVideoParaseUtil.parasToAVAsset(phAsset: phAsset) { avAsset, fileSize, _, _ in
|
|
|
- if avAsset is AVURLAsset {
|
|
|
- // 创建目录
|
|
|
- createDirectory(path: photoLibraryDirectory)
|
|
|
- let fileName = (avAsset as! AVURLAsset).url.absoluteString
|
|
|
- let filePath = photoLibraryDirectory + fileName.md5 + ".mp4"
|
|
|
- let data = try? Data(contentsOf: NSURL.fileURL(withPath: filePath))
|
|
|
- if FileManager.default.fileExists(atPath: filePath) && (data?.count ?? 0) > fileSize / 40 {
|
|
|
- BFLog(message: "导出相册视频-已经导出完成:\(filePath)")
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, filePath, nil)
|
|
|
- }
|
|
|
- } else {
|
|
|
- let tempExportSession = PQSingletoMemoryUtil.shared.allExportSession[phAsset]
|
|
|
- if tempExportSession != nil {
|
|
|
- BFLog(message: "导出相册视频-正在导出")
|
|
|
- return
|
|
|
- }
|
|
|
- BFLog(message: "导出相册视频-未导出视频过,开始导出:phAsset = \(phAsset)")
|
|
|
- // 删除以创建地址
|
|
|
- if FileManager.default.fileExists(atPath: filePath) {
|
|
|
- do {
|
|
|
- try FileManager.default.removeItem(at: NSURL.fileURL(withPath: filePath))
|
|
|
- } catch {
|
|
|
- BFLog(message: "导出相册视频-error == \(error)")
|
|
|
- }
|
|
|
- }
|
|
|
- do {
|
|
|
- try FileManager.default.copyItem(at: (avAsset as! AVURLAsset).url, to: URL(fileURLWithPath: filePath))
|
|
|
- } catch {
|
|
|
- BFLog(message: "导出相册视频-error == \(error)")
|
|
|
- }
|
|
|
-
|
|
|
-// NSError *error;
|
|
|
-// AVURLAsset *avurlasset = (AVURLAsset*)asset;
|
|
|
-// NSURL *fileURL = [NSURL fileURLWithPath:savePath];
|
|
|
-//
|
|
|
-// if ([[NSFileManager defaultManager] copyItemAtURL:avurlasset.URL toURL:fileURL error:&error]) {
|
|
|
-// CBLog(@"保存成功");
|
|
|
-// dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
-// if (result) {
|
|
|
-// result(savePath,[savePath lastPathComponent]);
|
|
|
-// }
|
|
|
-// });
|
|
|
-// }else{
|
|
|
-// CBLog(@"error=%@",error);
|
|
|
-// [[NSFileManager defaultManager]removeItemAtPath:savePath error:nil];
|
|
|
-// dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
-// failure(error.description);
|
|
|
-// });
|
|
|
-// }
|
|
|
-// let requestOptions = PHVideoRequestOptions()
|
|
|
-// // 解决慢动作视频返回AVComposition而不是AVURLAsset
|
|
|
-// // videoRequestOptions.version = .original
|
|
|
-// requestOptions.version = .current
|
|
|
-// // 下载iCloud视频
|
|
|
-// requestOptions.isNetworkAccessAllowed = false
|
|
|
-// requestOptions.progressHandler = { progress, error, pointer, info in
|
|
|
-// BFLog(message: "导出相册视频-progress = \(progress),error = \(String(describing: error)),pointer = \(pointer),info = \(String(describing: info))")
|
|
|
-// }
|
|
|
-// requestOptions.deliveryMode = deliveryMode ?? .automatic
|
|
|
-
|
|
|
-// PHImageManager.default().requestExportSession(forVideo: phAsset, options: requestOptions, exportPreset: (deliveryMode == .automatic || deliveryMode == .mediumQualityFormat) ? AVAssetExportPreset1920x1080 :(deliveryMode == .highQualityFormat ? AVAssetExportPresetHighestQuality : AVAssetExportPresetLowQuality) , resultHandler: { avAssetExportSession, _ in
|
|
|
-// BFLog(message: "导出相册视频-请求到导出 avAssetExportSession = \(avAssetExportSession)")
|
|
|
-// currentExportSession = avAssetExportSession
|
|
|
-// if avAssetExportSession != nil {
|
|
|
-// PQSingletoMemoryUtil.shared.allExportSession[phAsset] = avAssetExportSession!
|
|
|
-// }
|
|
|
-// avAssetExportSession?.outputURL = NSURL(fileURLWithPath: filePath) as URL
|
|
|
-// avAssetExportSession?.shouldOptimizeForNetworkUse = true
|
|
|
-// avAssetExportSession?.outputFileType = .mp4
|
|
|
-// if isAdjustRotationAngle {
|
|
|
-// let rotationAngle = PQPHAssetVideoParaseUtil.videoRotationAngle(assert: avAsset!)
|
|
|
-// // mdf by ak 统一导出的视频为30FPS
|
|
|
-// var centerTranslate: CGAffineTransform = CGAffineTransform(translationX: 0, y: 0)
|
|
|
-// var mixedTransform: CGAffineTransform = CGAffineTransform()
|
|
|
-// let videoComposition = AVMutableVideoComposition()
|
|
|
-// videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
|
|
|
-// let tracks = avAsset?.tracks(withMediaType: .video)
|
|
|
-// let firstTrack = tracks?.first
|
|
|
-//
|
|
|
-// videoComposition.renderSize = CGSize(width: firstTrack?.naturalSize.width ?? 0, height: firstTrack?.naturalSize.height ?? 0)
|
|
|
-//
|
|
|
-// mixedTransform = centerTranslate.rotated(by: 0)
|
|
|
-//
|
|
|
-// if rotationAngle == 90 {
|
|
|
-// centerTranslate = CGAffineTransform(translationX: firstTrack?.naturalSize.height ?? 0, y: 0)
|
|
|
-// mixedTransform = centerTranslate.rotated(by: .pi / 2)
|
|
|
-// videoComposition.renderSize = CGSize(width: firstTrack?.naturalSize.height ?? 0, height: firstTrack?.naturalSize.width ?? 0)
|
|
|
-// } else if rotationAngle == 180 {
|
|
|
-// centerTranslate = CGAffineTransform(translationX: firstTrack?.naturalSize.width ?? 0, y: firstTrack?.naturalSize.height ?? 0)
|
|
|
-// mixedTransform = centerTranslate.rotated(by: .pi)
|
|
|
-// videoComposition.renderSize = CGSize(width: firstTrack?.naturalSize.width ?? 0, height: firstTrack?.naturalSize.height ?? 0)
|
|
|
-// } else if rotationAngle == 270 {
|
|
|
-// centerTranslate = CGAffineTransform(translationX: 0, y: firstTrack?.naturalSize.width ?? 0)
|
|
|
-// mixedTransform = centerTranslate.rotated(by: .pi / 2 * 3)
|
|
|
-// videoComposition.renderSize = CGSize(width: firstTrack?.naturalSize.height ?? 0, height: firstTrack?.naturalSize.width ?? 0)
|
|
|
-// }
|
|
|
-// let roateInstruction = AVMutableVideoCompositionInstruction()
|
|
|
-// roateInstruction.timeRange = CMTimeRange(start: CMTime.zero, end: avAsset?.duration ?? CMTime.zero)
|
|
|
-// if firstTrack != nil {
|
|
|
-// let layRoateInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: firstTrack!)
|
|
|
-// layRoateInstruction.setTransform(mixedTransform, at: CMTime.zero)
|
|
|
-// roateInstruction.layerInstructions = [layRoateInstruction]
|
|
|
-// videoComposition.instructions = [roateInstruction]
|
|
|
-// avAssetExportSession?.videoComposition = videoComposition
|
|
|
-// } else {
|
|
|
-// BFLog(message: "firstTrack is error !!!")
|
|
|
-// }
|
|
|
-// }
|
|
|
-// avAssetExportSession?.shouldOptimizeForNetworkUse = true
|
|
|
-// avAssetExportSession?.exportAsynchronously(completionHandler: {
|
|
|
-// BFLog(message: "导出相册视频-progress = \(avAssetExportSession?.progress ?? 0),status = \(String(describing: avAssetExportSession?.status))")
|
|
|
-// switch avAssetExportSession?.status {
|
|
|
-// case .unknown:
|
|
|
-// DispatchQueue.main.async {
|
|
|
-// resultHandler(phAsset, avAsset, nil, avAssetExportSession?.error?.localizedDescription)
|
|
|
-// }
|
|
|
-// avAssetExportSession?.cancelExport()
|
|
|
-// PQSingletoMemoryUtil.shared.allExportSession.removeValue(forKey: phAsset)
|
|
|
-// BFLog(message: "导出相册视频-发生未知错误:\(filePath),\(avAssetExportSession?.error?.localizedDescription ?? "")")
|
|
|
-// case .waiting:
|
|
|
-// BFLog(message: "导出相册视频-等待导出mp4:\(filePath)")
|
|
|
-// case .exporting:
|
|
|
-// BFLog(message: "导出相册视频-导出相册视频中...:\(filePath)")
|
|
|
-// case .completed:
|
|
|
-// DispatchQueue.main.async {
|
|
|
-// resultHandler(phAsset, avAsset, filePath, nil)
|
|
|
-// }
|
|
|
-// avAssetExportSession?.cancelExport()
|
|
|
-// PQSingletoMemoryUtil.shared.allExportSession.removeValue(forKey: phAsset)
|
|
|
-// BFLog(message: "导出相册视频-导出完成:\(filePath)")
|
|
|
-// case .failed:
|
|
|
-// DispatchQueue.main.async {
|
|
|
-// resultHandler(phAsset, avAsset, nil, avAssetExportSession?.error?.localizedDescription)
|
|
|
-// }
|
|
|
-// avAssetExportSession?.cancelExport()
|
|
|
-// PQSingletoMemoryUtil.shared.allExportSession.removeValue(forKey: phAsset)
|
|
|
-// BFLog(message: "导出相册视频-导出失败:\(filePath),\(avAssetExportSession?.error?.localizedDescription ?? "")")
|
|
|
-// case .cancelled:
|
|
|
-// DispatchQueue.main.async {
|
|
|
-// resultHandler(phAsset, avAsset, nil, avAssetExportSession?.error?.localizedDescription)
|
|
|
-// }
|
|
|
-// avAssetExportSession?.cancelExport()
|
|
|
-// PQSingletoMemoryUtil.shared.allExportSession.removeValue(forKey: phAsset)
|
|
|
-// BFLog(message: "导出相册视频-取消导出:\(filePath),\(avAssetExportSession?.error?.localizedDescription ?? "")")
|
|
|
-// default:
|
|
|
-// break
|
|
|
-// }
|
|
|
-// })
|
|
|
-// })
|
|
|
- }
|
|
|
- } else if avAsset is AVComposition {
|
|
|
- BFLog(message: "导出相册视频-是AVComposition = \(String(describing: avAsset))")
|
|
|
- let assetResources = PHAssetResource.assetResources(for: phAsset)
|
|
|
- var resource: PHAssetResource?
|
|
|
- for assetRes in assetResources {
|
|
|
- if assetRes.type == .video || assetRes.type == .pairedVideo {
|
|
|
- resource = assetRes
|
|
|
- }
|
|
|
- }
|
|
|
- if phAsset.mediaType == .video, resource != nil {
|
|
|
- let fileName = (resource?.originalFilename ?? "") + (resource?.assetLocalIdentifier ?? "") + (resource?.uniformTypeIdentifier ?? "")
|
|
|
- let filePath = photoLibraryDirectory + fileName.md5 + ".mp4"
|
|
|
- let data = try? Data(contentsOf: NSURL.fileURL(withPath: filePath))
|
|
|
- if FileManager.default.fileExists(atPath: filePath) && (data?.count ?? 0) > fileSize / 40 {
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, filePath, nil)
|
|
|
- }
|
|
|
- } else {
|
|
|
- PHAssetResourceManager.default().writeData(for: resource!, toFile: URL(fileURLWithPath: filePath), options: nil) { error in
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, error == nil ? filePath : nil, nil)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, nil, nil)
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(phAsset, avAsset, nil, nil)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// 导出相册视频
|
|
|
- /// - Parameters:
|
|
|
- /// - aVAsset: <#aVAsset description#>
|
|
|
- /// - isAdjustRotationAngle: <#isAdjustRotationAngle description#>
|
|
|
- /// - resultHandler: <#resultHandler description#>
|
|
|
- public class func exportAVAssetToMP4(aVAsset: AVURLAsset, isAdjustRotationAngle: Bool = true, resultHandler: @escaping (_ aVAsset: AVURLAsset?, _ filePath: String?, _ errorMsg: String?) -> Void) {
|
|
|
- currentExportSession?.cancelExport()
|
|
|
- BFLog(message: "开始导出相册视频:url = \(aVAsset.url.absoluteString)")
|
|
|
- // 创建目录
|
|
|
- createDirectory(path: photoLibraryDirectory)
|
|
|
- let fileName = aVAsset.url.absoluteString
|
|
|
- let filePath = photoLibraryDirectory + fileName.md5 + ".mp4"
|
|
|
- let data = try? Data(contentsOf: NSURL.fileURL(withPath: filePath))
|
|
|
- let fileSize = try! aVAsset.url.resourceValues(forKeys: [.fileSizeKey]).fileSize ?? 0
|
|
|
- if FileManager.default.fileExists(atPath: filePath) && (data?.count ?? 0) > fileSize / 40 {
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(aVAsset, filePath, nil)
|
|
|
- }
|
|
|
- } else {
|
|
|
- BFLog(message: "未导出视频过,开始导出:aVAsset = \(aVAsset)")
|
|
|
- // 删除以创建地址
|
|
|
- try? FileManager.default.removeItem(at: NSURL.fileURL(withPath: filePath))
|
|
|
- let avAssetExportSession = AVAssetExportSession(asset: aVAsset, presetName: AVAssetExportPreset1280x720)
|
|
|
- currentExportSession = avAssetExportSession
|
|
|
- avAssetExportSession?.outputURL = NSURL(fileURLWithPath: filePath) as URL
|
|
|
- avAssetExportSession?.shouldOptimizeForNetworkUse = false
|
|
|
- avAssetExportSession?.outputFileType = .mp4
|
|
|
- if isAdjustRotationAngle {
|
|
|
- let rotationAngle = PQPHAssetVideoParaseUtil.videoRotationAngle(assert: aVAsset)
|
|
|
- // mdf by ak 统一导出的视频为30FPS
|
|
|
- var centerTranslate: CGAffineTransform = CGAffineTransform(translationX: 0, y: 0)
|
|
|
- var mixedTransform: CGAffineTransform = CGAffineTransform()
|
|
|
- let videoComposition = AVMutableVideoComposition()
|
|
|
- videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
|
|
|
- let tracks = aVAsset.tracks(withMediaType: .video)
|
|
|
- let firstTrack = tracks.first
|
|
|
- videoComposition.renderSize = CGSize(width: firstTrack?.naturalSize.width ?? 0, height: firstTrack?.naturalSize.height ?? 0)
|
|
|
-
|
|
|
- mixedTransform = centerTranslate.rotated(by: 0)
|
|
|
-
|
|
|
- if rotationAngle == 90 {
|
|
|
- centerTranslate = CGAffineTransform(translationX: firstTrack?.naturalSize.height ?? 0, y: 0)
|
|
|
- mixedTransform = centerTranslate.rotated(by: .pi / 2)
|
|
|
- videoComposition.renderSize = CGSize(width: firstTrack?.naturalSize.height ?? 0, height: firstTrack?.naturalSize.width ?? 0)
|
|
|
- } else if rotationAngle == 180 {
|
|
|
- centerTranslate = CGAffineTransform(translationX: firstTrack?.naturalSize.width ?? 0, y: firstTrack?.naturalSize.height ?? 0)
|
|
|
- mixedTransform = centerTranslate.rotated(by: .pi)
|
|
|
- videoComposition.renderSize = CGSize(width: firstTrack?.naturalSize.width ?? 0, height: firstTrack?.naturalSize.height ?? 0)
|
|
|
- } else if rotationAngle == 270 {
|
|
|
- centerTranslate = CGAffineTransform(translationX: 0, y: firstTrack?.naturalSize.width ?? 0)
|
|
|
- mixedTransform = centerTranslate.rotated(by: .pi / 2 * 3)
|
|
|
- videoComposition.renderSize = CGSize(width: firstTrack?.naturalSize.height ?? 0, height: firstTrack?.naturalSize.width ?? 0)
|
|
|
- }
|
|
|
- let roateInstruction = AVMutableVideoCompositionInstruction()
|
|
|
- roateInstruction.timeRange = CMTimeRange(start: CMTime.zero, end: aVAsset.duration)
|
|
|
- let layRoateInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: firstTrack!)
|
|
|
- layRoateInstruction.setTransform(mixedTransform, at: CMTime.zero)
|
|
|
- roateInstruction.layerInstructions = [layRoateInstruction]
|
|
|
- videoComposition.instructions = [roateInstruction]
|
|
|
- avAssetExportSession?.videoComposition = videoComposition
|
|
|
- }
|
|
|
-
|
|
|
- avAssetExportSession?.shouldOptimizeForNetworkUse = true
|
|
|
- avAssetExportSession?.exportAsynchronously(completionHandler: {
|
|
|
- BFLog(message: "导出相册视频progress = \(avAssetExportSession?.progress ?? 0)")
|
|
|
- switch avAssetExportSession?.status {
|
|
|
- case .unknown:
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(aVAsset, nil, avAssetExportSession?.error?.localizedDescription)
|
|
|
- }
|
|
|
- BFLog(message: "导出相册视频发生未知错误:\(filePath),\(avAssetExportSession?.error?.localizedDescription ?? "")")
|
|
|
- case .waiting:
|
|
|
- BFLog(message: "等待导出mp4:\(filePath)")
|
|
|
- case .exporting:
|
|
|
- BFLog(message: "导出相册视频中...:\(filePath)")
|
|
|
- case .completed:
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(aVAsset, filePath, nil)
|
|
|
- }
|
|
|
- BFLog(message: "导出相册视频完成:\(filePath)")
|
|
|
- case .failed:
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(aVAsset, nil, avAssetExportSession?.error?.localizedDescription)
|
|
|
- }
|
|
|
- BFLog(message: "导出相册视频失败:\(filePath),\(avAssetExportSession?.error?.localizedDescription ?? "")")
|
|
|
- case .cancelled:
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(aVAsset, nil, avAssetExportSession?.error?.localizedDescription)
|
|
|
- }
|
|
|
- BFLog(message: "取消导出相册视频:\(filePath),\(avAssetExportSession?.error?.localizedDescription ?? "")")
|
|
|
- default:
|
|
|
- break
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// 获取视频资源的旋转角度
|
|
|
- /// - Parameter assert: <#assert description#>
|
|
|
- /// - Returns: <#description#>
|
|
|
- public class func videoRotationAngle(assert: AVAsset) -> Int {
|
|
|
- var rotationAngle: Int = 0
|
|
|
-
|
|
|
- let tracks = assert.tracks(withMediaType: .video)
|
|
|
- if tracks.count > 0 {
|
|
|
- let firstTrack = tracks.first
|
|
|
- let transform = firstTrack?.preferredTransform
|
|
|
- if transform?.a == 0, transform?.b == 1.0, transform?.c == -1.0, transform?.d == 0 {
|
|
|
- rotationAngle = 90
|
|
|
- } else if transform?.a == -1.0, transform?.b == 0, transform?.c == 0, transform?.d == -1.0 {
|
|
|
- rotationAngle = 180
|
|
|
- } else if transform?.a == 0, transform?.b == -1.0, transform?.c == 1.0, transform?.d == 0 {
|
|
|
- rotationAngle = 270
|
|
|
- } else if transform?.a == 1.0, transform?.b == 0, transform?.c == 0, transform?.d == 1.0 {
|
|
|
- rotationAngle = 0
|
|
|
- }
|
|
|
- }
|
|
|
- return rotationAngle
|
|
|
- }
|
|
|
-
|
|
|
- /// 裁剪背景音乐并导出
|
|
|
- /// - Parameters:
|
|
|
- /// - url: 原始地址
|
|
|
- /// - startTime: 开始时间
|
|
|
- /// - endTime: 结束时间
|
|
|
- /// - resultHandler: <#resultHandler description#>
|
|
|
- /// - Returns: <#description#>
|
|
|
- public class func cutAudioToLocal(url: String, startTime: Float, endTime: Float, resultHandler: @escaping (_ url: String, _ filePath: String?, _ startTime: Float, _ endTime: Float, _ errorMsg: String?) -> Void) {
|
|
|
- // 创建目录
|
|
|
- createDirectory(path: bgMusicDirectory)
|
|
|
- let filePath = bgMusicDirectory + url.md5 + ".mp3"
|
|
|
- let data = try? Data(contentsOf: NSURL.fileURL(withPath: filePath))
|
|
|
- if FileManager.default.fileExists(atPath: filePath) && (data?.count ?? 0) > 0 {
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(url, filePath, startTime, endTime, nil)
|
|
|
- }
|
|
|
- } else {
|
|
|
- // 删除以创建地址
|
|
|
- try? FileManager.default.removeItem(at: NSURL.fileURL(withPath: filePath))
|
|
|
- let audioAsset = AVURLAsset(url: URL(string: url)!)
|
|
|
- audioAsset.loadValuesAsynchronously(forKeys: ["duration", "tracks"]) {
|
|
|
- let status = audioAsset.statusOfValue(forKey: "tracks", error: nil)
|
|
|
- switch status {
|
|
|
- case .loaded: // 加载完成
|
|
|
- // AVAssetExportPresetPassthrough /AVAssetExportPresetAppleM4A
|
|
|
- let exportSession = AVAssetExportSession(asset: audioAsset, presetName: AVAssetExportPresetHighestQuality)
|
|
|
- exportSession?.outputURL = URL(fileURLWithPath: filePath)
|
|
|
- exportSession?.outputFileType = .mp3
|
|
|
- exportSession?.timeRange = CMTimeRange(start: CMTime(seconds: Double(startTime), preferredTimescale: 1000), end: CMTime(seconds: Double(endTime), preferredTimescale: 1000))
|
|
|
- exportSession?.exportAsynchronously(completionHandler: {
|
|
|
- switch exportSession?.status {
|
|
|
- case .waiting:
|
|
|
- BFLog(message: "等待导出mp3:\(filePath)")
|
|
|
- case .exporting:
|
|
|
- BFLog(message: "导出中...:\(filePath)")
|
|
|
- case .completed:
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(url, filePath, startTime, endTime, nil)
|
|
|
- }
|
|
|
- BFLog(message: "导出完成:\(filePath)")
|
|
|
-
|
|
|
- case .cancelled, .failed, .unknown:
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(url, nil, startTime, endTime, exportSession?.error?.localizedDescription)
|
|
|
- }
|
|
|
- BFLog(message: "导出失败:\(filePath),\(exportSession?.error?.localizedDescription ?? "")")
|
|
|
- default:
|
|
|
- break
|
|
|
- }
|
|
|
- })
|
|
|
- case .loading:
|
|
|
- BFLog(message: "加载中...:\(url)")
|
|
|
- case .failed, .cancelled, .unknown:
|
|
|
- DispatchQueue.main.async {
|
|
|
- resultHandler(url, nil, startTime, endTime, "导出失败")
|
|
|
- }
|
|
|
- default:
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// 创建本地保存地址
|
|
|
- /// - Parameters:
|
|
|
- /// - sourceFilePath: <#sourceFilePath description#>
|
|
|
- /// - completeHandle: <#completeHandle description#>
|
|
|
- /// - Returns: <#description#>
|
|
|
- public class func createLocalFile(sourceFilePath: String, completeHandle: (_ isFileExists: Bool, _ isCreateSuccess: Bool, _ filePath: String) -> Void) {
|
|
|
- let cLocalPath = NSString(string: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!).appendingPathComponent("\(sourceFilePath.md5).mp4")
|
|
|
- if FileManager.default.fileExists(atPath: cLocalPath) {
|
|
|
- BFLog(message: "文件已经存在:\(cLocalPath)")
|
|
|
- completeHandle(true, false, cLocalPath)
|
|
|
- } else {
|
|
|
- let result = FileManager.default.createFile(atPath: cLocalPath, contents: nil, attributes: nil)
|
|
|
- BFLog(message: "文件创建:\(cLocalPath),\(result)")
|
|
|
- completeHandle(false, result, cLocalPath)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// 获取图库图片
|
|
|
- /// - Parameters:
|
|
|
- /// - asset: <#asset description#>
|
|
|
- /// - itemSize: <#itemSize description#>
|
|
|
- /// - resultHandler: <#resultHandler description#>
|
|
|
- /// - Returns: <#description#>
|
|
|
- public class func requestAssetImage(asset: PHAsset, itemSize: CGSize, resultHandler: @escaping (UIImage?, [AnyHashable: Any]?) -> Void) {
|
|
|
- PHCachingImageManager().requestImage(for: asset, targetSize: itemSize, contentMode: .aspectFill, options: imagesOptions, resultHandler: { image, info in
|
|
|
- BFLog(message: "info = \(info ?? [:])")
|
|
|
- if info?.keys.contains("PHImageResultIsDegradedKey") ?? false, "\(info?["PHImageResultIsDegradedKey"] ?? "0")" == "0" {
|
|
|
- resultHandler(image, info)
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- /// 获取图库原图
|
|
|
- /// - Parameters:
|
|
|
- /// - asset: <#asset description#>
|
|
|
- /// - resultHandler: <#resultHandler description#>
|
|
|
- /// - Returns: <#description#>
|
|
|
- public class func requestAssetOringinImage(asset: PHAsset, resultHandler: @escaping (_ isGIF: Bool, _ data: Data?, UIImage?, [AnyHashable: Any]?) -> Void) {
|
|
|
- PHCachingImageManager().requestImageData(for: asset, options: singleImageOptions) { data, _, _, info in
|
|
|
- var image: UIImage?
|
|
|
- if data != nil {
|
|
|
- image = UIImage(data: data!)
|
|
|
- }
|
|
|
- if info?.keys.contains("PHImageFileUTIKey") ?? false, "\(info?["PHImageFileUTIKey"] ?? "")" == "com.compuserve.gif" {
|
|
|
- resultHandler(true, data, image, info)
|
|
|
- } else {
|
|
|
- resultHandler(false, data, image, info)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// 获取gif帧跟时长
|
|
|
- /// - Parameters:
|
|
|
- /// - data: <#data description#>
|
|
|
- /// - isRenderingTemplate
|
|
|
- /// - resultHandler: <#resultHandler description#>
|
|
|
- /// - Returns: <#description#>
|
|
|
- public class func parasGIFImage(data: Data, isRenderingColor: UIColor? = nil, resultHandler: @escaping (_ data: Data, _ images: [UIImage]?, _ duration: Double?) -> Void) {
|
|
|
- let info: [String: Any] = [
|
|
|
- kCGImageSourceShouldCache as String: true,
|
|
|
- kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF,
|
|
|
- ]
|
|
|
- guard let imageSource = CGImageSourceCreateWithData(data as CFData, info as CFDictionary) else {
|
|
|
- resultHandler(data, nil, nil)
|
|
|
- BFLog(message: "获取gifimageSource 失败")
|
|
|
- return
|
|
|
- }
|
|
|
- // 获取帧数
|
|
|
- let frameCount = CGImageSourceGetCount(imageSource)
|
|
|
- var gifDuration = 0.0
|
|
|
- var images = [UIImage]()
|
|
|
- for i in 0 ..< frameCount {
|
|
|
- // 取出索引对应的图片
|
|
|
- guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, info as CFDictionary) else {
|
|
|
- BFLog(message: "取出对应的图片失败")
|
|
|
- return
|
|
|
- }
|
|
|
- if frameCount == 1 {
|
|
|
- // 单帧
|
|
|
- gifDuration = .infinity
|
|
|
- } else {
|
|
|
- // 1.获取gif没帧的时间间隔
|
|
|
- // 获取到该帧图片的属性字典
|
|
|
- guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil) as? [String: Any] else {
|
|
|
- BFLog(message: "取出对应的图片属性失败")
|
|
|
- return
|
|
|
- }
|
|
|
- // 获取该帧图片中的GIF相关的属性字典
|
|
|
- guard let gifInfo = properties[kCGImagePropertyGIFDictionary as String] as? [String: Any] else {
|
|
|
- BFLog(message: "取出对应的图片属性失败")
|
|
|
- return
|
|
|
- }
|
|
|
- let defaultFrameDuration = 0.1
|
|
|
- // 获取该帧图片的播放时间
|
|
|
- let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as? NSNumber
|
|
|
- // 如果通过kCGImagePropertyGIFUnclampedDelayTime没有获取到播放时长,就通过kCGImagePropertyGIFDelayTime来获取,两者的含义是相同的;
|
|
|
- let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber
|
|
|
- let duration = unclampedDelayTime ?? delayTime
|
|
|
- guard let frameDuration = duration else {
|
|
|
- BFLog(message: "获取帧时间间隔失败")
|
|
|
- return
|
|
|
- }
|
|
|
- // 对于播放时间低于0.011s的,重新指定时长为0.100s;
|
|
|
- let gifFrameDuration = frameDuration.doubleValue > 0.011 ? frameDuration.doubleValue : defaultFrameDuration
|
|
|
- // 计算总时间
|
|
|
- gifDuration += gifFrameDuration
|
|
|
- // 2.图片
|
|
|
- var frameImage: UIImage? = UIImage(cgImage: imageRef, scale: 1.0, orientation: .up)
|
|
|
- if isRenderingColor != nil {
|
|
|
- frameImage = frameImage?.tintImage(color: isRenderingColor!, blendMode: .destinationIn)
|
|
|
- }
|
|
|
- if frameImage != nil {
|
|
|
- images.append(frameImage!)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- resultHandler(data, images, gifDuration)
|
|
|
- }
|
|
|
-}
|