@stefanmartin/expo-video-watermark 0.3.1 → 0.3.3
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.
|
@@ -254,34 +254,55 @@ class ExpoVideoWatermarkModule : Module() {
|
|
|
254
254
|
rawVideoWidth to rawVideoHeight
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
// Step 8:
|
|
258
|
-
val
|
|
259
|
-
val
|
|
260
|
-
val
|
|
257
|
+
// Step 8: Pre-scale watermark bitmap to match video width if needed
|
|
258
|
+
val originalWidth = watermarkBitmap.width
|
|
259
|
+
val originalHeight = watermarkBitmap.height
|
|
260
|
+
val targetWidth = videoWidth.toInt()
|
|
261
|
+
val scale = targetWidth.toFloat() / originalWidth.toFloat()
|
|
262
|
+
val targetHeight = (originalHeight * scale).toInt()
|
|
263
|
+
|
|
264
|
+
// Skip scaling if watermark already matches video width
|
|
265
|
+
val scaledWatermark: Bitmap = if (originalWidth == targetWidth) {
|
|
266
|
+
Log.d(TAG, "[Step 8] Watermark already matches video width (${originalWidth}x${originalHeight}), skipping scale")
|
|
267
|
+
watermarkBitmap
|
|
268
|
+
} else {
|
|
269
|
+
Log.d(TAG, "[Step 8] Pre-scaling watermark: ${originalWidth}x${originalHeight} -> ${targetWidth}x${targetHeight} (scale: $scale)")
|
|
270
|
+
try {
|
|
271
|
+
val scaled = Bitmap.createScaledBitmap(watermarkBitmap, targetWidth, targetHeight, true)
|
|
272
|
+
// Recycle original if we created a new scaled version
|
|
273
|
+
if (scaled !== watermarkBitmap) {
|
|
274
|
+
watermarkBitmap.recycle()
|
|
275
|
+
}
|
|
276
|
+
scaled
|
|
277
|
+
} catch (e: Exception) {
|
|
278
|
+
watermarkBitmap.recycle()
|
|
279
|
+
promise.reject("STEP8_SCALE_ERROR", "[Step 8] Failed to scale watermark bitmap: ${e.message}", e)
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
}
|
|
261
283
|
|
|
262
|
-
// Step 9: Create overlay settings for
|
|
284
|
+
// Step 9: Create overlay settings for bottom positioning (no GPU scaling needed)
|
|
263
285
|
// In Media3, coordinates are normalized: (0,0) is center
|
|
264
286
|
// x range [-1, 1] (left to right), y range [-1, 1] (bottom to top)
|
|
265
287
|
val overlaySettings = try {
|
|
266
288
|
StaticOverlaySettings.Builder()
|
|
267
|
-
.setScale(scale, scale) // Scale uniformly to match video width
|
|
268
289
|
.setOverlayFrameAnchor(0f, -1f) // Anchor at bottom-center of watermark
|
|
269
290
|
.setBackgroundFrameAnchor(0f, -1f) // Position at very bottom of video
|
|
270
291
|
.build()
|
|
271
292
|
} catch (e: Exception) {
|
|
272
|
-
|
|
293
|
+
scaledWatermark.recycle()
|
|
273
294
|
promise.reject("STEP9_OVERLAY_SETTINGS_ERROR", "[Step 9] Failed to create overlay settings: ${e.message}", e)
|
|
274
295
|
return
|
|
275
296
|
}
|
|
276
297
|
|
|
277
|
-
// Step 10: Create the bitmap overlay with
|
|
298
|
+
// Step 10: Create the bitmap overlay with pre-scaled bitmap
|
|
278
299
|
val bitmapOverlay = try {
|
|
279
300
|
BitmapOverlay.createStaticBitmapOverlay(
|
|
280
|
-
|
|
301
|
+
scaledWatermark,
|
|
281
302
|
overlaySettings
|
|
282
303
|
)
|
|
283
304
|
} catch (e: Exception) {
|
|
284
|
-
|
|
305
|
+
scaledWatermark.recycle()
|
|
285
306
|
promise.reject("STEP10_BITMAP_OVERLAY_ERROR", "[Step 10] Failed to create bitmap overlay: ${e.message}", e)
|
|
286
307
|
return
|
|
287
308
|
}
|
|
@@ -290,7 +311,7 @@ class ExpoVideoWatermarkModule : Module() {
|
|
|
290
311
|
val overlayEffect = try {
|
|
291
312
|
OverlayEffect(ImmutableList.of<TextureOverlay>(bitmapOverlay))
|
|
292
313
|
} catch (e: Exception) {
|
|
293
|
-
|
|
314
|
+
scaledWatermark.recycle()
|
|
294
315
|
promise.reject("STEP11_OVERLAY_EFFECT_ERROR", "[Step 11] Failed to create overlay effect: ${e.message}", e)
|
|
295
316
|
return
|
|
296
317
|
}
|
|
@@ -310,11 +331,24 @@ class ExpoVideoWatermarkModule : Module() {
|
|
|
310
331
|
.setEffects(effects)
|
|
311
332
|
.build()
|
|
312
333
|
} catch (e: Exception) {
|
|
313
|
-
|
|
334
|
+
scaledWatermark.recycle()
|
|
314
335
|
promise.reject("STEP14_EDITED_MEDIA_ERROR", "[Step 14] Failed to create edited media item: ${e.message}", e)
|
|
315
336
|
return
|
|
316
337
|
}
|
|
317
338
|
|
|
339
|
+
// Step 14b: Create composition with HDR to SDR tone mapping
|
|
340
|
+
// Use MediaCodec-based tone mapping (avoids OpenGL frame processing issues on some devices)
|
|
341
|
+
val composition = try {
|
|
342
|
+
Composition.Builder(listOf(editedMediaItem))
|
|
343
|
+
.setHdrMode(Composition.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC)
|
|
344
|
+
.build()
|
|
345
|
+
} catch (e: Exception) {
|
|
346
|
+
scaledWatermark.recycle()
|
|
347
|
+
promise.reject("STEP14B_COMPOSITION_ERROR", "[Step 14b] Failed to create composition: ${e.message}", e)
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
Log.d(TAG, "[Step 14b] Created composition with HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC")
|
|
351
|
+
|
|
318
352
|
// Handler for main thread callbacks
|
|
319
353
|
val mainHandler = Handler(Looper.getMainLooper())
|
|
320
354
|
|
|
@@ -330,8 +364,6 @@ class ExpoVideoWatermarkModule : Module() {
|
|
|
330
364
|
val transformer = Transformer.Builder(context)
|
|
331
365
|
// Force H.264 output for maximum compatibility
|
|
332
366
|
.setVideoMimeType(MimeTypes.VIDEO_H264)
|
|
333
|
-
// Enable HDR to SDR tone mapping for videos with HDR content
|
|
334
|
-
.setHdrMode(Transformer.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL)
|
|
335
367
|
.addListener(object : Transformer.Listener {
|
|
336
368
|
override fun onCompleted(composition: Composition, exportResult: ExportResult) {
|
|
337
369
|
Log.d(TAG, "[Step 15] Transform completed successfully")
|
|
@@ -340,7 +372,7 @@ class ExpoVideoWatermarkModule : Module() {
|
|
|
340
372
|
"averageAudioBitrate: ${exportResult.averageAudioBitrate}, " +
|
|
341
373
|
"averageVideoBitrate: ${exportResult.averageVideoBitrate}, " +
|
|
342
374
|
"videoFrameCount: ${exportResult.videoFrameCount}")
|
|
343
|
-
|
|
375
|
+
scaledWatermark.recycle()
|
|
344
376
|
|
|
345
377
|
// Step 16: Re-encode to H.265 if device supports HEVC encoder
|
|
346
378
|
val supportsHevc = hasHevcEncoder()
|
|
@@ -454,7 +486,8 @@ class ExpoVideoWatermarkModule : Module() {
|
|
|
454
486
|
appendLine("Input path: $cleanVideoPath")
|
|
455
487
|
appendLine()
|
|
456
488
|
appendLine("--- Watermark Info ---")
|
|
457
|
-
appendLine("
|
|
489
|
+
appendLine("Original dimensions: ${originalWidth}x${originalHeight}")
|
|
490
|
+
appendLine("Scaled dimensions: ${targetWidth}x${targetHeight}")
|
|
458
491
|
appendLine("Bitmap info: $bitmapInfo")
|
|
459
492
|
appendLine("Scale factor: $scale")
|
|
460
493
|
appendLine()
|
|
@@ -480,14 +513,14 @@ class ExpoVideoWatermarkModule : Module() {
|
|
|
480
513
|
causeLevel++
|
|
481
514
|
}
|
|
482
515
|
|
|
483
|
-
|
|
516
|
+
scaledWatermark.recycle()
|
|
484
517
|
|
|
485
518
|
// Reject with comprehensive error message
|
|
486
519
|
val errorMessage = "[Step 15] Transform failed - " +
|
|
487
520
|
"ErrorCode: $errorCodeName (${exportException.errorCode}), " +
|
|
488
521
|
"Device: ${Build.MANUFACTURER} ${Build.MODEL} (API ${Build.VERSION.SDK_INT}), " +
|
|
489
522
|
"Video: ${videoWidth.toInt()}x${videoHeight.toInt()} $mimeType, " +
|
|
490
|
-
"Watermark: ${
|
|
523
|
+
"Watermark: ${originalWidth}x${originalHeight} -> ${targetWidth}x${targetHeight}, " +
|
|
491
524
|
"Scale: $scale, " +
|
|
492
525
|
"Message: ${exportException.message ?: "Unknown error"}"
|
|
493
526
|
|
|
@@ -501,13 +534,13 @@ class ExpoVideoWatermarkModule : Module() {
|
|
|
501
534
|
.build()
|
|
502
535
|
|
|
503
536
|
Log.d(TAG, "[Step 15] Transformer built, starting export...")
|
|
504
|
-
transformer.start(
|
|
537
|
+
transformer.start(composition, cleanOutputPath)
|
|
505
538
|
Log.d(TAG, "[Step 15] Transformer.start() called, waiting for completion...")
|
|
506
539
|
} catch (e: Exception) {
|
|
507
540
|
Log.e(TAG, "[Step 15] Exception building/starting transformer", e)
|
|
508
541
|
Log.e(TAG, "[Step 15] Device info: $deviceInfo")
|
|
509
542
|
Log.e(TAG, "[Step 15] GL info: $glInfo")
|
|
510
|
-
|
|
543
|
+
scaledWatermark.recycle()
|
|
511
544
|
promise.reject(
|
|
512
545
|
"STEP15_TRANSFORMER_BUILD_ERROR",
|
|
513
546
|
"[Step 15] Failed to build/start transformer on ${Build.MANUFACTURER} ${Build.MODEL}: ${e.message}",
|