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.
@@ -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 encodeImage(imageDataByteArrayOutputStream: ByteArrayOutputStream, isBase64: Boolean, outputExtension: String?, reactContext: ReactApplicationContext?): String? {
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(srcPath, destinationPath, resultWidth, resultHeight, videoBitRate.toInt(),
35
- listener = object : CompressionListener {
36
- override fun onProgress(index: Int, percent: Float) {
37
- if (percent <= 100)
38
- {
39
- val roundProgress = Math.round(percent)
40
- if (progressDivider==0||(roundProgress % progressDivider == 0 && roundProgress > currentVideoCompression[0])) {
41
- EventEmitterHandler.emitVideoCompressProgress((percent / 100).toDouble(),uuid)
42
- currentVideoCompression[0] = roundProgress
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
- override fun onStart(index: Int) {
48
+ override fun onStart(index: Int) {
48
49
 
49
- }
50
+ }
50
51
 
51
- override fun onSuccess(index: Int, size: Long, path: String?) {
52
- val fileUrl = "file://$destinationPath"
53
- //convert finish,result(true is success,false is fail)
54
- promise.resolve(fileUrl)
55
- MediaCache.removeCompletedImagePath(fileUrl)
56
- currentVideoCompression[0] = 0
57
- compressorExports[uuid]=null
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
 
@@ -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: "")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-compressor",
3
- "version": "1.8.13",
3
+ "version": "1.8.15",
4
4
  "description": "This library compress image, video and audio",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",