//
//  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]
                    let tempExportSession: AVAssetExportSession? = nil
                    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]
                    let tempExportSession: AVAssetExportSession? = nil
                    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)
    }
}
