react-native-compressor 1.7.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -7
- package/android/build.gradle +8 -0
- package/android/src/main/java/com/reactnativecompressor/Audio/AudioCompressor.kt +527 -0
- package/android/src/main/java/com/reactnativecompressor/Audio/AudioMain.kt +31 -0
- package/android/src/main/java/com/reactnativecompressor/CompressorModule.kt +157 -0
- package/android/src/main/java/com/reactnativecompressor/CompressorPackage.kt +34 -0
- package/android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.kt +199 -0
- package/android/src/main/java/com/reactnativecompressor/Image/ImageCompressorOptions.kt +57 -0
- package/android/src/main/java/com/reactnativecompressor/Image/ImageMain.kt +31 -0
- package/android/src/main/java/com/reactnativecompressor/Image/ImageSize.kt +3 -0
- package/android/src/main/java/com/reactnativecompressor/Utils/Downloader.kt +119 -0
- package/android/src/main/java/com/reactnativecompressor/Utils/EventEmitterHandler.kt +66 -0
- package/android/src/main/java/com/reactnativecompressor/Utils/MediaCache.kt +61 -0
- package/android/src/main/java/com/reactnativecompressor/Utils/RealPathUtil.kt +177 -0
- package/android/src/main/java/com/reactnativecompressor/Utils/Uploader.kt +117 -0
- package/android/src/main/java/com/reactnativecompressor/Utils/Utils.kt +162 -0
- package/android/src/main/java/com/reactnativecompressor/Video/AutoVideoCompression.kt +63 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressorHelper.kt +125 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoMain.kt +86 -0
- package/android/src/newarch/CompressorSpec.kt +5 -0
- package/android/src/oldarch/CompressorSpec.kt +32 -0
- package/ios/Audio/AudioMain.swift +115 -0
- package/ios/Compressor-Bridging-Header.h +1 -2
- package/ios/Compressor.h +0 -8
- package/ios/Compressor.mm +28 -389
- package/ios/CompressorManager.swift +96 -0
- package/ios/Image/ImageCompressor.swift +354 -0
- package/ios/Image/ImageCompressorOptions.swift +93 -0
- package/ios/Image/ImageMain.swift +27 -0
- package/ios/Utils/Downloader.swift +157 -0
- package/ios/Utils/EventEmitterHandler.swift +54 -0
- package/ios/Utils/MediaCache.swift +48 -0
- package/ios/Utils/Uploader.swift +99 -0
- package/ios/Utils/Utils.swift +110 -0
- package/ios/Video/{VideoCompressor.swift → VideoMain.swift} +167 -146
- package/lib/commonjs/Image/index.js +1 -1
- package/lib/commonjs/Image/index.js.map +1 -1
- package/lib/commonjs/Main.js +1 -8
- package/lib/commonjs/Main.js.map +1 -1
- package/lib/commonjs/Spec/NativeCompressor.js.map +1 -1
- package/lib/commonjs/Video/index.js +4 -33
- package/lib/commonjs/Video/index.js.map +1 -1
- package/lib/commonjs/expo-plugin/compressor.js.map +1 -1
- package/lib/commonjs/index.js +11 -6
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/Downloader.js +38 -0
- package/lib/commonjs/utils/Downloader.js.map +1 -0
- package/lib/commonjs/utils/Uploader.js +40 -0
- package/lib/commonjs/utils/Uploader.js.map +1 -0
- package/lib/commonjs/utils/index.js +35 -0
- package/lib/commonjs/utils/index.js.map +1 -1
- package/lib/module/Image/index.js +1 -1
- package/lib/module/Image/index.js.map +1 -1
- package/lib/module/Main.js +1 -7
- package/lib/module/Main.js.map +1 -1
- package/lib/module/Spec/NativeCompressor.js.map +1 -1
- package/lib/module/Video/index.js +5 -33
- package/lib/module/Video/index.js.map +1 -1
- package/lib/module/expo-plugin/compressor.js.map +1 -1
- package/lib/module/index.js +6 -6
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/Downloader.js +31 -0
- package/lib/module/utils/Downloader.js.map +1 -0
- package/lib/module/utils/Uploader.js +33 -0
- package/lib/module/utils/Uploader.js.map +1 -0
- package/lib/module/utils/index.js +2 -0
- package/lib/module/utils/index.js.map +1 -1
- package/lib/typescript/Image/index.d.ts +4 -0
- package/lib/typescript/Image/index.d.ts.map +1 -1
- package/lib/typescript/Main.d.ts +1 -2
- package/lib/typescript/Main.d.ts.map +1 -1
- package/lib/typescript/Spec/NativeCompressor.d.ts +8 -2
- package/lib/typescript/Spec/NativeCompressor.d.ts.map +1 -1
- package/lib/typescript/Video/index.d.ts +4 -28
- package/lib/typescript/Video/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +5 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/utils/Downloader.d.ts +2 -0
- package/lib/typescript/utils/Downloader.d.ts.map +1 -0
- package/lib/typescript/utils/Uploader.d.ts +28 -0
- package/lib/typescript/utils/Uploader.d.ts.map +1 -0
- package/lib/typescript/utils/index.d.ts +2 -1
- package/lib/typescript/utils/index.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/Image/index.tsx +5 -1
- package/src/Main.tsx +1 -15
- package/src/Spec/NativeCompressor.ts +14 -2
- package/src/Video/index.tsx +11 -84
- package/src/expo-plugin/compressor.ts +1 -1
- package/src/index.tsx +6 -3
- package/src/utils/Downloader.tsx +38 -0
- package/src/utils/Uploader.tsx +78 -0
- package/src/utils/index.tsx +2 -0
- package/android/src/main/java/com/reactnativecompressor/Audio/AudioCompressor.java +0 -605
- package/android/src/main/java/com/reactnativecompressor/CompressorModule.java +0 -220
- package/android/src/main/java/com/reactnativecompressor/CompressorPackage.java +0 -58
- package/android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.java +0 -257
- package/android/src/main/java/com/reactnativecompressor/Image/utils/ImageCompressorOptions.java +0 -69
- package/android/src/main/java/com/reactnativecompressor/Image/utils/ImageSize.java +0 -13
- package/android/src/main/java/com/reactnativecompressor/Utils/FileUplaoder/FileUploadHelper.java +0 -39
- package/android/src/main/java/com/reactnativecompressor/Utils/FileUplaoder/FileUploader.java +0 -129
- package/android/src/main/java/com/reactnativecompressor/Utils/MediaCache.java +0 -65
- package/android/src/main/java/com/reactnativecompressor/Utils/RealPathUtil.java +0 -210
- package/android/src/main/java/com/reactnativecompressor/Utils/Utils.java +0 -272
- package/android/src/main/java/com/reactnativecompressor/Video/AutoVideoCompression/AutoVideoCompression.java +0 -87
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressorHelper.java +0 -182
- package/android/src/main/java/com/reactnativecompressor/Video/VideoModule.java +0 -112
- package/android/src/newarch/CompressorSpec.java +0 -9
- package/android/src/newarch/VideoCompressorSpec.java +0 -9
- package/android/src/oldarch/CompressorSpec.java +0 -34
- package/android/src/oldarch/VideoCompressorSpec.java +0 -26
- package/ios/Image/ImageCompressor.h +0 -22
- package/ios/Image/ImageCompressor.m +0 -540
- package/ios/Image/ImageCompressorOptions.h +0 -28
- package/ios/Image/ImageCompressorOptions.m +0 -97
- package/ios/Utils/Downloader.h +0 -16
- package/ios/Utils/Downloader.m +0 -150
- package/ios/Utils/MediaCache.h +0 -11
- package/ios/Utils/MediaCache.m +0 -51
- package/ios/Video/VideoCompressor.h +0 -5
- package/ios/Video/VideoCompressor.m +0 -1
- package/lib/commonjs/Spec/NativeVideoCompressor.js +0 -10
- package/lib/commonjs/Spec/NativeVideoCompressor.js.map +0 -1
- package/lib/module/Spec/NativeVideoCompressor.js +0 -3
- package/lib/module/Spec/NativeVideoCompressor.js.map +0 -1
- package/lib/typescript/Spec/NativeVideoCompressor.d.ts +0 -13
- package/lib/typescript/Spec/NativeVideoCompressor.d.ts.map +0 -1
- package/src/Spec/NativeVideoCompressor.ts +0 -14
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import Accelerate
|
|
2
|
+
import CoreGraphics
|
|
3
|
+
import Photos
|
|
4
|
+
import React
|
|
5
|
+
import Foundation
|
|
6
|
+
import MobileCoreServices
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ImageCompressor {
|
|
10
|
+
static func findTargetSize(_ image: UIImage, maxWidth: Int, maxHeight: Int) -> CGSize {
|
|
11
|
+
let width = image.size.width
|
|
12
|
+
let height = image.size.height
|
|
13
|
+
|
|
14
|
+
if width > height {
|
|
15
|
+
let newHeight = height / (width / CGFloat(maxWidth))
|
|
16
|
+
return CGSize(width: CGFloat(maxWidth), height: newHeight)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let newWidth = width / (height / CGFloat(maxHeight))
|
|
20
|
+
return CGSize(width: newWidth, height: CGFloat(maxHeight))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
static func decodeImage(_ value: String) -> UIImage? {
|
|
25
|
+
if let data = Data(base64Encoded: value, options: .ignoreUnknownCharacters) {
|
|
26
|
+
return UIImage(data: data)
|
|
27
|
+
}
|
|
28
|
+
return nil
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
static func loadImage(_ path: String) -> UIImage? {
|
|
33
|
+
var image: UIImage?
|
|
34
|
+
|
|
35
|
+
if path.hasPrefix("data:") || path.hasPrefix("file:") {
|
|
36
|
+
if let imageUrl = URL(string: path), let imageData = try? Data(contentsOf: imageUrl) {
|
|
37
|
+
image = UIImage(data: imageData)
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
image = UIImage(contentsOfFile: path)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return image
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
static func manualResize(_ image: UIImage, maxWidth: Int, maxHeight: Int) -> UIImage {
|
|
48
|
+
let targetSize = findTargetSize(image, maxWidth: maxWidth, maxHeight: maxHeight)
|
|
49
|
+
|
|
50
|
+
if let cgImage = image.cgImage {
|
|
51
|
+
let sourceWidth = Int(image.size.width)
|
|
52
|
+
let sourceHeight = Int(image.size.height)
|
|
53
|
+
let targetWidth = Int(targetSize.width)
|
|
54
|
+
let targetHeight = Int(targetSize.height)
|
|
55
|
+
let bytesPerPixel = 4
|
|
56
|
+
let sourceBytesPerRow = sourceWidth * bytesPerPixel
|
|
57
|
+
let targetBytesPerRow = targetWidth * bytesPerPixel
|
|
58
|
+
let bitsPerComponent = 8
|
|
59
|
+
|
|
60
|
+
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
61
|
+
|
|
62
|
+
var sourceData = [UInt8](repeating: 0, count: sourceHeight * sourceBytesPerRow)
|
|
63
|
+
let context = CGContext(data: &sourceData,
|
|
64
|
+
width: Int(sourceWidth),
|
|
65
|
+
height: Int(sourceHeight),
|
|
66
|
+
bitsPerComponent: bitsPerComponent,
|
|
67
|
+
bytesPerRow: sourceBytesPerRow,
|
|
68
|
+
space: colorSpace,
|
|
69
|
+
bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Big.rawValue)
|
|
70
|
+
|
|
71
|
+
context?.draw(cgImage, in: CGRect(x: 0, y: 0, width: sourceWidth, height: sourceHeight))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
var targetData = [UInt8](repeating: 0, count: targetWidth * targetHeight * bytesPerPixel)
|
|
75
|
+
|
|
76
|
+
var srcBuffer = vImage_Buffer(data: &sourceData, height: vImagePixelCount(sourceHeight), width: vImagePixelCount(sourceWidth), rowBytes: sourceBytesPerRow)
|
|
77
|
+
var targetBuffer = vImage_Buffer(data: &targetData, height: vImagePixelCount(targetHeight), width: vImagePixelCount(targetWidth), rowBytes: targetBytesPerRow)
|
|
78
|
+
|
|
79
|
+
let error = vImageScale_ARGB8888(&srcBuffer, &targetBuffer, nil, vImage_Flags(kvImageHighQualityResampling))
|
|
80
|
+
|
|
81
|
+
if error != kvImageNoError {
|
|
82
|
+
free(&targetData)
|
|
83
|
+
let exception = NSException(name: NSExceptionName(rawValue: "drawing_error"), reason: "Problem while rendering your image", userInfo: nil)
|
|
84
|
+
exception.raise()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let targetContext = CGContext(data: &targetData,
|
|
88
|
+
width: targetWidth,
|
|
89
|
+
height: targetHeight,
|
|
90
|
+
bitsPerComponent: bitsPerComponent,
|
|
91
|
+
bytesPerRow: targetBytesPerRow,
|
|
92
|
+
space: colorSpace,
|
|
93
|
+
bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Big.rawValue)
|
|
94
|
+
|
|
95
|
+
let targetRef = targetContext?.makeImage()
|
|
96
|
+
let resizedImage = UIImage(cgImage: targetRef!)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
return resizedImage
|
|
100
|
+
}
|
|
101
|
+
return UIImage()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
static func manualCompress(_ image: UIImage, output: Int, quality: Float, outputExtension: String, isBase64: Bool) -> String {
|
|
106
|
+
var data: Data
|
|
107
|
+
var exception: NSException?
|
|
108
|
+
|
|
109
|
+
switch OutputType(rawValue: output)! {
|
|
110
|
+
case .jpg:
|
|
111
|
+
data = image.jpegData(compressionQuality: CGFloat(quality))!
|
|
112
|
+
case .png:
|
|
113
|
+
data = image.pngData()!
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if isBase64 {
|
|
117
|
+
return data.base64EncodedString(options: [])
|
|
118
|
+
} else {
|
|
119
|
+
let filePath = Utils.generateCacheFilePath(outputExtension)
|
|
120
|
+
do {
|
|
121
|
+
try data.write(to: URL(fileURLWithPath: filePath), options: .atomic)
|
|
122
|
+
let returnablePath = makeValidUri(filePath)
|
|
123
|
+
return returnablePath
|
|
124
|
+
} catch {
|
|
125
|
+
exception = NSException(name: NSExceptionName(rawValue: "file_error"), reason: "Error writing file", userInfo: nil)
|
|
126
|
+
exception?.raise()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return ""
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
static func scaleAndRotateImage(_ image: UIImage) -> UIImage {
|
|
135
|
+
if image.imageOrientation == .up {
|
|
136
|
+
return image
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
var transform = CGAffineTransform.identity
|
|
140
|
+
|
|
141
|
+
switch image.imageOrientation {
|
|
142
|
+
case .down, .downMirrored:
|
|
143
|
+
transform = transform.translatedBy(x: image.size.width, y: image.size.height)
|
|
144
|
+
transform = transform.rotated(by: CGFloat.pi)
|
|
145
|
+
case .left, .leftMirrored:
|
|
146
|
+
transform = transform.translatedBy(x: image.size.width, y: 0)
|
|
147
|
+
transform = transform.rotated(by: CGFloat.pi / 2)
|
|
148
|
+
case .right, .rightMirrored:
|
|
149
|
+
transform = transform.translatedBy(x: 0, y: image.size.height)
|
|
150
|
+
transform = transform.rotated(by: -CGFloat.pi / 2)
|
|
151
|
+
default:
|
|
152
|
+
break
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
switch image.imageOrientation {
|
|
156
|
+
case .upMirrored, .downMirrored:
|
|
157
|
+
transform = transform.translatedBy(x: image.size.width, y: 0)
|
|
158
|
+
transform = transform.scaledBy(x: -1, y: 1)
|
|
159
|
+
case .leftMirrored, .rightMirrored:
|
|
160
|
+
transform = transform.translatedBy(x: image.size.height, y: 0)
|
|
161
|
+
transform = transform.scaledBy(x: -1, y: 1)
|
|
162
|
+
default:
|
|
163
|
+
break
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if let cgImage = image.cgImage, let colorSpace = cgImage.colorSpace {
|
|
167
|
+
guard let context = CGContext(data: nil, width: Int(image.size.width), height: Int(image.size.height),
|
|
168
|
+
bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0,
|
|
169
|
+
space: colorSpace, bitmapInfo: cgImage.bitmapInfo.rawValue) else {
|
|
170
|
+
return image
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
context.concatenate(transform)
|
|
174
|
+
|
|
175
|
+
switch image.imageOrientation {
|
|
176
|
+
case .left, .leftMirrored, .right, .rightMirrored:
|
|
177
|
+
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: image.size.height, height: image.size.width))
|
|
178
|
+
default:
|
|
179
|
+
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if let rotatedCGImage = context.makeImage() {
|
|
183
|
+
return UIImage(cgImage: rotatedCGImage)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return image
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
static func makeValidUri(_ filePath: String) -> String {
|
|
192
|
+
let fileWithUrl = URL(fileURLWithPath: filePath)
|
|
193
|
+
let absoluteUrl = fileWithUrl.deletingLastPathComponent()
|
|
194
|
+
let fileUrl = "file://\(absoluteUrl.path)/\(fileWithUrl.lastPathComponent)"
|
|
195
|
+
return fileUrl
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
static func manualCompressHandler(_ imagePath: String, options: ImageCompressorOptions) -> String {
|
|
200
|
+
var exception: NSException?
|
|
201
|
+
var image: UIImage?
|
|
202
|
+
|
|
203
|
+
switch options.input {
|
|
204
|
+
case .base64:
|
|
205
|
+
image = ImageCompressor.decodeImage(imagePath)
|
|
206
|
+
case .uri:
|
|
207
|
+
image = ImageCompressor.loadImage(imagePath)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if let _image = image {
|
|
211
|
+
image = ImageCompressor.scaleAndRotateImage(_image)
|
|
212
|
+
let outputExtension = ImageCompressorOptions.getOutputInString(options.output)
|
|
213
|
+
let resizedImage = ImageCompressor.manualResize(_image, maxWidth: options.maxWidth, maxHeight: options.maxHeight)
|
|
214
|
+
let isBase64 = options.returnableOutputType == .rbase64
|
|
215
|
+
return ImageCompressor.manualCompress(resizedImage, output: options.output.rawValue, quality: options.quality, outputExtension: outputExtension, isBase64: isBase64)
|
|
216
|
+
} else {
|
|
217
|
+
exception = NSException(name: NSExceptionName(rawValue: "unsupported_value"), reason: "Unsupported value type.", userInfo: nil)
|
|
218
|
+
exception?.raise()
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return ""
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
static func autoCompressHandler(_ imagePath: String, options: ImageCompressorOptions) -> String {
|
|
226
|
+
var exception: NSException?
|
|
227
|
+
var image = ImageCompressor.loadImage(imagePath)
|
|
228
|
+
|
|
229
|
+
if var image = image {
|
|
230
|
+
image = ImageCompressor.scaleAndRotateImage(image)
|
|
231
|
+
let outputExtension = ImageCompressorOptions.getOutputInString(options.output)
|
|
232
|
+
|
|
233
|
+
var actualHeight = image.size.height
|
|
234
|
+
var actualWidth = image.size.width
|
|
235
|
+
let maxHeight: CGFloat = 1280.0
|
|
236
|
+
let maxWidth: CGFloat = 1280.0
|
|
237
|
+
var imgRatio = actualWidth / actualHeight
|
|
238
|
+
let maxRatio = maxWidth / maxHeight
|
|
239
|
+
let compressionQuality: CGFloat = 0.8
|
|
240
|
+
|
|
241
|
+
if actualHeight > maxHeight || actualWidth > maxWidth {
|
|
242
|
+
if imgRatio < maxRatio {
|
|
243
|
+
imgRatio = maxHeight / actualHeight
|
|
244
|
+
actualWidth = imgRatio * actualWidth
|
|
245
|
+
actualHeight = maxHeight
|
|
246
|
+
} else if imgRatio > maxRatio {
|
|
247
|
+
imgRatio = maxWidth / actualWidth
|
|
248
|
+
actualHeight = imgRatio * actualHeight
|
|
249
|
+
actualWidth = maxWidth
|
|
250
|
+
} else {
|
|
251
|
+
actualHeight = maxHeight
|
|
252
|
+
actualWidth = maxWidth
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let rect = CGRect(x: 0.0, y: 0.0, width: actualWidth, height: actualHeight)
|
|
257
|
+
UIGraphicsBeginImageContext(rect.size)
|
|
258
|
+
image.draw(in: rect)
|
|
259
|
+
if let img = UIGraphicsGetImageFromCurrentImageContext() {
|
|
260
|
+
let imageData = img.jpegData(compressionQuality: compressionQuality)
|
|
261
|
+
UIGraphicsEndImageContext()
|
|
262
|
+
let filePath = Utils.generateCacheFilePath(outputExtension)
|
|
263
|
+
do {
|
|
264
|
+
try imageData?.write(to: URL(fileURLWithPath: filePath), options: .atomic)
|
|
265
|
+
let returnablePath = ImageCompressor.makeValidUri(filePath)
|
|
266
|
+
return returnablePath
|
|
267
|
+
} catch {
|
|
268
|
+
exception = NSException(name: NSExceptionName(rawValue: "file_error"), reason: "Error writing file", userInfo: nil)
|
|
269
|
+
exception?.raise()
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
exception = NSException(name: NSExceptionName(rawValue: "unsupported_value"), reason: "Unsupported value type.", userInfo: nil)
|
|
274
|
+
exception?.raise()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return ""
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
static func getAbsoluteImagePath(_ imagePath: String, options: ImageCompressorOptions, completionHandler: @escaping (String) -> Void) {
|
|
281
|
+
if imagePath.hasPrefix("http://") || imagePath.hasPrefix("https://") {
|
|
282
|
+
let uuid=options.uuid ?? ""
|
|
283
|
+
let progressDivider=options.progressDivider
|
|
284
|
+
Downloader.downloadFileAndSaveToCache(imagePath, uuid: uuid,progressDivider: progressDivider) { downloadedPath in
|
|
285
|
+
completionHandler(downloadedPath)
|
|
286
|
+
}
|
|
287
|
+
return
|
|
288
|
+
} else if !imagePath.contains("ph://") {
|
|
289
|
+
completionHandler(imagePath)
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let imageURL = URL(string: imagePath.replacingOccurrences(of: " ", with: "%20"))
|
|
294
|
+
var size = CGSize.zero
|
|
295
|
+
var scale: CGFloat = 1
|
|
296
|
+
var resizeMode = RCTResizeMode.contain
|
|
297
|
+
var assetID = ""
|
|
298
|
+
var results: PHFetchResult<PHAsset>?
|
|
299
|
+
|
|
300
|
+
if imageURL?.scheme?.caseInsensitiveCompare("assets-library") == .orderedSame {
|
|
301
|
+
assetID = imageURL?.absoluteString ?? ""
|
|
302
|
+
results = PHAsset.fetchAssets(withALAssetURLs: [imageURL!], options: nil)
|
|
303
|
+
} else {
|
|
304
|
+
assetID = imagePath.replacingOccurrences(of: "ph://", with: "")
|
|
305
|
+
results = PHAsset.fetchAssets(withLocalIdentifiers: [assetID], options: nil)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
guard let asset = results?.firstObject else {
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
let imageOptions = PHImageRequestOptions()
|
|
313
|
+
imageOptions.isNetworkAccessAllowed = true
|
|
314
|
+
imageOptions.deliveryMode = .highQualityFormat
|
|
315
|
+
|
|
316
|
+
var useMaximumSize = size.equalTo(CGSize.zero)
|
|
317
|
+
var targetSize: CGSize
|
|
318
|
+
|
|
319
|
+
if useMaximumSize {
|
|
320
|
+
targetSize = PHImageManagerMaximumSize
|
|
321
|
+
imageOptions.resizeMode = .none
|
|
322
|
+
} else {
|
|
323
|
+
targetSize = CGSize(width: size.width * scale, height: size.height * scale)
|
|
324
|
+
imageOptions.resizeMode = .fast
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
var contentMode = PHImageContentMode.aspectFill
|
|
328
|
+
if resizeMode == .contain {
|
|
329
|
+
contentMode = .aspectFit
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
PHImageManager.default().requestImage(for: asset, targetSize: targetSize, contentMode: contentMode, options: imageOptions) { result, info in
|
|
333
|
+
if let result = result {
|
|
334
|
+
let imageName = assetID.replacingOccurrences(of: "/", with: "_")
|
|
335
|
+
if let imagePath = saveImageIntoCache(result, withName: imageName) {
|
|
336
|
+
completionHandler(imagePath)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
static func saveImageIntoCache(_ image: UIImage, withName name: String) -> String? {
|
|
343
|
+
if let imageData = image.jpegData(compressionQuality: 1),
|
|
344
|
+
let filePath: String? = Utils.generateCacheFilePath("jpg") {
|
|
345
|
+
do {
|
|
346
|
+
try imageData.write(to: URL(fileURLWithPath: filePath!), options: .atomic)
|
|
347
|
+
return filePath
|
|
348
|
+
} catch {
|
|
349
|
+
return nil
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return nil
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
enum OutputType: Int {
|
|
5
|
+
case jpg
|
|
6
|
+
case png
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
enum InputType: Int {
|
|
10
|
+
case base64
|
|
11
|
+
case uri
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
enum ReturnableOutputType: Int {
|
|
15
|
+
case rbase64
|
|
16
|
+
case ruri
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class ImageCompressorOptions: NSObject {
|
|
20
|
+
static func fromDictionary(_ dictionary: [String: Any]?) -> ImageCompressorOptions {
|
|
21
|
+
let options = ImageCompressorOptions()
|
|
22
|
+
|
|
23
|
+
guard let dictionary = dictionary else {
|
|
24
|
+
return options
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (key, value) in dictionary {
|
|
28
|
+
switch key {
|
|
29
|
+
case "autoCompress":
|
|
30
|
+
options.autoCompress = (value as? Bool) ?? false
|
|
31
|
+
case "maxWidth":
|
|
32
|
+
options.maxWidth = (value as? Int) ?? 1280
|
|
33
|
+
case "maxHeight":
|
|
34
|
+
options.maxHeight = (value as? Int) ?? 1280
|
|
35
|
+
case "progressDivider":
|
|
36
|
+
options.progressDivider = (value as? Int) ?? 0
|
|
37
|
+
case "quality":
|
|
38
|
+
options.quality = (value as? Float) ?? 0.8
|
|
39
|
+
case "input":
|
|
40
|
+
options.parseInput(value as? String)
|
|
41
|
+
case "output":
|
|
42
|
+
options.parseOutput(value as? String)
|
|
43
|
+
case "returnableOutputType":
|
|
44
|
+
options.parseReturnableOutput(value as? String)
|
|
45
|
+
case "uuid":
|
|
46
|
+
options.uuid = value as? String
|
|
47
|
+
default:
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return options
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static func getOutputInString(_ output: OutputType) -> String {
|
|
56
|
+
return (output == .jpg) ? "jpg" : "png"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
var autoCompress: Bool = true
|
|
60
|
+
var maxWidth: Int = 1280
|
|
61
|
+
var maxHeight: Int = 1280
|
|
62
|
+
var progressDivider: Int = 0
|
|
63
|
+
var quality: Float = 0.8
|
|
64
|
+
var input: InputType = .uri
|
|
65
|
+
var output: OutputType = .jpg
|
|
66
|
+
var returnableOutputType: ReturnableOutputType = .ruri
|
|
67
|
+
var uuid: String?
|
|
68
|
+
|
|
69
|
+
override init() {
|
|
70
|
+
super.init()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private func parseInput(_ input: String?) {
|
|
74
|
+
guard let input = input else { return }
|
|
75
|
+
|
|
76
|
+
let inputTranslations: [String: InputType] = ["base64": .base64, "uri": .uri]
|
|
77
|
+
self.input = inputTranslations[input] ?? .uri
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private func parseReturnableOutput(_ input: String?) {
|
|
81
|
+
guard let input = input else { return }
|
|
82
|
+
|
|
83
|
+
let outputTranslations: [String: ReturnableOutputType] = ["base64": .rbase64, "uri": .ruri]
|
|
84
|
+
self.returnableOutputType = outputTranslations[input] ?? .ruri
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private func parseOutput(_ output: String?) {
|
|
88
|
+
guard let output = output else { return }
|
|
89
|
+
|
|
90
|
+
let outputTranslations: [String: OutputType] = ["jpg": .jpg, "png": .png]
|
|
91
|
+
self.output = outputTranslations[output] ?? .jpg
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//
|
|
2
|
+
// ImageMain.swift
|
|
3
|
+
// react-native-compressor
|
|
4
|
+
//
|
|
5
|
+
// Created by Numan on 10/09/2023.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
class ImageMain {
|
|
10
|
+
static func image_compress(_ imagePath: String, optionMap: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
11
|
+
do {
|
|
12
|
+
let options = ImageCompressorOptions.fromDictionary(optionMap as! [String : Any])
|
|
13
|
+
ImageCompressor.getAbsoluteImagePath(imagePath, options: options) { absoluteImagePath in
|
|
14
|
+
if options.autoCompress {
|
|
15
|
+
let result = ImageCompressor.autoCompressHandler(absoluteImagePath, options: options)
|
|
16
|
+
resolve(result)
|
|
17
|
+
} else {
|
|
18
|
+
let result = ImageCompressor.manualCompressHandler(absoluteImagePath, options: options)
|
|
19
|
+
resolve(result)
|
|
20
|
+
}
|
|
21
|
+
MediaCache.removeCompletedImagePath(absoluteImagePath)
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
reject(error.localizedDescription, error.localizedDescription, nil)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
class Downloader: NSObject, URLSessionDownloadDelegate {
|
|
4
|
+
private var session: URLSession?
|
|
5
|
+
private var task: URLSessionDownloadTask?
|
|
6
|
+
private var statusCode: NSNumber?
|
|
7
|
+
private var lastProgressEmitTimestamp: TimeInterval = 0
|
|
8
|
+
private var lastProgressValue: NSNumber?
|
|
9
|
+
private var contentLength: NSNumber?
|
|
10
|
+
private var bytesWritten: NSNumber?
|
|
11
|
+
private var resumeData: Data?
|
|
12
|
+
private var toFile: String?
|
|
13
|
+
private var progressDivider: Int=0
|
|
14
|
+
|
|
15
|
+
typealias ProgressCallback = (NSNumber) -> Void
|
|
16
|
+
typealias DownloadCompleteCallback = (String) -> Void
|
|
17
|
+
typealias ErrorCallback = (Error) -> Void
|
|
18
|
+
|
|
19
|
+
var globalProgressCallback: ProgressCallback?
|
|
20
|
+
var globalDownloadCompleteCallback: DownloadCompleteCallback?
|
|
21
|
+
var globalErrorCallback: ErrorCallback?
|
|
22
|
+
|
|
23
|
+
private var fileHandle: FileHandle?
|
|
24
|
+
|
|
25
|
+
func generateCacheFilePath(extension ext: String) -> String {
|
|
26
|
+
let uuid = UUID()
|
|
27
|
+
let imageNameWithoutExtension = uuid.uuidString
|
|
28
|
+
let imageName = imageNameWithoutExtension + "." + ext
|
|
29
|
+
let filePath = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(imageName).path
|
|
30
|
+
return filePath
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static func downloadFileAndSaveToCache(_ fileUrl: String, uuid: String,progressDivider: Int, completion: @escaping (String) -> Void) {
|
|
34
|
+
let downloader = Downloader()
|
|
35
|
+
|
|
36
|
+
downloader.downloadFile(fromUrl: fileUrl,progressDivider: progressDivider) { filePath in
|
|
37
|
+
MediaCache.addCompletedImagePath(filePath)
|
|
38
|
+
print("download completed file path: \(filePath)")
|
|
39
|
+
completion(filePath)
|
|
40
|
+
} progressCallback: { progress in
|
|
41
|
+
EventEmitterHandler.emitDownloadProgress(progress, uuid: uuid)
|
|
42
|
+
} errorCallback: { error in
|
|
43
|
+
print("error downloadFile", error)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func downloadFile(fromUrl: String, progressDivider: Int, downloadCompleteCallback: @escaping DownloadCompleteCallback, progressCallback: @escaping ProgressCallback, errorCallback: @escaping ErrorCallback) -> String? {
|
|
48
|
+
globalProgressCallback = progressCallback
|
|
49
|
+
globalDownloadCompleteCallback = downloadCompleteCallback
|
|
50
|
+
globalErrorCallback = errorCallback
|
|
51
|
+
self.progressDivider=progressDivider
|
|
52
|
+
|
|
53
|
+
lastProgressEmitTimestamp = 0
|
|
54
|
+
bytesWritten = 0
|
|
55
|
+
|
|
56
|
+
guard let url = URL(string: fromUrl) else {
|
|
57
|
+
let error = NSError(domain: "Downloader", code: NSURLErrorBadURL, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])
|
|
58
|
+
globalErrorCallback?(error)
|
|
59
|
+
return nil
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let fileExtension = url.pathExtension
|
|
63
|
+
toFile = generateCacheFilePath(extension: fileExtension)
|
|
64
|
+
|
|
65
|
+
if FileManager.default.fileExists(atPath: toFile!) {
|
|
66
|
+
fileHandle = FileHandle(forWritingAtPath: toFile!)
|
|
67
|
+
|
|
68
|
+
if fileHandle == nil {
|
|
69
|
+
let error = NSError(domain: "Downloader", code: NSURLErrorFileDoesNotExist, userInfo: [NSLocalizedDescriptionKey: "Failed to write target file at path: \(toFile!)"])
|
|
70
|
+
globalErrorCallback?(error)
|
|
71
|
+
return nil
|
|
72
|
+
} else {
|
|
73
|
+
fileHandle?.closeFile()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let config = URLSessionConfiguration.default
|
|
78
|
+
session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
|
|
79
|
+
task = session?.downloadTask(with: url)
|
|
80
|
+
task?.resume()
|
|
81
|
+
|
|
82
|
+
return nil
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
|
|
86
|
+
guard let httpResponse = downloadTask.response as? HTTPURLResponse else { return }
|
|
87
|
+
|
|
88
|
+
statusCode = NSNumber(value: httpResponse.statusCode)
|
|
89
|
+
contentLength = NSNumber(value: httpResponse.expectedContentLength)
|
|
90
|
+
|
|
91
|
+
if statusCode?.intValue == 200 {
|
|
92
|
+
self.bytesWritten = NSNumber(value: totalBytesWritten)
|
|
93
|
+
|
|
94
|
+
let doubleBytesWritten = Double(truncating: self.bytesWritten!)
|
|
95
|
+
let doubleContentLength = Double(truncating: contentLength!)
|
|
96
|
+
let doublePercents = doubleBytesWritten / doubleContentLength * 100
|
|
97
|
+
let progress = NSNumber(value: floor(doublePercents))
|
|
98
|
+
|
|
99
|
+
if self.progressDivider==0||progress.intValue % self.progressDivider == 0 {
|
|
100
|
+
if (progress != lastProgressValue) || (bytesWritten == contentLength as! Int64) {
|
|
101
|
+
lastProgressValue = progress
|
|
102
|
+
let progressPercentage = NSNumber(value: Double(truncating: self.bytesWritten!) / Double(truncating: contentLength!))
|
|
103
|
+
globalProgressCallback?(progressPercentage)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
|
110
|
+
guard let httpResponse = downloadTask.response as? HTTPURLResponse else { return }
|
|
111
|
+
|
|
112
|
+
if statusCode == nil {
|
|
113
|
+
statusCode = NSNumber(value: httpResponse.statusCode)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let destURL = URL(fileURLWithPath: toFile!)
|
|
117
|
+
let fm = FileManager.default
|
|
118
|
+
|
|
119
|
+
do {
|
|
120
|
+
if statusCode!.intValue >= 200 && statusCode!.intValue < 300 {
|
|
121
|
+
do {
|
|
122
|
+
try fm.removeItem(at: destURL) // Remove file at destination path, if it exists
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
print("unable to remove item \(error)")
|
|
126
|
+
}
|
|
127
|
+
try fm.moveItem(at: location, to: destURL)
|
|
128
|
+
bytesWritten = NSNumber(value: try fm.attributesOfItem(atPath: toFile!)[.size] as! Int64)
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
print("Downloader: Unable to move tempfile to destination. \(error)")
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Manually flush and invalidate the session to free up space
|
|
135
|
+
if session != nil {
|
|
136
|
+
session.flush(completionHandler: {
|
|
137
|
+
session.finishTasksAndInvalidate()
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let fileURL = URL(fileURLWithPath: toFile!)
|
|
142
|
+
globalDownloadCompleteCallback?(fileURL.absoluteString)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
|
146
|
+
if let error = error {
|
|
147
|
+
print("Downloader: didCompleteWithError \(error), \(error.localizedDescription)")
|
|
148
|
+
if error._code != NSURLErrorCancelled {
|
|
149
|
+
resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data
|
|
150
|
+
|
|
151
|
+
if resumeData == nil {
|
|
152
|
+
globalErrorCallback?(error)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//
|
|
2
|
+
// EventEmitterHandler.swift
|
|
3
|
+
// react-native-compressor
|
|
4
|
+
//
|
|
5
|
+
// Created by Numan on 09/09/2023.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
|
|
10
|
+
class EventEmitterHandler {
|
|
11
|
+
static var sharedCompressorObject: Any!
|
|
12
|
+
static var hasListener: Bool=false
|
|
13
|
+
|
|
14
|
+
static func initCompressorInstance(_ object: Any) {
|
|
15
|
+
sharedCompressorObject = object
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
static func stopObserving() -> Void {
|
|
20
|
+
hasListener = false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static func startObserving() -> Void {
|
|
24
|
+
hasListener = true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static func emitDownloadProgress(_ progress: NSNumber, uuid: String) {
|
|
28
|
+
var params = [String: Any]()
|
|
29
|
+
var data = [String: Any]()
|
|
30
|
+
params["uuid"] = uuid
|
|
31
|
+
data["progress"] = progress
|
|
32
|
+
params["data"] = data
|
|
33
|
+
|
|
34
|
+
(sharedCompressorObject as AnyObject).sendEvent(withName: "downloadProgress", body: params)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static func emitVideoCompressProgress(_ progress: Float, uuid: String) {
|
|
38
|
+
if(self.hasListener){
|
|
39
|
+
(sharedCompressorObject as AnyObject).sendEvent(withName: "videoCompressProgress", body: ["uuid": uuid, "data": ["progress": progress]])
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static func emituploadProgress(_ uuid: String, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
|
|
44
|
+
if(self.hasListener){
|
|
45
|
+
(sharedCompressorObject as AnyObject).sendEvent(withName: "uploadProgress", body: ["uuid": uuid, "data": ["written": totalBytesSent, "total": totalBytesExpectedToSend]])
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static func emitBackgroundTaskExpired(_ backgroundTaskId:UIBackgroundTaskIdentifier) {
|
|
50
|
+
if(self.hasListener){
|
|
51
|
+
(sharedCompressorObject as AnyObject).sendEvent(withName: "backgroundTaskExpired", body: ["backgroundTaskId": backgroundTaskId])
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|