Skip to content
Merged
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,10 @@ 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)

- ###### `preserveMetadata: boolean` (default: true)

By default the output keeps the source video's metadata, such as its GPS location and creation date. Pass `false` to leave it out, for example when the compressed video leaves the device and the location should not go with it. With `false`, a video at or under `minimumFileSizeForCompress` is still compressed, because returning the source file would keep its metadata.

**if you wanna get video metadata then [read this](#get-metadata-of-video)**

## Audio
Expand Down
10 changes: 10 additions & 0 deletions __tests__/compressor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ describe('react-native-compressor JS wrapper API', () => {
);
});

it('forwards preserveMetadata only when the caller opts out of source metadata', async () => {
mockCompressor.compress.mockResolvedValue('file://no-metadata.mp4');

await api.Video.compress(localVideoUri, { compressionMethod: 'manual', preserveMetadata: false });
await api.Video.compress(localVideoUri, { compressionMethod: 'manual', preserveMetadata: true });

expect(mockCompressor.compress.mock.calls[0]?.[1]).toEqual(expect.objectContaining({ preserveMetadata: false }));
expect(mockCompressor.compress.mock.calls[1]?.[1]).not.toHaveProperty('preserveMetadata');
});

it('proxies video cancellation and background task lifecycle', async () => {
mockCompressor.activateBackgroundTask.mockImplementation(async (_options, onExpired) => {
onExpired?.();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ object Utils {
}

@JvmStatic
fun compressVideo(srcPath: String, destinationPath: String, resultWidth: Int, resultHeight: Int, videoBitRate: Float, frameRate: Int, uuid: String,progressDivider:Int, stripAudio: Boolean = false, promise: Promise, reactContext: ReactApplicationContext) {
fun compressVideo(srcPath: String, destinationPath: String, resultWidth: Int, resultHeight: Int, videoBitRate: Float, frameRate: Int, uuid: String,progressDivider:Int, stripAudio: Boolean = false, preserveMetadata: Boolean = true, promise: Promise, reactContext: ReactApplicationContext) {
val currentVideoCompression = intArrayOf(0)
val videoCompressorClass: VideoCompressorClass? = VideoCompressorClass(reactContext);
compressorExports[uuid] = videoCompressorClass
videoCompressorClass?.start(
srcPath, destinationPath, resultWidth, resultHeight, videoBitRate.toInt(), frameRate, stripAudio,
srcPath, destinationPath, resultWidth, resultHeight, videoBitRate.toInt(), frameRate, stripAudio, preserveMetadata,
listener = object : CompressionListener {
override fun onProgress(index: Int, percent: Float) {
if (percent <= 100) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ object AutoVideoCompression {
val file = File(srcPath)
val sizeInBytes = file.length().toFloat()
val sizeInMb = sizeInBytes / (1024 * 1024)
if (sizeInMb > minimumFileSizeForCompress) {
if (sizeInMb > minimumFileSizeForCompress || !options.preserveMetadata) {
val destinationPath = generateCacheFilePath("mp4", reactContext!!)
val actualHeight = VideoCompressorHelper.getMetadataInt(metaRetriever, MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
val actualWidth = VideoCompressorHelper.getMetadataInt(metaRetriever, MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
Expand All @@ -46,6 +46,7 @@ object AutoVideoCompression {
options.uuid!!,
options.progressDivider!!,
options.stripAudio,
options.preserveMetadata,
Comment thread
Copilot marked this conversation as resolved.
Resolved
promise,
reactContext,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class VideoCompressorClass(private val context: ReactApplicationContext) {
bitrate: Int,
frameRate: Int,
stripAudio: Boolean = false,
preserveMetadata: Boolean = true,
listener: CompressionListener,
) {
val uris = mutableListOf<Uri>()
Expand All @@ -40,6 +41,7 @@ class VideoCompressorClass(private val context: ReactApplicationContext) {
bitrate,
frameRate,
stripAudio,
preserveMetadata,
listener,
destPath
)
Expand All @@ -58,6 +60,7 @@ class VideoCompressorClass(private val context: ReactApplicationContext) {
bitrate: Int,
frameRate: Int,
stripAudio: Boolean,
preserveMetadata: Boolean,
listener: CompressionListener,
destPath: String
) {
Expand All @@ -82,6 +85,7 @@ class VideoCompressorClass(private val context: ReactApplicationContext) {
bitrate,
frameRate,
stripAudio,
preserveMetadata,
listener,
)

Expand All @@ -106,6 +110,7 @@ class VideoCompressorClass(private val context: ReactApplicationContext) {
bitrate: Int,
frameRate: Int,
disableAudio: Boolean = false,
preserveMetadata: Boolean = true,
listener: CompressionListener,
): Result = withContext(Dispatchers.Default) {
return@withContext compressVideo(
Expand All @@ -119,6 +124,7 @@ class VideoCompressorClass(private val context: ReactApplicationContext) {
bitrate,
frameRate,
disableAudio,
preserveMetadata,
object : CompressionProgressListener {
override fun onProgressChanged(index: Int, percent: Float) {
listener.onProgress(index, percent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ object Compressor {
outputBitrate: Int,
outputFrameRate: Int,
disableAudio: Boolean = false,
preserveMetadata: Boolean = true,
listener: CompressionProgressListener,
): Result = withContext(Dispatchers.Default) {

Expand Down Expand Up @@ -109,16 +110,20 @@ object Compressor {
// MediaMetadataRetriever only reads moov/udta/©xyz — returning null
// (or empty) and dropping GPS. Fall back to a raw MP4 walker that
// scans the whole file for every known location encoding.
val retrievedLocation =
val retrievedLocation = if (preserveMetadata) {
mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
val locationData = if (!retrievedLocation.isNullOrEmpty()) {
retrievedLocation
} else {
LocationExtractor.extract(context, srcUri)
null
}
val locationData = when {
!preserveMetadata -> null
!retrievedLocation.isNullOrEmpty() -> retrievedLocation
else -> LocationExtractor.extract(context, srcUri)
}
// Never log the resolved ISO 6709 string — it is the user's exact GPS
// coordinates. Log only presence and which mechanism resolved it.
val locationSource = when {
!preserveMetadata -> "not preserved"
!retrievedLocation.isNullOrEmpty() -> "retriever"
!locationData.isNullOrEmpty() -> "extractor"
else -> "none"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class VideoCompressorHelper {
var progressDivider: Int? = 0
var minimumFileSizeForCompress = 0.0f
var stripAudio = false
var preserveMetadata = true

companion object {
private var _reactContext: ReactApplicationContext? = null
Expand Down Expand Up @@ -89,6 +90,7 @@ class VideoCompressorHelper {
"bitrate" -> options.bitrate = map.getDouble(key).toFloat()
"progressDivider" -> options.progressDivider = map.getInt(key)
"stripAudio" -> options.stripAudio = map.getBoolean(key)
"preserveMetadata" -> options.preserveMetadata = map.getBoolean(key)
}
}
return options
Expand Down Expand Up @@ -159,6 +161,7 @@ class VideoCompressorHelper {
options.uuid!!,
options.progressDivider!!,
options.stripAudio,
options.preserveMetadata,
promise,
reactContext,
)
Expand Down
23 changes: 14 additions & 9 deletions ios/Video/VideoMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ class VideoCompressor {
{
minimumFileSizeForCompress=(options["minimumFileSizeForCompress"] as? NSNumber)?.doubleValue ?? 0;
}
if(fileSize>minimumFileSizeForCompress)
let preserveMetadata=options["preserveMetadata"] as? Bool ?? true
if(fileSize>minimumFileSizeForCompress || !preserveMetadata)
{
if(options["compressionMethod"] as! String=="auto")
{
Expand Down Expand Up @@ -268,6 +269,7 @@ class VideoCompressor {
let uuid:String = options["uuid"] as! String
let progressDivider=(options["progressDivider"] as? NSNumber)?.intValue ?? 0
let stripAudio=options["stripAudio"] as? Bool ?? false
let preserveMetadata=options["preserveMetadata"] as? Bool ?? true
Comment thread
Copilot marked this conversation as resolved.
Resolved

let asset = AVAsset(url: url)
guard let track = getVideoTrack(asset: asset) else {
Expand All @@ -277,7 +279,7 @@ class VideoCompressor {
}
let profile = createCompressionProfile(track: track, maxSize: CGFloat(maxSize), requestedBitrate: nil)

exportVideoHelper(url: url, asset: asset, bitRate: profile.bitrate, frameRate: profile.frameRate, resultWidth: profile.width, resultHeight: profile.height,uuid: uuid,progressDivider: progressDivider, stripAudio: stripAudio) { progress in
exportVideoHelper(url: url, asset: asset, bitRate: profile.bitrate, frameRate: profile.frameRate, resultWidth: profile.width, resultHeight: profile.height,uuid: uuid,progressDivider: progressDivider, stripAudio: stripAudio, preserveMetadata: preserveMetadata) { progress in
onProgress(progress)
} onCompletion: { outputURL in
onCompletion(outputURL)
Expand All @@ -291,6 +293,7 @@ class VideoCompressor {
let bitRate = (options["bitrate"] as? NSNumber)?.intValue
let progressDivider=(options["progressDivider"] as? NSNumber)?.intValue ?? 0
let stripAudio=options["stripAudio"] as? Bool ?? false
let preserveMetadata=options["preserveMetadata"] as? Bool ?? true
let asset = AVAsset(url: url)
guard let track = getVideoTrack(asset: asset) else {
let error = CompressionError(message: "Invalid video URL, no track found")
Expand All @@ -300,7 +303,7 @@ class VideoCompressor {
let maxSize = (options["maxSize"] as? NSNumber)?.floatValue ?? Float(1920)
let profile = createCompressionProfile(track: track, maxSize: CGFloat(maxSize), requestedBitrate: bitRate)

exportVideoHelper(url: url, asset: asset, bitRate: profile.bitrate, frameRate: profile.frameRate, resultWidth: profile.width, resultHeight: profile.height,uuid: uuid,progressDivider: progressDivider, stripAudio: stripAudio) { progress in
exportVideoHelper(url: url, asset: asset, bitRate: profile.bitrate, frameRate: profile.frameRate, resultWidth: profile.width, resultHeight: profile.height,uuid: uuid,progressDivider: progressDivider, stripAudio: stripAudio, preserveMetadata: preserveMetadata) { progress in
onProgress(progress)
} onCompletion: { outputURL in
onCompletion(outputURL)
Expand All @@ -309,7 +312,7 @@ class VideoCompressor {
}
}

func exportVideoHelper(url: URL,asset: AVAsset, bitRate: Int, frameRate: Int, resultWidth:Int,resultHeight:Int,uuid:String,progressDivider: Int, stripAudio: Bool = false, onProgress: @escaping (Float) -> Void, onCompletion: @escaping (URL) -> Void, onFailure: @escaping (Error) -> Void){
func exportVideoHelper(url: URL,asset: AVAsset, bitRate: Int, frameRate: Int, resultWidth:Int,resultHeight:Int,uuid:String,progressDivider: Int, stripAudio: Bool = false, preserveMetadata: Bool = true, onProgress: @escaping (Float) -> Void, onCompletion: @escaping (URL) -> Void, onFailure: @escaping (Error) -> Void){
var currentVideoCompression:Int=0

var tmpURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
Expand Down Expand Up @@ -350,12 +353,14 @@ class VideoCompressor {
}

// Preserve source metadata (location, creation date, etc.) by forwarding
// every available metadata format to the writer.
var preservedMetadata: [AVMetadataItem] = asset.metadata
for format in asset.availableMetadataFormats {
preservedMetadata.append(contentsOf: asset.metadata(forFormat: format))
// every available metadata format to the writer, unless the caller opted out.
if preserveMetadata {
var preservedMetadata: [AVMetadataItem] = asset.metadata
for format in asset.availableMetadataFormats {
preservedMetadata.append(contentsOf: asset.metadata(forFormat: format))
}
exporter.metadata = preservedMetadata
}
exporter.metadata = preservedMetadata

compressorExports[uuid] = exporter
exporter.export(progressHandler: { (progress) in
Expand Down
10 changes: 10 additions & 0 deletions src/Video/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ type videoCompresssionType = {
* Default: false
*/
stripAudio?: boolean;
/***
* When false, the source video's metadata (its GPS location, creation date, etc.) is not copied
* into the output video.
* When false, a video at or under minimumFileSizeForCompress is still compressed.
* Default: true
*/
preserveMetadata?: boolean;
};

export type VideoCompressorType = {
Expand Down Expand Up @@ -56,6 +63,9 @@ const Video: VideoCompressorType = {
if (options?.stripAudio) {
modifiedOptions.stripAudio = options.stripAudio;
}
if (options?.preserveMetadata === false) {
modifiedOptions.preserveMetadata = false;
}
if (options?.getCancellationId) {
options?.getCancellationId(uuid);
}
Expand Down
Loading