react-native-compressor 1.7.2 → 1.8.1
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 +51 -8
- package/android/build.gradle +9 -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 +169 -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/Utils/createVideoThumbnail.kt +158 -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 +34 -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 +37 -389
- package/ios/CompressorManager.swift +107 -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/CreateVideoThumbnail.swift +111 -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 +25 -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 +47 -1
- 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 +9 -7
- 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 +9 -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 +16 -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 +7 -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 +4 -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 +25 -2
- package/src/Video/index.tsx +11 -84
- package/src/expo-plugin/compressor.ts +1 -1
- package/src/index.tsx +12 -3
- package/src/utils/Downloader.tsx +38 -0
- package/src/utils/Uploader.tsx +78 -0
- package/src/utils/index.tsx +15 -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,111 @@
|
|
|
1
|
+
//
|
|
2
|
+
// CreateVideoThumbnail.swift
|
|
3
|
+
// react-native-compressor
|
|
4
|
+
//
|
|
5
|
+
// Created by Numan on 13/09/2023.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import AVFoundation
|
|
10
|
+
import UIKit
|
|
11
|
+
|
|
12
|
+
class CreateVideoThumbnail: NSObject {
|
|
13
|
+
|
|
14
|
+
func create(_ fileUrl:String, options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
15
|
+
let headers = options["headers"] as? [String: Any] ?? [:]
|
|
16
|
+
let format = "jpeg"
|
|
17
|
+
|
|
18
|
+
do {
|
|
19
|
+
// Prepare cache folder
|
|
20
|
+
var tempDirectory = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last ?? ""
|
|
21
|
+
tempDirectory = (tempDirectory as NSString).appendingPathComponent("thumbnails/")
|
|
22
|
+
// Create thumbnail directory if not exists
|
|
23
|
+
try FileManager.default.createDirectory(atPath: tempDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
24
|
+
let fileName = "thumb-\(ProcessInfo.processInfo.globallyUniqueString).\(format)"
|
|
25
|
+
let fullPath = (tempDirectory as NSString).appendingPathComponent(fileName)
|
|
26
|
+
|
|
27
|
+
if FileManager.default.fileExists(atPath: fullPath) {
|
|
28
|
+
if let imageData = try? Data(contentsOf: URL(fileURLWithPath: fullPath)) {
|
|
29
|
+
if let thumbnail = UIImage(data: imageData) {
|
|
30
|
+
resolve([
|
|
31
|
+
"path": fullPath,
|
|
32
|
+
"size": Float(imageData.count),
|
|
33
|
+
"mime": "image/\(format)",
|
|
34
|
+
"width": Float(thumbnail.size.width),
|
|
35
|
+
"height": Float(thumbnail.size.height)
|
|
36
|
+
])
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
var vidURL: URL?
|
|
43
|
+
let fileUrlLowerCase = fileUrl.lowercased()
|
|
44
|
+
|
|
45
|
+
if fileUrlLowerCase.hasPrefix("http://") || fileUrlLowerCase.hasPrefix("https://") || fileUrlLowerCase.hasPrefix("file://") {
|
|
46
|
+
vidURL = URL(string: fileUrl)
|
|
47
|
+
} else {
|
|
48
|
+
// Consider it's file url path
|
|
49
|
+
vidURL = URL(fileURLWithPath: fileUrl)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let asset = AVURLAsset(url: vidURL!, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
|
|
53
|
+
generateThumbImage(asset: asset, atTime: 0, completion: { thumbnail in
|
|
54
|
+
// Generate thumbnail
|
|
55
|
+
var data: Data? = thumbnail.jpegData(compressionQuality: 1.0)
|
|
56
|
+
|
|
57
|
+
if let data = data {
|
|
58
|
+
try? data.write(to: URL(fileURLWithPath: fullPath))
|
|
59
|
+
resolve([
|
|
60
|
+
"path": fullPath,
|
|
61
|
+
"size": Float(data.count),
|
|
62
|
+
"mime": "image/\(format)",
|
|
63
|
+
"width": Float(thumbnail.size.width),
|
|
64
|
+
"height": Float(thumbnail.size.height)
|
|
65
|
+
] as [String : Any])
|
|
66
|
+
}
|
|
67
|
+
}, failure: { error in
|
|
68
|
+
reject(error._domain, error.localizedDescription, nil)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
} catch {
|
|
72
|
+
reject("Error", error.localizedDescription, nil)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static func cleanCacheDir(cacheDir: String, resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
77
|
+
let cachePathDir: String = cacheDir.isEmpty ? "thumbnails/" : cacheDir
|
|
78
|
+
var cacheDirectoryPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last ?? ""
|
|
79
|
+
cacheDirectoryPath = (cacheDirectoryPath as NSString).appendingPathComponent(cachePathDir)
|
|
80
|
+
|
|
81
|
+
let fm = FileManager.default
|
|
82
|
+
do {
|
|
83
|
+
let files = try fm.contentsOfDirectory(atPath: cacheDirectoryPath)
|
|
84
|
+
for file in files {
|
|
85
|
+
let filePath = (cacheDirectoryPath as NSString).appendingPathComponent(file)
|
|
86
|
+
try fm.removeItem(atPath: filePath)
|
|
87
|
+
}
|
|
88
|
+
resolve("done")
|
|
89
|
+
} catch {
|
|
90
|
+
// Handle error if necessary
|
|
91
|
+
reject("Error", error.localizedDescription, nil)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func generateThumbImage(asset: AVURLAsset, atTime timeStamp: Int, completion: @escaping (UIImage) -> Void, failure: @escaping (Error) -> Void) {
|
|
96
|
+
let generator = AVAssetImageGenerator(asset: asset)
|
|
97
|
+
generator.appliesPreferredTrackTransform = true
|
|
98
|
+
generator.maximumSize = CGSize(width: 512, height: 512)
|
|
99
|
+
generator.requestedTimeToleranceBefore = CMTimeMake(value: 0, timescale: 1000)
|
|
100
|
+
generator.requestedTimeToleranceAfter = CMTimeMake(value: 0, timescale: 1000)
|
|
101
|
+
let time = CMTimeMake(value: Int64(timeStamp), timescale: 1000)
|
|
102
|
+
generator.generateCGImagesAsynchronously(forTimes: [NSValue(time: time)]) { _, image, _, result, error in
|
|
103
|
+
if result == .succeeded, let cgImage = image {
|
|
104
|
+
let thumbnail = UIImage(cgImage: cgImage)
|
|
105
|
+
completion(thumbnail)
|
|
106
|
+
} else if let error = error {
|
|
107
|
+
failure(error)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|