2024-01-28 17:49:08 +01:00
|
|
|
//Made by Lumaa
|
|
|
|
|
|
|
|
import Foundation
|
2024-01-28 17:50:54 +01:00
|
|
|
import SwiftUI
|
|
|
|
import UIKit
|
|
|
|
import PhotosUI
|
|
|
|
|
|
|
|
public actor Compressor {
|
|
|
|
public init() { }
|
|
|
|
|
|
|
|
enum CompressorError: Error {
|
|
|
|
case noData
|
|
|
|
}
|
|
|
|
|
|
|
|
public func compressImageFrom(url: URL) async -> Data? {
|
|
|
|
await withCheckedContinuation { continuation in
|
|
|
|
let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
|
|
|
|
guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else {
|
|
|
|
continuation.resume(returning: nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let maxPixelSize: Int = if Bundle.main.bundlePath.hasSuffix(".appex") {
|
|
|
|
1536
|
|
|
|
} else {
|
|
|
|
4096
|
|
|
|
}
|
|
|
|
|
|
|
|
let downsampleOptions = [
|
|
|
|
kCGImageSourceCreateThumbnailFromImageAlways: true,
|
|
|
|
kCGImageSourceCreateThumbnailWithTransform: true,
|
|
|
|
kCGImageSourceThumbnailMaxPixelSize: maxPixelSize,
|
|
|
|
] as [CFString: Any] as CFDictionary
|
|
|
|
|
|
|
|
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else {
|
|
|
|
continuation.resume(returning: nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let data = NSMutableData()
|
|
|
|
guard let imageDestination = CGImageDestinationCreateWithData(data, UTType.jpeg.identifier as CFString, 1, nil) else {
|
|
|
|
continuation.resume(returning: 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)
|
|
|
|
|
|
|
|
continuation.resume(returning: data as Data)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public func compressImageForUpload(_ image: UIImage) async throws -> Data {
|
|
|
|
var image = image
|
|
|
|
if image.size.height > 5000 || image.size.width > 5000 {
|
|
|
|
image = image.resized(to: .init(width: image.size.width / 4,
|
|
|
|
height: image.size.height / 4))
|
|
|
|
}
|
|
|
|
|
|
|
|
guard var imageData = image.jpegData(compressionQuality: 0.8) else {
|
|
|
|
throw CompressorError.noData
|
|
|
|
}
|
|
|
|
|
|
|
|
let maxSize = 10 * 1024 * 1024
|
|
|
|
|
|
|
|
if imageData.count > maxSize {
|
|
|
|
while imageData.count > maxSize {
|
|
|
|
guard let compressedImage = UIImage(data: imageData),
|
|
|
|
let compressedData = compressedImage.jpegData(compressionQuality: 0.8)
|
|
|
|
else {
|
|
|
|
throw CompressorError.noData
|
|
|
|
}
|
|
|
|
imageData = compressedData
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return imageData
|
|
|
|
}
|
|
|
|
|
|
|
|
func compressVideo(_ url: URL) async -> URL? {
|
|
|
|
await withCheckedContinuation { continuation in
|
|
|
|
let urlAsset = AVURLAsset(url: url, options: nil)
|
|
|
|
let presetName: String = if Bundle.main.bundlePath.hasSuffix(".appex") {
|
|
|
|
AVAssetExportPreset1280x720
|
|
|
|
} else {
|
|
|
|
AVAssetExportPreset1920x1080
|
|
|
|
}
|
|
|
|
guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: presetName) else {
|
|
|
|
continuation.resume(returning: nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
let outputURL = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).\(url.pathExtension)")
|
|
|
|
exportSession.outputURL = outputURL
|
|
|
|
exportSession.outputFileType = .mp4
|
|
|
|
exportSession.shouldOptimizeForNetworkUse = true
|
|
|
|
exportSession.exportAsynchronously { () in
|
|
|
|
continuation.resume(returning: outputURL)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|