2021-03-18 08:16:35 +01:00
|
|
|
//
|
2021-07-19 11:12:45 +02:00
|
|
|
// ItemProviderLoader.swift
|
|
|
|
// MastodonUI
|
2021-03-18 08:16:35 +01:00
|
|
|
//
|
|
|
|
// Created by MainasuK Cirno on 2021-3-18.
|
|
|
|
//
|
|
|
|
|
|
|
|
import os.log
|
|
|
|
import Foundation
|
|
|
|
import Combine
|
|
|
|
import MobileCoreServices
|
|
|
|
import PhotosUI
|
2021-05-31 10:42:49 +02:00
|
|
|
import MastodonSDK
|
2021-03-18 08:16:35 +01:00
|
|
|
|
|
|
|
// load image with low memory usage
|
|
|
|
// Refs: https://christianselig.com/2020/09/phpickerviewcontroller-efficiently/
|
2021-07-19 11:12:45 +02:00
|
|
|
public enum ItemProviderLoader {
|
|
|
|
static let logger = Logger(subsystem: "ItemProviderLoader", category: "logic")
|
|
|
|
}
|
|
|
|
|
|
|
|
extension ItemProviderLoader {
|
|
|
|
|
|
|
|
public static func loadImageData(from result: PHPickerResult) -> Future<Mastodon.Query.MediaAttachment?, Error> {
|
|
|
|
loadImageData(from: result.itemProvider)
|
|
|
|
}
|
2021-03-18 08:16:35 +01:00
|
|
|
|
2021-07-19 11:12:45 +02:00
|
|
|
public static func loadImageData(from itemProvider: NSItemProvider) -> Future<Mastodon.Query.MediaAttachment?, Error> {
|
2021-03-18 08:16:35 +01:00
|
|
|
Future { promise in
|
2021-07-19 11:12:45 +02:00
|
|
|
itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in
|
2021-03-18 08:16:35 +01:00
|
|
|
if let error = error {
|
|
|
|
promise(.failure(error))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let url = url else {
|
|
|
|
promise(.success(nil))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
|
|
|
|
guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else {
|
|
|
|
return
|
|
|
|
}
|
2021-07-20 10:40:04 +02:00
|
|
|
|
|
|
|
#if APP_EXTENSION
|
|
|
|
let maxPixelSize: Int = 4096 // not limit but may upload fail
|
|
|
|
#else
|
|
|
|
let maxPixelSize: Int = 1536 // fit 120MB RAM limit
|
|
|
|
#endif
|
2021-03-18 08:16:35 +01:00
|
|
|
|
|
|
|
let downsampleOptions = [
|
|
|
|
kCGImageSourceCreateThumbnailFromImageAlways: true,
|
|
|
|
kCGImageSourceCreateThumbnailWithTransform: true,
|
2021-07-20 10:40:04 +02:00
|
|
|
kCGImageSourceThumbnailMaxPixelSize: maxPixelSize,
|
2021-03-18 08:16:35 +01:00
|
|
|
] as CFDictionary
|
|
|
|
|
|
|
|
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else {
|
2022-03-10 10:12:36 +01:00
|
|
|
// fallback to loadItem when create thumbnail failure
|
|
|
|
itemProvider.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) { image, error in
|
|
|
|
if let error = error {
|
|
|
|
promise(.failure(error))
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let image = image as? UIImage,
|
|
|
|
let data = image.jpegData(compressionQuality: 0.75)
|
|
|
|
else {
|
|
|
|
promise(.success(nil))
|
|
|
|
assertionFailure()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let file = Mastodon.Query.MediaAttachment.jpeg(data)
|
|
|
|
promise(.success(file))
|
|
|
|
|
|
|
|
} // end itemProvider.loadItem
|
2021-03-18 08:16:35 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let data = NSMutableData()
|
|
|
|
guard let imageDestination = CGImageDestinationCreateWithData(data, kUTTypeJPEG, 1, nil) else {
|
|
|
|
promise(.success(nil))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let isPNG: Bool = {
|
|
|
|
guard let utType = cgImage.utType else { return false }
|
|
|
|
return (utType as String) == UTType.png.identifier
|
|
|
|
}()
|
|
|
|
|
|
|
|
let destinationProperties = [
|
|
|
|
kCGImageDestinationLossyCompressionQuality: isPNG ? 1.0 : 0.75
|
|
|
|
] as CFDictionary
|
|
|
|
|
|
|
|
CGImageDestinationAddImage(imageDestination, cgImage, destinationProperties)
|
|
|
|
CGImageDestinationFinalize(imageDestination)
|
|
|
|
|
|
|
|
let dataSize = ByteCountFormatter.string(fromByteCount: Int64(data.length), countStyle: .memory)
|
2021-07-19 11:12:45 +02:00
|
|
|
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): load image \(dataSize)")
|
2021-03-18 08:16:35 +01:00
|
|
|
|
2021-05-31 10:42:49 +02:00
|
|
|
let file = Mastodon.Query.MediaAttachment.jpeg(data as Data)
|
|
|
|
promise(.success(file))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-19 11:12:45 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
extension ItemProviderLoader {
|
|
|
|
|
|
|
|
public static func loadVideoData(from result: PHPickerResult) -> Future<Mastodon.Query.MediaAttachment?, Error> {
|
|
|
|
loadVideoData(from: result.itemProvider)
|
|
|
|
}
|
|
|
|
|
|
|
|
public static func loadVideoData(from itemProvider: NSItemProvider) -> Future<Mastodon.Query.MediaAttachment?, Error> {
|
2021-05-31 10:42:49 +02:00
|
|
|
Future { promise in
|
2021-07-19 11:12:45 +02:00
|
|
|
itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in
|
2021-05-31 10:42:49 +02:00
|
|
|
if let error = error {
|
|
|
|
promise(.failure(error))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let url = url else {
|
|
|
|
promise(.success(nil))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let fileName = UUID().uuidString
|
|
|
|
let tempDirectoryURL = FileManager.default.temporaryDirectory
|
|
|
|
let fileURL = tempDirectoryURL.appendingPathComponent(fileName).appendingPathExtension(url.pathExtension)
|
|
|
|
do {
|
|
|
|
try FileManager.default.createDirectory(at: tempDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
|
|
|
try FileManager.default.copyItem(at: url, to: fileURL)
|
|
|
|
let file = Mastodon.Query.MediaAttachment.other(fileURL, fileExtension: fileURL.pathExtension, mimeType: UTType.movie.preferredMIMEType ?? "video/mp4")
|
|
|
|
promise(.success(file))
|
|
|
|
} catch {
|
|
|
|
promise(.failure(error))
|
|
|
|
}
|
2021-03-18 08:16:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|