Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 0 additions & 64 deletions .yarn/patches/react-native-video-npm-6.19.1-2c06079fd0.patch

This file was deleted.

940 changes: 0 additions & 940 deletions .yarn/releases/yarn-4.14.1.cjs

This file was deleted.

1,000 changes: 1,000 additions & 0 deletions .yarn/releases/yarn-4.18.0.cjs

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ nmHoistingLimits: workspaces

nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-4.14.1.cjs
npmMinimalAgeGate: 7

yarnPath: .yarn/releases/yarn-4.18.0.cjs
98 changes: 98 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
all:

ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
BARE_DIR := $(ROOT_DIR)/examples/bare
EXPO_DIR := $(ROOT_DIR)/examples/expo
VERSION := $(shell awk '/version/{gsub(/("|",)/,"",$$2);print $$2};' package.json)

install: # Yarn install (root + example workspaces)
yarn install

pod-install: # Pod install for the bare example
cd $(BARE_DIR) && bundle install
cd $(BARE_DIR) && bundle exec pod install --project-directory=ios/ --repo-update

sync: install pod-install

nitrogen: # Regenerate Nitro native bindings into nitrogen/generated/
yarn nitrogen

prepack: # nitrogen + build the publishable lib/
yarn prepack

run-ios: # Run the bare example on an iOS device
yarn example:bare ios

run-ios-sim: # Run the bare example on an iOS simulator
IOS_SIMULATOR="iPhone 17 Pro" yarn example:bare ios

run-android: # Run the bare example on an Android device or emulator
yarn example:bare android

run-bundler: # Run metro bundler for the bare example
watchman watch-del $(ROOT_DIR)
watchman watch-project $(ROOT_DIR)
yarn example:bare start --reset-cache

run-expo-ios: # Run the expo example on iOS
yarn example:expo ios

run-expo-android: # Run the expo example on Android
yarn example:expo android

run-expo-bundler: # Run the expo bundler
yarn example:expo start

build-android: # Native Android compile check (arm64-v8a)
yarn build:android

build-ios: # Native iOS compile check (simulator)
yarn build:ios

test: # Jest unit tests (native mocked)
yarn test

typecheck: # tsc --noEmit
yarn typecheck

lint: # eslint over **/*.{js,ts,tsx}
yarn lint

lint-fix: # eslint --fix
yarn lint --fix

test-pr: # Full PR gate: test + typecheck + lint
yarn test:pr

test-harness-android: # On-device harness tests (requires an Android emulator)
yarn test:harness:android

test-harness-ios: # On-device harness tests (requires an iOS simulator)
yarn test:harness:ios

clean: # Clean native build artifacts
yarn clean

clean-deps: # Remove lockfile-installed dependencies
rm -rf node_modules
rm -rf $(BARE_DIR)/node_modules
rm -rf $(EXPO_DIR)/node_modules

purge: # Deep clean repository (removes all untracked files/dirs)
git clean -xdf

refresh: clean-deps install pod-install

resync: purge install pod-install # Full reinstall with deep clean

open-android:
open -a /Applications/Android\ Studio.app

open-ios:
open $(BARE_DIR)/ios/BareExample.xcworkspace

check-dep:
yarn outdated

release: # Cut a release (release-it + conventional-changelog)
yarn release
163 changes: 149 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ 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)**
**if you wanna get image metadata (dimensions, orientation, GPS, camera settings) then [read this](#get-metadata-of-image)**

## Video

Expand Down Expand Up @@ -590,41 +590,176 @@ import { getVideoMetaData } from 'react-native-compressor';
const metaData = await getVideoMetaData(filePath);
```

```
```json
{
"duration": 20.11,
"extension": "mp4",
"height": 1080,
"size": 16940.0,
"width": 1920
"extension": "mp4",
"size": 29820480,
"duration": 10.0,

"width": 1920,
"height": 1080,
"displayWidth": 1080,
"displayHeight": 1920,
"rotation": 90,

"hasVideo": true,
"hasAudio": true,
"isHDR": false,
"colorTransfer": "sdr",
"fileName": "IMG_0042.mp4",

"bitrate": 17842176,
"frameRate": 30,
"frameCount": 300,
"codec": "hevc",
"trackCount": 2,

"location": "+37.5090-122.2620/",
"latitude": 37.509,
"longitude": -122.262,

"audioBitrate": 128000,
"audioSampleRate": 44100,
"audioChannels": 2
}
```

**Always present on both platforms:** `extension`, `size`, `duration`, `width`, `height`,
`displayWidth`, `displayHeight`, `rotation`, `hasVideo`, `hasAudio`, `isHDR`, `colorTransfer`,
`fileName`. Everything else appears only when the file carries it.

| Field | Meaning |
| --- | --- |
| `width` / `height` | The **stored** track dimensions, before rotation |
| `displayWidth` / `displayHeight` | Dimensions as the video is meant to be shown, with rotation applied |
| `rotation` | `0`, `90`, `180` or `270`. Phone recordings store a landscape track plus a rotation flag, so comparing `width` to `height` is *not* how you detect a portrait video — read this instead |
| `duration` | Seconds |
| `bitrate` | Bits per second. The video track's own rate where the platform reports it, otherwise the container's overall rate |
| `codec` | Normalized lowercase name — `"h264"`, `"hevc"`, `"av1"`, `"vp9"` |
| `colorTransfer` | One of `"sdr"`, `"hlg"`, `"pq"`, `"linear"` |
| `isHDR` | True for HLG and PQ. HDR footage compressed as if it were SDR comes out washed out |
| `location` | Raw ISO-6709 string |
| `latitude` / `longitude` | Signed decimal degrees, parsed natively |
| `frameCount` | **Android only.** Reading it on iOS would require a full pass over the video samples |

A file with no video track (an audio-only file) resolves with `hasVideo: false` and zeroed
dimensions rather than rejecting. A corrupt or unreadable file rejects.

- ###### `getVideoMetaData(path: string)`

### Get Metadata Of Image

if you want to get metadata of video than you can use this function
if you want to get metadata of an image than you can use this function

```js
import { getImageMetaData } from 'react-native-compressor';

const metaData = await getImageMetaData(filePath);
```

```
```json
{
"ImageWidth": 4032,
"ImageHeight": 3024,
"Orientation": 3,
"size": 4127057,
"width": 4032,
"height": 3024,
"displayWidth": 3024,
"displayHeight": 4032,
"rotation": 90,
"isMirrored": false,
"orientation": 6,

"size": 3145728,
"extension": "jpg",
"exif":{...}
"mimeType": "image/jpeg",
"hasAlpha": false,
"fileName": "IMG_0042.jpg",

"isAnimated": false,
"frameCount": 1,
"bitDepth": 8,
"colorSpace": "Display P3",
"isHDR": false,
"dpiX": 72,
"dpiY": 72,

"latitude": 37.509,
"longitude": -122.262,
"altitude": 12.4,

"make": "Apple",
"model": "iPhone 15 Pro",
"lensModel": "iPhone 15 Pro back triple camera 6.86mm f/1.78",
"software": "17.2",
"iso": 64,
"exposureTime": 0.0166,
"focalLength": 6.86,
"flash": false
}
```

**Always present on both platforms:** `width`, `height`, `displayWidth`, `displayHeight`,
`rotation`, `isMirrored`, `orientation`, `size`, `extension`, `mimeType`, `hasAlpha`, `fileName`.
Everything else appears only when the file carries it — a missing EXIF block is normal (PNGs,
screenshots, most edited images) and is not an error.

| Field | Meaning |
| --- | --- |
| `width` / `height` | True pixel dimensions from the decoder — never the EXIF tag, so these are correct for images with no EXIF |
| `displayWidth` / `displayHeight` | Dimensions after orientation is applied — swapped when `orientation` is 5–8 |
| `rotation` | `0`, `90`, `180` or `270` |
| `isMirrored` | True for orientations 2, 4, 5 and 7 |
| `orientation` | The raw EXIF value 1–8, as a number. Missing or `0` is reported as `1` |
| `latitude` / `longitude` | Signed decimal degrees — already negated for the southern and western hemispheres |
| `altitude` | Metres, negative below sea level |
| `exposureTime` | Seconds as a number (`0.0166`), not the `"1/60"` string |
| `hasAlpha` | Read exactly on iOS. **Inferred from the container on Android** (PNG/WebP/GIF), since confirming it would mean decoding the bitmap |
| `isHDR` | **iOS only.** Detecting it on Android is unreliable, so the key is omitted there rather than guessed |

Orientation is the piece most often got wrong, so the full mapping:

| `orientation` | `rotation` | `isMirrored` | swaps dimensions |
| --- | --- | --- | --- |
| 1 | 0 | false | no |
| 2 | 0 | true | no |
| 3 | 180 | false | no |
| 4 | 180 | true | no |
| 5 | 90 | true | **yes** |
| 6 | 90 | false | **yes** |
| 7 | 270 | true | **yes** |
| 8 | 270 | false | **yes** |

The image is never fully decoded, so a 100 MP file costs no more memory than a thumbnail.

- ###### `getImageMetaData(path: string)`

#### Migrating from v2

`getImageMetaData` returned differently-shaped data on each platform, so the keys were renamed and
the raw `exif` passthrough — an unnormalized platform dump, nested `{Exif}` / `{GPS}` / `{TIFF}`
dictionaries on iOS and a flat all-strings tag map on Android — was removed. Every value it carried
that is worth reading is now a normalized, identically-shaped, top-level key.

| v2 | v3 |
| --- | --- |
| `ImageWidth` | `width` (and it is no longer `NaN` on Android for images without EXIF) |
| `ImageHeight` | `height` |
| `Orientation` | `orientation` (plus the derived `rotation`, `isMirrored`, `displayWidth`, `displayHeight`) |
| `exif['{GPS}'].Latitude` / `GPSLatitude` | `latitude` — signed, no reference letter to apply |
| `exif['{GPS}'].Longitude` / `GPSLongitude` | `longitude` |
| `exif['{GPS}'].Altitude` / `GPSAltitude` | `altitude` |
| `exif['{TIFF}'].Make` / `Make` | `make` |
| `exif['{TIFF}'].Model` / `Model` | `model` |
| `exif['{TIFF}'].Software` / `Software` | `software` |
| `exif['{Exif}'].LensModel` / `LensModel` | `lensModel` |
| `exif['{Exif}'].ISOSpeedRatings` / `ISOSpeedRatings` | `iso` |
| `exif['{Exif}'].ExposureTime` / `ExposureTime` | `exposureTime` (seconds, as a number) |
| `exif['{Exif}'].FocalLength` / `FocalLength` | `focalLength` |
| `exif['{Exif}'].Flash` / `Flash` | `flash` (boolean: did the flash fire) |
| `exif.PixelWidth` (iOS) / `exif.ImageWidth` (Android) | `width` — the `Platform.OS` branch is gone |

`getVideoMetaData` keeps every key it had. The undeclared, Android-only `creationTime` — which
returned a raw `"20240115T123456.000Z"` string, or the literal string `"null"` when the file had no
date — was removed.

### Get Real Path

if you want to convert
Expand Down
6 changes: 6 additions & 0 deletions TRIAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,9 @@ These should be closed upstream unless a current repro still exists on the lates
- iOS: return background-upload response bodies consistently
- Android: annotate the `HybridCompressor` impl with `@DoNotStrip @Keep` so the Nitro `Compressor` HybridObject (instantiated via JNI by name) survives R8 in consumer release builds (#406)
- Android: rewrite `NitroPromiseAdapter` in Java so the Nitro module compiles across all React Native versions regardless of `Promise.reject`'s `code` nullability (#404)
- iOS: `getVideoMetaData` could never settle — audio-only files and paths not starting with `file://` fell through every branch without resolving or rejecting, hanging the caller's `await` forever. Every path now settles
- Android: `getVideoMetaData` threw a null-pointer exception on audio-only and unusual files, because width/height/duration were read with `!!` and those keys are legitimately absent
- Android: `getVideoMetaData` leaked its `MediaMetadataRetriever` — opened and never released on any path. Now released in a `finally`
- Android: `getImageMetaData` returned `NaN` for `ImageWidth`/`ImageHeight` on any image without EXIF (PNGs, screenshots, WebP, most edited images), because dimensions were read from the EXIF tags rather than the decoder. Now read from a bounds-only header decode
- Android: `getImageMetaData` could reject the whole call for an image with no EXIF block, and could not read HEIC/WebP/PNG/DNG metadata on many devices (deprecated framework EXIF reader, now AndroidX)
- Android/iOS: widened both metadata responses and made them identical in shape across platforms — rotation and display dimensions, codec, bitrate, HDR/colour transfer, audio track details, parsed GPS. Breaking: the image response renames `ImageWidth`/`ImageHeight`/`Orientation` and drops the raw `exif` passthrough; the video response drops the undeclared Android-only `creationTime`
Loading
Loading