123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- //
- // TLAssetsCollection.swift
- // TLPhotosPicker
- //
- // Created by wade.hawk on 2017. 4. 18..
- // Copyright © 2017년 wade.hawk. All rights reserved.
- //
- import Foundation
- import Photos
- import PhotosUI
- import MobileCoreServices
- public struct TLPHAsset {
- enum CloudDownloadState {
- case ready, progress, complete, failed
- }
-
- public enum AssetType {
- case photo, video, livePhoto
- }
-
- public enum ImageExtType: String {
- case png, jpg, gif, heic
- }
-
- var state = CloudDownloadState.ready
- public var phAsset: PHAsset? = nil
- public var selectedOrder: Int = 0
- public var type: AssetType {
- get {
- guard let phAsset = self.phAsset else { return .photo }
- if phAsset.mediaSubtypes.contains(.photoLive) {
- return .livePhoto
- }else if phAsset.mediaType == .video {
- return .video
- }else {
- return .photo
- }
- }
- }
-
- public var fullResolutionImage: UIImage? {
- get {
- guard let phAsset = self.phAsset else { return nil }
- return TLPhotoLibrary.fullResolutionImageData(asset: phAsset)
- }
- }
-
- public func extType() -> ImageExtType {
- var ext = ImageExtType.png
- if let fileName = self.originalFileName, let extention = URL(string: fileName)?.pathExtension.lowercased() {
- ext = ImageExtType(rawValue: extention) ?? .png
- }
- return ext
- }
-
- @discardableResult
- public func cloudImageDownload(progressBlock: @escaping (Double) -> Void, completionBlock:@escaping (UIImage?)-> Void ) -> PHImageRequestID? {
- guard let phAsset = self.phAsset else { return nil }
- return TLPhotoLibrary.cloudImageDownload(asset: phAsset, progressBlock: progressBlock, completionBlock: completionBlock)
- }
-
- public var originalFileName: String? {
- get {
- guard let phAsset = self.phAsset,let resource = PHAssetResource.assetResources(for: phAsset).first else { return nil }
- return resource.originalFilename
- }
- }
-
- public func photoSize(options: PHImageRequestOptions? = nil ,completion: @escaping ((Int)->Void), livePhotoVideoSize: Bool = false) {
- guard let phAsset = self.phAsset, self.type == .photo else { completion(-1); return }
- var resource: PHAssetResource? = nil
- if phAsset.mediaSubtypes.contains(.photoLive) == true, livePhotoVideoSize {
- resource = PHAssetResource.assetResources(for: phAsset).filter { $0.type == .pairedVideo }.first
- }else {
- resource = PHAssetResource.assetResources(for: phAsset).filter { $0.type == .photo }.first
- }
- if let fileSize = resource?.value(forKey: "fileSize") as? Int {
- completion(fileSize)
- }else {
- PHImageManager.default().requestImageData(for: phAsset, options: nil) { (data, uti, orientation, info) in
- var fileSize = -1
- if let data = data {
- let bcf = ByteCountFormatter()
- bcf.countStyle = .file
- fileSize = data.count
- }
- DispatchQueue.main.async {
- completion(fileSize)
- }
- }
- }
- }
-
- public func videoSize(options: PHVideoRequestOptions? = nil, completion: @escaping ((Int)->Void)) {
- guard let phAsset = self.phAsset, self.type == .video else { completion(-1); return }
- let resource = PHAssetResource.assetResources(for: phAsset).filter { $0.type == .video }.first
- if let fileSize = resource?.value(forKey: "fileSize") as? Int {
- completion(fileSize)
- }else {
- PHImageManager.default().requestAVAsset(forVideo: phAsset, options: options) { (avasset, audioMix, info) in
- func fileSize(_ url: URL?) -> Int? {
- do {
- guard let fileSize = try url?.resourceValues(forKeys: [.fileSizeKey]).fileSize else { return nil }
- return fileSize
- }catch { return nil }
- }
- var url: URL? = nil
- if let urlAsset = avasset as? AVURLAsset {
- url = urlAsset.url
- }else if let sandboxKeys = info?["PHImageFileSandboxExtensionTokenKey"] as? String, let path = sandboxKeys.components(separatedBy: ";").last {
- url = URL(fileURLWithPath: path)
- }
- let size = fileSize(url) ?? -1
- DispatchQueue.main.async {
- completion(size)
- }
- }
- }
- }
-
- func MIMEType(_ url: URL?) -> String? {
- guard let ext = url?.pathExtension else { return nil }
- if !ext.isEmpty {
- let UTIRef = UTTypeCreatePreferredIdentifierForTag("public.filename-extension" as CFString, ext as CFString, nil)
- let UTI = UTIRef?.takeUnretainedValue()
- UTIRef?.release()
- if let UTI = UTI {
- guard let MIMETypeRef = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType) else { return nil }
- let MIMEType = MIMETypeRef.takeUnretainedValue()
- MIMETypeRef.release()
- return MIMEType as String
- }
- }
- return nil
- }
-
- @discardableResult
- //convertLivePhotosToPNG
- // false : If you want mov file at live photos
- // true : If you want png file at live photos ( HEIC )
- public func tempCopyMediaFile(videoRequestOptions: PHVideoRequestOptions? = nil, imageRequestOptions: PHImageRequestOptions? = nil, exportPreset: String = AVAssetExportPresetHighestQuality, convertLivePhotosToJPG: Bool = false, progressBlock:((Double) -> Void)? = nil, completionBlock:@escaping ((URL,String) -> Void)) -> PHImageRequestID? {
- guard let phAsset = self.phAsset else { return nil }
- var type: PHAssetResourceType? = nil
- if phAsset.mediaSubtypes.contains(.photoLive) == true, convertLivePhotosToJPG == false {
- type = .pairedVideo
- }else {
- type = phAsset.mediaType == .video ? .video : .photo
- }
- guard let resource = (PHAssetResource.assetResources(for: phAsset).filter{ $0.type == type }).first else { return nil }
- let fileName = resource.originalFilename
- var writeURL: URL? = nil
- if #available(iOS 10.0, *) {
- writeURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(fileName)")
- } else {
- writeURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("\(fileName)")
- }
- if (writeURL?.pathExtension.uppercased() == "HEIC" || writeURL?.pathExtension.uppercased() == "HEIF") && convertLivePhotosToJPG {
- if let fileName2 = writeURL?.deletingPathExtension().lastPathComponent {
- writeURL?.deleteLastPathComponent()
- writeURL?.appendPathComponent("\(fileName2).jpg")
- }
- }
- guard let localURL = writeURL,let mimetype = MIMEType(writeURL) else { return nil }
- switch phAsset.mediaType {
- case .video:
- var requestOptions = PHVideoRequestOptions()
- if let options = videoRequestOptions {
- requestOptions = options
- }else {
- requestOptions.isNetworkAccessAllowed = true
- }
- //iCloud download progress
- requestOptions.progressHandler = { (progress, error, stop, info) in
- DispatchQueue.main.async {
- progressBlock?(progress)
- }
- }
- return PHImageManager.default().requestExportSession(forVideo: phAsset, options: requestOptions, exportPreset: exportPreset) { (session, infoDict) in
- session?.outputURL = localURL
- session?.outputFileType = AVFileType.mov
- session?.exportAsynchronously(completionHandler: {
- DispatchQueue.main.async {
- completionBlock(localURL, mimetype)
- }
- })
- }
- case .image:
- var requestOptions = PHImageRequestOptions()
- if let options = imageRequestOptions {
- requestOptions = options
- }else {
- requestOptions.isNetworkAccessAllowed = true
- }
- //iCloud download progress
- requestOptions.progressHandler = { (progress, error, stop, info) in
- DispatchQueue.main.async {
- progressBlock?(progress)
- }
- }
- return PHImageManager.default().requestImageData(for: phAsset, options: requestOptions, resultHandler: { (data, uti, orientation, info) in
- do {
- var data = data
- if convertLivePhotosToJPG == true, let imgData = data, let rawImage = UIImage(data: imgData)?.upOrientationImage() {
- data = rawImage.jpegData(compressionQuality: 1)
- }
- try data?.write(to: localURL)
- DispatchQueue.main.async {
- completionBlock(localURL, mimetype)
- }
- }catch { }
- })
- default:
- return nil
- }
- }
-
- //Apparently, this method is not be safety to export a video.
- //There is many way that export a video.
- //This method was one of them.
- public func exportVideoFile(options: PHVideoRequestOptions? = nil, progressBlock:((Float) -> Void)? = nil, completionBlock:@escaping ((URL,String) -> Void)) {
- guard let phAsset = self.phAsset, phAsset.mediaType == .video else { return }
- var type = PHAssetResourceType.video
- guard let resource = (PHAssetResource.assetResources(for: phAsset).filter{ $0.type == type }).first else { return }
- let fileName = resource.originalFilename
- var writeURL: URL? = nil
- if #available(iOS 10.0, *) {
- writeURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(fileName)")
- } else {
- writeURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent("\(fileName)")
- }
- guard let localURL = writeURL,let mimetype = MIMEType(writeURL) else { return }
- var requestOptions = PHVideoRequestOptions()
- if let options = options {
- requestOptions = options
- }else {
- requestOptions.isNetworkAccessAllowed = true
- }
- //iCloud download progress
- //options.progressHandler = { (progress, error, stop, info) in
-
- //}
- PHImageManager.default().requestAVAsset(forVideo: phAsset, options: options) { (avasset, avaudioMix, infoDict) in
- guard let avasset = avasset else { return }
- let exportSession = AVAssetExportSession.init(asset: avasset, presetName: AVAssetExportPresetHighestQuality)
- exportSession?.outputURL = localURL
- exportSession?.outputFileType = AVFileType.mov
- exportSession?.exportAsynchronously(completionHandler: {
- completionBlock(localURL,mimetype)
- })
- func checkExportSession() {
- DispatchQueue.global().async { [weak exportSession] in
- guard let exportSession = exportSession else { return }
- switch exportSession.status {
- case .waiting,.exporting:
- DispatchQueue.main.async {
- progressBlock?(exportSession.progress)
- }
- Thread.sleep(forTimeInterval: 1)
- checkExportSession()
- default:
- break
- }
- }
- }
- checkExportSession()
- }
- }
-
- init(asset: PHAsset?) {
- self.phAsset = asset
- }
- }
- extension TLPHAsset: Equatable {
- public static func ==(lhs: TLPHAsset, rhs: TLPHAsset) -> Bool {
- guard let lphAsset = lhs.phAsset, let rphAsset = rhs.phAsset else { return false }
- return lphAsset.localIdentifier == rphAsset.localIdentifier
- }
- }
- struct TLAssetsCollection {
- var phAssetCollection: PHAssetCollection? = nil
- var fetchResult: PHFetchResult<PHAsset>? = nil
- var useCameraButton: Bool = false
- var recentPosition: CGPoint = CGPoint.zero
- var title: String
- var localIdentifier: String
- var count: Int {
- get {
- guard let count = self.fetchResult?.count, count > 0 else { return self.useCameraButton ? 1 : 0 }
- return count + (self.useCameraButton ? 1 : 0)
- }
- }
-
- init(collection: PHAssetCollection) {
- self.phAssetCollection = collection
- self.title = collection.localizedTitle ?? ""
- self.localIdentifier = collection.localIdentifier
- }
-
- func getAsset(at index: Int) -> PHAsset? {
- if self.useCameraButton && index == 0 { return nil }
- let index = index - (self.useCameraButton ? 1 : 0)
- guard let result = self.fetchResult, index < result.count else { return nil }
- return result.object(at: max(index,0))
- }
-
- func getTLAsset(at index: Int) -> TLPHAsset? {
- if self.useCameraButton && index == 0 { return nil }
- let index = index - (self.useCameraButton ? 1 : 0)
- guard let result = self.fetchResult, index < result.count else { return nil }
- return TLPHAsset(asset: result.object(at: max(index,0)))
- }
-
- func getAssets(at range: CountableClosedRange<Int>) -> [PHAsset]? {
- let lowerBound = range.lowerBound - (self.useCameraButton ? 1 : 0)
- let upperBound = range.upperBound - (self.useCameraButton ? 1 : 0)
- return self.fetchResult?.objects(at: IndexSet(integersIn: max(lowerBound,0)...min(upperBound,count)))
- }
-
- static func ==(lhs: TLAssetsCollection, rhs: TLAssetsCollection) -> Bool {
- return lhs.localIdentifier == rhs.localIdentifier
- }
- }
- extension UIImage {
- func upOrientationImage() -> UIImage? {
- switch imageOrientation {
- case .up:
- return self
- default:
- UIGraphicsBeginImageContextWithOptions(size, false, scale)
- draw(in: CGRect(origin: .zero, size: size))
- let result = UIGraphicsGetImageFromCurrentImageContext()
- UIGraphicsEndImageContext()
- return result
- }
- }
- }
|