diff --git a/README.md b/README.md index ba37784..49b76df 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ We should use **react-native-compressor** instead of **FFmpeg** because **react- * [Other Utilities](#api) - [Background Upload](#background-upload-1) - [Cancel Background Upload](#cancel-background-upload-1) + - [Get Metadata Of Image](#get-metadata-of-image) - [Get Metadata Of Video](#get-metadata-of-video) - [Get Real Path](#get-real-path) - [Get Temp file Path](#get-temp-file-path) @@ -438,6 +439,8 @@ await clearCache(); // this will clear cache of thumbnails cache directory - ###### `returnableOutputType: ReturnableOutputType` (default: uri) Can be either `uri` or `base64`, defines the Returnable output image format. +**if you wanna get image metadata (exif) then [read this](#get-metadata-of-image)** + ## Video - ###### `compress(url: string, options?: videoCompresssionType , onProgress?: (progress: number)): Promise` @@ -451,6 +454,9 @@ await clearCache(); // this will clear cache of thumbnails cache directory - ###### `deactivateBackgroundTask(): Promise` if you call `activateBackgroundTask` method, then after video compression, you should call `deactivateBackgroundTask` for disable background task mode. +- ###### `getCancellationId: function` + `getCancellationId` is a callback function that gives us compress video id, which can be used in `Video.cancelCompression` method to cancel the compression + ### videoCompresssionType - ###### `compressionMethod: compressionMethod` (default: "manual") @@ -477,8 +483,7 @@ await clearCache(); // this will clear cache of thumbnails cache directory previously default was 16 but now it is 0 by default. 0 mean 0mb. This is an offset, which you can set for minimumFileSizeForCompress will allow this package to dont compress less than or equal to `minimumFileSizeForCompress` ref [#26](https://github.com/numandev1/react-native-compressor/issues/26) -- ###### `getCancellationId: function` - `getCancellationId` is a callback function that gives us compress video id, which can be used in `Video.cancelCompression` method to cancel the compression +**if you wanna get video metadata then [read this](#get-metadata-of-video)** ## Audio @@ -597,6 +602,29 @@ const metaData = await getVideoMetaData(filePath); - ###### `getVideoMetaData(path: string)` +### Get Metadata Of Image + +if you want to get metadata of video than you can use this function + +```js +import { getImageMetaData } from 'react-native-compressor'; + +const metaData = await getImageMetaData(filePath); +``` + +``` +{ + "ImageWidth": 4032, + "ImageHeight": 3024, + "Orientation": 3, + "size": 4127057, + "extension": "jpg", + "exif":{...} +} +``` + +- ###### `getImageMetaData(path: string)` + ### Get Real Path if you want to convert diff --git a/android/src/main/java/com/reactnativecompressor/CompressorModule.kt b/android/src/main/java/com/reactnativecompressor/CompressorModule.kt index c878392..f758458 100644 --- a/android/src/main/java/com/reactnativecompressor/CompressorModule.kt +++ b/android/src/main/java/com/reactnativecompressor/CompressorModule.kt @@ -15,7 +15,6 @@ import com.reactnativecompressor.Utils.Uploader import com.reactnativecompressor.Utils.Utils import com.reactnativecompressor.Utils.Utils.generateCacheFilePath import com.reactnativecompressor.Utils.Utils.getRealPath -import com.reactnativecompressor.Utils.convertReadableMapToUploaderOptions import com.reactnativecompressor.Video.VideoMain class CompressorModule(private val reactContext: ReactApplicationContext) : CompressorSpec(reactContext) { @@ -47,6 +46,11 @@ class CompressorModule(private val reactContext: ReactApplicationContext) : Comp imageMain.image_compress(imagePath,optionMap,promise) } + @ReactMethod + override fun getImageMetaData(filePath: String, promise: Promise) { + imageMain.getImageMetaData(filePath,promise) + } + // VIdeo @ReactMethod override fun compress( diff --git a/android/src/main/java/com/reactnativecompressor/Image/ImageMain.kt b/android/src/main/java/com/reactnativecompressor/Image/ImageMain.kt index 7f46bf1..5b546c5 100644 --- a/android/src/main/java/com/reactnativecompressor/Image/ImageMain.kt +++ b/android/src/main/java/com/reactnativecompressor/Image/ImageMain.kt @@ -1,10 +1,15 @@ package com.reactnativecompressor.Image +import android.media.ExifInterface +import android.net.Uri +import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableMap import com.reactnativecompressor.Utils.MediaCache import com.reactnativecompressor.Utils.Utils +import com.reactnativecompressor.Utils.Utils.exifAttributes +import java.io.File class ImageMain(private val reactContext: ReactApplicationContext) { fun image_compress( @@ -28,4 +33,31 @@ class ImageMain(private val reactContext: ReactApplicationContext) { promise.reject(ex) } } + + fun getImageMetaData(filePath: String, promise: Promise) { + var filePath: String? = filePath + try { + filePath = Utils.getRealPath(filePath, reactContext) + val uri = Uri.parse(filePath) + val srcPath = uri.path + val params = Arguments.createMap() + val file = File(srcPath) + val sizeInKBs = (file.length() / 1024).toDouble() + val exif = ExifInterface(srcPath!!) + for (tag in exifAttributes) { + val value: String? = exif.getAttribute(tag) + if(value != null) + { + params.putString(tag, value) + } + + } + val extension = filePath!!.substring(filePath.lastIndexOf(".") + 1) + params.putDouble("size", sizeInKBs) + params.putString("extension", extension) + promise.resolve(params) + } catch (e: Exception) { + promise.reject(e) + } + } } diff --git a/android/src/oldarch/CompressorSpec.kt b/android/src/oldarch/CompressorSpec.kt index 18ca452..805e510 100644 --- a/android/src/oldarch/CompressorSpec.kt +++ b/android/src/oldarch/CompressorSpec.kt @@ -19,6 +19,7 @@ abstract class CompressorSpec(context: ReactApplicationContext?) : ReactContextB abstract fun generateFilePath(_extension: String, promise: Promise) abstract fun getRealPath(path: String, type: String, promise: Promise) abstract fun getVideoMetaData(filePath: String, promise: Promise) + abstract fun getImageMetaData(String filePath, Promise promise); abstract fun getFileSize(filePath: String, promise: Promise) abstract fun compress(fileUrl: String, optionMap: ReadableMap, promise: Promise) abstract fun cancelCompression(uuid: String) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index a1bb53e..f8d1acd 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -880,7 +880,7 @@ PODS: - React-Codegen - React-RCTFabric - ReactCommon/turbomodule/core - - react-native-compressor (1.8.17): + - react-native-compressor (1.8.19): - hermes-engine - RCT-Folly (= 2021.07.22.00) - RCTRequired @@ -1377,7 +1377,7 @@ SPEC CHECKSUMS: React-jsinspector: 194e32c6aab382d88713ad3dd0025c5f5c4ee072 React-logger: cebf22b6cf43434e471dc561e5911b40ac01d289 react-native-cameraroll: 5d9523136a929b58f092fd7f0a9a13367a4b46e3 - react-native-compressor: 8b6e302c4531f93aeaabd9658495bf9855adf3bd + react-native-compressor: 6b7e979126569ab20a5a53e17d569158463064e1 react-native-document-picker: c9ac93d7b511413f4a0ed61c92ff6c7b1bcf4f94 react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb react-native-image-picker: 9b4b1d0096500050cbdabf8f4fd00b771065d983 diff --git a/example/src/Screens/Image/index.tsx b/example/src/Screens/Image/index.tsx index 5eef3ba..34d9ae1 100644 --- a/example/src/Screens/Image/index.tsx +++ b/example/src/Screens/Image/index.tsx @@ -14,7 +14,7 @@ import type { ProgressBarRafType } from '../../Components/ProgressBar'; import * as ImagePicker from 'react-native-image-picker'; import CameraRoll from '@react-native-camera-roll/camera-roll'; import prettyBytes from 'pretty-bytes'; -import { Image, getFileSize } from 'react-native-compressor'; +import { Image, getFileSize, getImageMetaData } from 'react-native-compressor'; import { getFileInfo } from '../../Utils'; const Index = () => { const progressRef = useRef(); @@ -26,7 +26,7 @@ const Index = () => { const [orignalSize, setOrignalSize] = useState(''); const [compressedSize, setCompressedSize] = useState(0); - const compressHandler = (result: ImagePicker.ImagePickerResponse) => { + const compressHandler = async (result: ImagePicker.ImagePickerResponse) => { if (result.didCancel) { Alert.alert('Failed selecting Image'); return; @@ -40,9 +40,12 @@ const Index = () => { setMimeType(source.type); setOrignalUri(source.uri); } + const metadata = await getImageMetaData(source.uri); + console.log(JSON.stringify(metadata, null, 4), 'metadata'); Image.compress(source.uri) .then(async (compressedFileUri) => { console.log(compressedFileUri, 'compressedFileUri'); + setCommpressedUri(compressedFileUri); const detail: any = await getFileInfo(compressedFileUri); setCompressedSize(prettyBytes(parseInt(detail.size || 0))); diff --git a/ios/Compressor.mm b/ios/Compressor.mm index e5e4847..38e63b7 100644 --- a/ios/Compressor.mm +++ b/ios/Compressor.mm @@ -7,6 +7,10 @@ @interface RCT_EXTERN_MODULE(Compressor, RCTEventEmitter) withResolver: (RCTPromiseResolveBlock) resolve withRejecter: (RCTPromiseRejectBlock) reject) +RCT_EXTERN_METHOD(getImageMetaData: (NSString*) filePath + withResolver: (RCTPromiseResolveBlock) resolve + withRejecter: (RCTPromiseRejectBlock) reject) + RCT_EXTERN_METHOD(compress_audio: (NSString*) filePath withOptions: (NSDictionary*) optionsDict withResolver: (RCTPromiseResolveBlock) resolve diff --git a/ios/CompressorManager.swift b/ios/CompressorManager.swift index eddd780..9d4fb6d 100644 --- a/ios/CompressorManager.swift +++ b/ios/CompressorManager.swift @@ -35,6 +35,11 @@ class Compressor: RCTEventEmitter { ImageMain.image_compress(imagePath, optionMap: optionMap, resolve: resolve, reject: reject) } + @objc(getImageMetaData:withResolver:withRejecter:) + func getImageMetaData(_ filePath: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + ImageMain.getImageMetaData(filePath,resolve: resolve,reject: reject) + } + @objc(compress_audio:withOptions:withResolver:withRejecter:) func compress_audio(_ fileUrl: String, optionMap: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { AudioMain.compress_audio(fileUrl, optionMap: optionMap, resolve: resolve, reject: reject) diff --git a/ios/Image/ImageMain.swift b/ios/Image/ImageMain.swift index ab2404f..ef56062 100644 --- a/ios/Image/ImageMain.swift +++ b/ios/Image/ImageMain.swift @@ -24,4 +24,28 @@ class ImageMain { reject(error.localizedDescription, error.localizedDescription, nil) } } + + static func getImageMetaData(_ filePath: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + let options = ImageCompressorOptions.fromDictionary([:]) + ImageCompressor.getAbsoluteImagePath(filePath, options: options) { ImagePath in + if ImagePath.hasPrefix("file://") { + let absoluteImagePath = URL(string: ImagePath)!.path + Utils.getFileSize(absoluteImagePath) { fileSize in + let _extension = (absoluteImagePath as NSString).pathExtension + let imageData = NSData(contentsOfFile: absoluteImagePath)! + let imageSource = CGImageSourceCreateWithData(imageData, nil)! + let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)! + let exif=imageProperties as NSDictionary + let mutableExif = exif.mutableCopy() as! NSMutableDictionary + if let fileSizeInt = Int(fileSize as! String) { + mutableExif.setValue(fileSizeInt, forKey: "size") + } + mutableExif.setValue(_extension, forKey: "extension") + resolve(mutableExif) + } reject: { code, message, error in + reject(code, message, error) + } + } + } + } } diff --git a/package.json b/package.json index 646a0e6..4b2ecfd 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,8 @@ "thumbnail", "create-thumbnail", "video-metadata", - "media" + "media", + "bitrate" ], "repository": "https://github.com/numandev1/react-native-compressor", "author": "numandev1 (https://github.com/numandev1)", diff --git a/src/Spec/NativeCompressor.ts b/src/Spec/NativeCompressor.ts index 8027ab9..c44975c 100644 --- a/src/Spec/NativeCompressor.ts +++ b/src/Spec/NativeCompressor.ts @@ -4,6 +4,7 @@ import { TurboModuleRegistry } from 'react-native'; export interface Spec extends TurboModule { // Image image_compress(imagePath: string, optionMap: Object): Promise; + getImageMetaData(filePath: string): Promise; // Video compress(fileUrl: string, optionMap: Object): Promise; cancelCompression(uuid: string): void; diff --git a/src/index.tsx b/src/index.tsx index a2be04b..68d8925 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -8,6 +8,7 @@ import { generateFilePath, getRealPath, getVideoMetaData, + getImageMetaData, getFileSize, backgroundUpload, cancelUpload, @@ -31,6 +32,7 @@ export { generateFilePath, getRealPath, getVideoMetaData, + getImageMetaData, createVideoThumbnail, clearCache, getFileSize, @@ -47,6 +49,7 @@ export default { generateFilePath, getRealPath, getVideoMetaData, + getImageMetaData, getFileSize, backgroundUpload, createVideoThumbnail, diff --git a/src/utils/index.tsx b/src/utils/index.tsx index c08819f..8814158 100644 --- a/src/utils/index.tsx +++ b/src/utils/index.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-bitwise */ import { Compressor } from '../Main'; - +import { Platform } from 'react-native'; type qualityType = 'low' | 'medium' | 'high'; const INCORRECT_INPUT_PATH = 'Incorrect input path. Please provide a valid one'; @@ -40,6 +40,16 @@ type createVideoThumbnailType = ( }>; type clearCacheType = (cacheDir?: string) => Promise; + +type getImageMetaDataType = (filePath: string) => Promise<{ + ImageWidth: number; + ImageHeight: number; + Orientation: number; + size: number; + extension: string; + exif: { [key: string]: string }; +}>; + type getVideoMetaDataType = (filePath: string) => Promise<{ extension: string; size: number; @@ -68,6 +78,32 @@ export const getVideoMetaData: getVideoMetaDataType = (path: string) => { return Compressor.getVideoMetaData(path); }; +const unifyMetaData = (exifResult: any) => { + const output: any = {}; + const isIos = Platform.OS === 'ios'; + output.ImageWidth = isIos + ? exifResult?.PixelWidth + : parseInt(exifResult.ImageWidth); + + output.ImageHeight = isIos + ? exifResult?.PixelHeight + : parseInt(exifResult.ImageLength); + + output.Orientation = isIos + ? exifResult.Orientation + : parseInt(exifResult.Orientation); + + output.size = exifResult.size; + output.extension = exifResult.extension; + output.exif = exifResult; + return output; +}; + +export const getImageMetaData: getImageMetaDataType = async (path: string) => { + const result = await Compressor.getImageMetaData(path); + return unifyMetaData(result); +}; + export const createVideoThumbnail: createVideoThumbnailType = ( fileUrl, options = {}