react-native-compressor 1.8.13 → 1.8.15
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/android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.kt +28 -3
- package/android/src/main/java/com/reactnativecompressor/Utils/Utils.kt +165 -21
- package/ios/Image/ImageCompressor.swift +37 -3
- package/ios/Utils/Utils.swift +16 -0
- package/ios/Video/VideoMain.swift +1 -1
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ import android.net.Uri
|
|
|
11
11
|
import android.util.Base64
|
|
12
12
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
13
13
|
import com.reactnativecompressor.Utils.MediaCache
|
|
14
|
+
import com.reactnativecompressor.Utils.Utils.exifAttributes
|
|
14
15
|
import com.reactnativecompressor.Utils.Utils.generateCacheFilePath
|
|
15
16
|
import com.reactnativecompressor.Utils.Utils.slashifyFilePath
|
|
16
17
|
import java.io.ByteArrayOutputStream
|
|
@@ -55,7 +56,28 @@ object ImageCompressor {
|
|
|
55
56
|
return BitmapFactory.decodeFile(filePath)
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
fun
|
|
59
|
+
fun copyExifInfo(imagePath:String, outputUri:String){
|
|
60
|
+
try {
|
|
61
|
+
// for copy exif info
|
|
62
|
+
val sourceExif = ExifInterface(imagePath)
|
|
63
|
+
val compressedExif = ExifInterface(outputUri)
|
|
64
|
+
for (tag in exifAttributes) {
|
|
65
|
+
val compressedValue = compressedExif.getAttribute(tag)
|
|
66
|
+
if(compressedValue==null)
|
|
67
|
+
{
|
|
68
|
+
val sourceValue = sourceExif.getAttribute(tag)
|
|
69
|
+
if (sourceValue != null) {
|
|
70
|
+
compressedExif.setAttribute(tag, sourceValue)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
compressedExif.saveAttributes()
|
|
75
|
+
} catch (e: Exception) {
|
|
76
|
+
e.printStackTrace()
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fun encodeImage(imageDataByteArrayOutputStream: ByteArrayOutputStream, isBase64: Boolean, outputExtension: String?,imagePath: String?, reactContext: ReactApplicationContext?): String? {
|
|
59
81
|
if (isBase64) {
|
|
60
82
|
val imageData = imageDataByteArrayOutputStream.toByteArray()
|
|
61
83
|
return Base64.encodeToString(imageData, Base64.DEFAULT)
|
|
@@ -64,6 +86,9 @@ object ImageCompressor {
|
|
|
64
86
|
try {
|
|
65
87
|
val fos = FileOutputStream(outputUri)
|
|
66
88
|
imageDataByteArrayOutputStream.writeTo(fos)
|
|
89
|
+
|
|
90
|
+
copyExifInfo(imagePath!!, outputUri)
|
|
91
|
+
|
|
67
92
|
return getRNFileUrl(outputUri)
|
|
68
93
|
} catch (e: Exception) {
|
|
69
94
|
e.printStackTrace()
|
|
@@ -112,7 +137,7 @@ object ImageCompressor {
|
|
|
112
137
|
val resizedImage = resize(image, options.maxWidth, options.maxHeight)
|
|
113
138
|
val imageDataByteArrayOutputStream = compress(resizedImage, options.output, options.quality,options.disablePngTransparency)
|
|
114
139
|
val isBase64 = options.returnableOutputType === ImageCompressorOptions.ReturnableOutputType.base64
|
|
115
|
-
return encodeImage(imageDataByteArrayOutputStream, isBase64, options.output.toString(), reactContext)
|
|
140
|
+
return encodeImage(imageDataByteArrayOutputStream, isBase64, options.output.toString(),imagePath, reactContext)
|
|
116
141
|
}
|
|
117
142
|
|
|
118
143
|
fun isCompressedSizeLessThanActualFile(sourceFileUrl: String,compressedFileUrl: String?): Boolean {
|
|
@@ -197,7 +222,7 @@ object ImageCompressor {
|
|
|
197
222
|
}
|
|
198
223
|
scaledBitmap = correctImageOrientation(scaledBitmap, imagePath)
|
|
199
224
|
val imageDataByteArrayOutputStream = compress(scaledBitmap, compressorOptions.output, compressorOptions.quality,compressorOptions.disablePngTransparency)
|
|
200
|
-
val compressedImagePath=encodeImage(imageDataByteArrayOutputStream, isBase64, compressorOptions.output.toString(), reactContext)
|
|
225
|
+
val compressedImagePath=encodeImage(imageDataByteArrayOutputStream, isBase64, compressorOptions.output.toString(),imagePath, reactContext)
|
|
201
226
|
if(isCompressedSizeLessThanActualFile(imagePath!!,compressedImagePath))
|
|
202
227
|
{
|
|
203
228
|
return compressedImagePath
|
|
@@ -16,6 +16,7 @@ import java.net.HttpURLConnection
|
|
|
16
16
|
import java.net.URL
|
|
17
17
|
import java.util.UUID
|
|
18
18
|
import java.util.regex.Pattern
|
|
19
|
+
import kotlin.Throwable
|
|
19
20
|
|
|
20
21
|
object Utils {
|
|
21
22
|
private const val TAG = "react-native-compessor"
|
|
@@ -31,39 +32,41 @@ object Utils {
|
|
|
31
32
|
val currentVideoCompression = intArrayOf(0)
|
|
32
33
|
val videoCompressorClass: VideoCompressorClass? = VideoCompressorClass(reactContext);
|
|
33
34
|
compressorExports[uuid] = videoCompressorClass
|
|
34
|
-
videoCompressorClass?.start(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
35
|
+
videoCompressorClass?.start(
|
|
36
|
+
srcPath, destinationPath, resultWidth, resultHeight, videoBitRate.toInt(),
|
|
37
|
+
listener = object : CompressionListener {
|
|
38
|
+
override fun onProgress(index: Int, percent: Float) {
|
|
39
|
+
if (percent <= 100) {
|
|
40
|
+
val roundProgress = Math.round(percent)
|
|
41
|
+
if (progressDivider == 0 || (roundProgress % progressDivider == 0 && roundProgress > currentVideoCompression[0])) {
|
|
42
|
+
EventEmitterHandler.emitVideoCompressProgress((percent / 100).toDouble(), uuid)
|
|
43
|
+
currentVideoCompression[0] = roundProgress
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
override fun onStart(index: Int) {
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
override fun onSuccess(index: Int, size: Long, path: String?) {
|
|
53
|
+
val fileUrl = "file://$destinationPath"
|
|
54
|
+
//convert finish,result(true is success,false is fail)
|
|
55
|
+
promise.resolve(fileUrl)
|
|
56
|
+
MediaCache.removeCompletedImagePath(fileUrl)
|
|
57
|
+
currentVideoCompression[0] = 0
|
|
58
|
+
compressorExports[uuid] = null
|
|
59
|
+
}
|
|
59
60
|
|
|
60
61
|
override fun onFailure(index: Int, failureMessage: String) {
|
|
61
62
|
Log.wtf("failureMessage", failureMessage)
|
|
63
|
+
promise.reject(Throwable(failureMessage))
|
|
62
64
|
currentVideoCompression[0] = 0
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
override fun onCancelled(index: Int) {
|
|
66
68
|
Log.wtf("TAG", "compression has been cancelled")
|
|
69
|
+
promise.reject(Throwable("compression has been cancelled"))
|
|
67
70
|
// make UI changes, cleanup, etc
|
|
68
71
|
currentVideoCompression[0] = 0
|
|
69
72
|
}
|
|
@@ -113,7 +116,7 @@ object Utils {
|
|
|
113
116
|
fileUrl = Downloader.downloadMediaWithProgress(fileUrl, uuid,progressDivider, reactContext)
|
|
114
117
|
Log.d(TAG, "getRealPath: $fileUrl")
|
|
115
118
|
}
|
|
116
|
-
return fileUrl
|
|
119
|
+
return slashifyFilePath(fileUrl)
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
|
|
@@ -152,6 +155,147 @@ object Utils {
|
|
|
152
155
|
Log.d(AudioCompressor.TAG, log)
|
|
153
156
|
}
|
|
154
157
|
|
|
158
|
+
val exifAttributes = arrayOf(
|
|
159
|
+
"FNumber",
|
|
160
|
+
"ApertureValue",
|
|
161
|
+
"Artist",
|
|
162
|
+
"BitsPerSample",
|
|
163
|
+
"BrightnessValue",
|
|
164
|
+
"CFAPattern",
|
|
165
|
+
"ColorSpace",
|
|
166
|
+
"ComponentsConfiguration",
|
|
167
|
+
"CompressedBitsPerPixel",
|
|
168
|
+
"Compression",
|
|
169
|
+
"Contrast",
|
|
170
|
+
"Copyright",
|
|
171
|
+
"CustomRendered",
|
|
172
|
+
"DateTime",
|
|
173
|
+
"DateTimeDigitized",
|
|
174
|
+
"DateTimeOriginal",
|
|
175
|
+
"DefaultCropSize",
|
|
176
|
+
"DeviceSettingDescription",
|
|
177
|
+
"DigitalZoomRatio",
|
|
178
|
+
"DNGVersion",
|
|
179
|
+
"ExifVersion",
|
|
180
|
+
"ExposureBiasValue",
|
|
181
|
+
"ExposureIndex",
|
|
182
|
+
"ExposureMode",
|
|
183
|
+
"ExposureProgram",
|
|
184
|
+
"ExposureTime",
|
|
185
|
+
"FileSource",
|
|
186
|
+
"Flash",
|
|
187
|
+
"FlashpixVersion",
|
|
188
|
+
"FlashEnergy",
|
|
189
|
+
"FocalLength",
|
|
190
|
+
"FocalLengthIn35mmFilm",
|
|
191
|
+
"FocalPlaneResolutionUnit",
|
|
192
|
+
"FocalPlaneXResolution",
|
|
193
|
+
"FocalPlaneYResolution",
|
|
194
|
+
"FNumber",
|
|
195
|
+
"GainControl",
|
|
196
|
+
"GPSAltitude",
|
|
197
|
+
"GPSAltitudeRef",
|
|
198
|
+
"GPSAreaInformation",
|
|
199
|
+
"GPSDateStamp",
|
|
200
|
+
"GPSDestBearing",
|
|
201
|
+
"GPSDestBearingRef",
|
|
202
|
+
"GPSDestDistance",
|
|
203
|
+
"GPSDestDistanceRef",
|
|
204
|
+
"GPSDestLatitude",
|
|
205
|
+
"GPSDestLatitudeRef",
|
|
206
|
+
"GPSDestLongitude",
|
|
207
|
+
"GPSDestLongitudeRef",
|
|
208
|
+
"GPSDifferential",
|
|
209
|
+
"GPSDOP",
|
|
210
|
+
"GPSImgDirection",
|
|
211
|
+
"GPSImgDirectionRef",
|
|
212
|
+
"GPSLatitude",
|
|
213
|
+
"GPSLatitudeRef",
|
|
214
|
+
"GPSLongitude",
|
|
215
|
+
"GPSLongitudeRef",
|
|
216
|
+
"GPSMapDatum",
|
|
217
|
+
"GPSMeasureMode",
|
|
218
|
+
"GPSProcessingMethod",
|
|
219
|
+
"GPSSatellites",
|
|
220
|
+
"GPSSpeed",
|
|
221
|
+
"GPSSpeedRef",
|
|
222
|
+
"GPSStatus",
|
|
223
|
+
"GPSTimeStamp",
|
|
224
|
+
"GPSTrack",
|
|
225
|
+
"GPSTrackRef",
|
|
226
|
+
"GPSVersionID",
|
|
227
|
+
"ImageDescription",
|
|
228
|
+
"ImageLength",
|
|
229
|
+
"ImageUniqueID",
|
|
230
|
+
"ImageWidth",
|
|
231
|
+
"InteroperabilityIndex",
|
|
232
|
+
"ISOSpeedRatings",
|
|
233
|
+
"ISOSpeedRatings",
|
|
234
|
+
"JPEGInterchangeFormat",
|
|
235
|
+
"JPEGInterchangeFormatLength",
|
|
236
|
+
"LightSource",
|
|
237
|
+
"Make",
|
|
238
|
+
"MakerNote",
|
|
239
|
+
"MaxApertureValue",
|
|
240
|
+
"MeteringMode",
|
|
241
|
+
"Model",
|
|
242
|
+
"NewSubfileType",
|
|
243
|
+
"OECF",
|
|
244
|
+
"AspectFrame",
|
|
245
|
+
"PreviewImageLength",
|
|
246
|
+
"PreviewImageStart",
|
|
247
|
+
"ThumbnailImage",
|
|
248
|
+
"Orientation",
|
|
249
|
+
"PhotometricInterpretation",
|
|
250
|
+
"PixelXDimension",
|
|
251
|
+
"PixelYDimension",
|
|
252
|
+
"PlanarConfiguration",
|
|
253
|
+
"PrimaryChromaticities",
|
|
254
|
+
"ReferenceBlackWhite",
|
|
255
|
+
"RelatedSoundFile",
|
|
256
|
+
"ResolutionUnit",
|
|
257
|
+
"RowsPerStrip",
|
|
258
|
+
"ISO",
|
|
259
|
+
"JpgFromRaw",
|
|
260
|
+
"SensorBottomBorder",
|
|
261
|
+
"SensorLeftBorder",
|
|
262
|
+
"SensorRightBorder",
|
|
263
|
+
"SensorTopBorder",
|
|
264
|
+
"SamplesPerPixel",
|
|
265
|
+
"Saturation",
|
|
266
|
+
"SceneCaptureType",
|
|
267
|
+
"SceneType",
|
|
268
|
+
"SensingMethod",
|
|
269
|
+
"Sharpness",
|
|
270
|
+
"ShutterSpeedValue",
|
|
271
|
+
"Software",
|
|
272
|
+
"SpatialFrequencyResponse",
|
|
273
|
+
"SpectralSensitivity",
|
|
274
|
+
"StripByteCounts",
|
|
275
|
+
"StripOffsets",
|
|
276
|
+
"SubfileType",
|
|
277
|
+
"SubjectArea",
|
|
278
|
+
"SubjectDistance",
|
|
279
|
+
"SubjectDistanceRange",
|
|
280
|
+
"SubjectLocation",
|
|
281
|
+
"SubSecTime",
|
|
282
|
+
"SubSecTimeDigitized",
|
|
283
|
+
"SubSecTimeDigitized",
|
|
284
|
+
"SubSecTimeOriginal",
|
|
285
|
+
"SubSecTimeOriginal",
|
|
286
|
+
"ThumbnailImageLength",
|
|
287
|
+
"ThumbnailImageWidth",
|
|
288
|
+
"TransferFunction",
|
|
289
|
+
"UserComment",
|
|
290
|
+
"WhiteBalance",
|
|
291
|
+
"WhitePoint",
|
|
292
|
+
"XResolution",
|
|
293
|
+
"YCbCrCoefficients",
|
|
294
|
+
"YCbCrPositioning",
|
|
295
|
+
"YCbCrSubSampling",
|
|
296
|
+
"YResolution"
|
|
297
|
+
)
|
|
298
|
+
|
|
155
299
|
fun getLength(uri: Uri, contentResolver: ContentResolver): Long {
|
|
156
300
|
var assetFileDescriptor: AssetFileDescriptor? = null
|
|
157
301
|
try {
|
|
@@ -114,7 +114,38 @@ class ImageCompressor {
|
|
|
114
114
|
}
|
|
115
115
|
return false
|
|
116
116
|
}
|
|
117
|
-
|
|
117
|
+
|
|
118
|
+
static func isPNG(_ data: Data) -> Bool {
|
|
119
|
+
return data.starts(with: [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static func copyExifInfo(actualImagePath:String, image: UIImage, data: Data) -> Data {
|
|
123
|
+
let fileURL = URL(string: actualImagePath)!
|
|
124
|
+
let filePath = fileURL.path
|
|
125
|
+
|
|
126
|
+
let url = URL(fileURLWithPath: filePath)
|
|
127
|
+
let source = CGImageSourceCreateWithURL(url as CFURL, nil)!
|
|
128
|
+
var metadata = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any]
|
|
129
|
+
|
|
130
|
+
let dataProvider = CGDataProvider(data: data as CFData)
|
|
131
|
+
let dataImageSource = CGImageSourceCreateWithDataProvider(dataProvider!, nil)!
|
|
132
|
+
let dataMetadata = CGImageSourceCopyPropertiesAtIndex(dataImageSource, 0, nil) as? [CFString: Any]
|
|
133
|
+
|
|
134
|
+
// Copy all keys from source metadata to destination metadata if they don't exist
|
|
135
|
+
for (key, value) in dataMetadata ?? [:] {
|
|
136
|
+
if metadata?[key] == nil {
|
|
137
|
+
metadata?[key] = value
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let outputFormat = isPNG(data) ? kUTTypePNG : kUTTypeJPEG
|
|
142
|
+
|
|
143
|
+
let destinationData = NSMutableData()
|
|
144
|
+
let destination = CGImageDestinationCreateWithData(destinationData, outputFormat, 1, nil)!
|
|
145
|
+
CGImageDestinationAddImage(destination, image.cgImage!, metadata as CFDictionary?)
|
|
146
|
+
CGImageDestinationFinalize(destination)
|
|
147
|
+
return destinationData as Data
|
|
148
|
+
}
|
|
118
149
|
|
|
119
150
|
static func writeImage(_ image: UIImage, output: Int, quality: Float, outputExtension: String, isBase64: Bool,disablePngTransparency:Bool,isEnableAutoCompress:Bool,actualImagePath:String)-> String{
|
|
120
151
|
var data: Data
|
|
@@ -135,7 +166,10 @@ class ImageCompressor {
|
|
|
135
166
|
}
|
|
136
167
|
|
|
137
168
|
}
|
|
138
|
-
|
|
169
|
+
|
|
170
|
+
data=copyExifInfo(actualImagePath: actualImagePath, image: image, data: data)
|
|
171
|
+
|
|
172
|
+
|
|
139
173
|
if isBase64 {
|
|
140
174
|
return data.base64EncodedString(options: [])
|
|
141
175
|
} else {
|
|
@@ -315,7 +349,7 @@ class ImageCompressor {
|
|
|
315
349
|
}
|
|
316
350
|
return
|
|
317
351
|
} else if !imagePath.contains("ph://") {
|
|
318
|
-
completionHandler(imagePath)
|
|
352
|
+
completionHandler(Utils.slashifyFilePath(path: imagePath)!)
|
|
319
353
|
return
|
|
320
354
|
}
|
|
321
355
|
|
package/ios/Utils/Utils.swift
CHANGED
|
@@ -127,4 +127,20 @@ class Utils {
|
|
|
127
127
|
return fileSize
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
static func slashifyFilePath(path: String?) -> String? {
|
|
131
|
+
if let path = path {
|
|
132
|
+
if path.hasPrefix("file:///") {
|
|
133
|
+
return path
|
|
134
|
+
} else if path.hasPrefix("/") {
|
|
135
|
+
return path.replacingOccurrences(of: "^/+", with: "file:///", options: .regularExpression, range: nil)
|
|
136
|
+
} else {
|
|
137
|
+
// Ensure leading schema with a triple slash
|
|
138
|
+
let regex = try! NSRegularExpression(pattern: "^file:/*")
|
|
139
|
+
let modifiedPath = regex.stringByReplacingMatches(in: path, options: [], range: NSRange(location: 0, length: path.utf16.count), withTemplate: "file:///")
|
|
140
|
+
return modifiedPath.hasPrefix("file:///") ? modifiedPath : "file:///" + modifiedPath
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return path
|
|
144
|
+
}
|
|
145
|
+
|
|
130
146
|
}
|
|
@@ -371,7 +371,7 @@ class VideoCompressor {
|
|
|
371
371
|
}
|
|
372
372
|
return
|
|
373
373
|
} else if !videoPath.contains("ph://") {
|
|
374
|
-
completionHandler(videoPath)
|
|
374
|
+
completionHandler(Utils.slashifyFilePath(path: videoPath)!)
|
|
375
375
|
return
|
|
376
376
|
}
|
|
377
377
|
let assetId = videoPath.replacingOccurrences(of: "ph://", with: "")
|