@stefanmartin/expo-video-watermark 0.3.2 → 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
|
|
|
@@ -338,7 +372,7 @@ class ExpoVideoWatermarkModule : Module() {
|
|
|
338
372
|
"averageAudioBitrate: ${exportResult.averageAudioBitrate}, " +
|
|
339
373
|
"averageVideoBitrate: ${exportResult.averageVideoBitrate}, " +
|
|
340
374
|
"videoFrameCount: ${exportResult.videoFrameCount}")
|
|
341
|
-
|
|
375
|
+
scaledWatermark.recycle()
|
|
342
376
|
|
|
343
377
|
// Step 16: Re-encode to H.265 if device supports HEVC encoder
|
|
344
378
|
val supportsHevc = hasHevcEncoder()
|
|
@@ -452,7 +486,8 @@ class ExpoVideoWatermarkModule : Module() {
|
|
|
452
486
|
appendLine("Input path: $cleanVideoPath")
|
|
453
487
|
appendLine()
|
|
454
488
|
appendLine("--- Watermark Info ---")
|
|
455
|
-
appendLine("
|
|
489
|
+
appendLine("Original dimensions: ${originalWidth}x${originalHeight}")
|
|
490
|
+
appendLine("Scaled dimensions: ${targetWidth}x${targetHeight}")
|
|
456
491
|
appendLine("Bitmap info: $bitmapInfo")
|
|
457
492
|
appendLine("Scale factor: $scale")
|
|
458
493
|
appendLine()
|
|
@@ -478,14 +513,14 @@ class ExpoVideoWatermarkModule : Module() {
|
|
|
478
513
|
causeLevel++
|
|
479
514
|
}
|
|
480
515
|
|
|
481
|
-
|
|
516
|
+
scaledWatermark.recycle()
|
|
482
517
|
|
|
483
518
|
// Reject with comprehensive error message
|
|
484
519
|
val errorMessage = "[Step 15] Transform failed - " +
|
|
485
520
|
"ErrorCode: $errorCodeName (${exportException.errorCode}), " +
|
|
486
521
|
"Device: ${Build.MANUFACTURER} ${Build.MODEL} (API ${Build.VERSION.SDK_INT}), " +
|
|
487
522
|
"Video: ${videoWidth.toInt()}x${videoHeight.toInt()} $mimeType, " +
|
|
488
|
-
"Watermark: ${
|
|
523
|
+
"Watermark: ${originalWidth}x${originalHeight} -> ${targetWidth}x${targetHeight}, " +
|
|
489
524
|
"Scale: $scale, " +
|
|
490
525
|
"Message: ${exportException.message ?: "Unknown error"}"
|
|
491
526
|
|
|
@@ -499,13 +534,13 @@ class ExpoVideoWatermarkModule : Module() {
|
|
|
499
534
|
.build()
|
|
500
535
|
|
|
501
536
|
Log.d(TAG, "[Step 15] Transformer built, starting export...")
|
|
502
|
-
transformer.start(
|
|
537
|
+
transformer.start(composition, cleanOutputPath)
|
|
503
538
|
Log.d(TAG, "[Step 15] Transformer.start() called, waiting for completion...")
|
|
504
539
|
} catch (e: Exception) {
|
|
505
540
|
Log.e(TAG, "[Step 15] Exception building/starting transformer", e)
|
|
506
541
|
Log.e(TAG, "[Step 15] Device info: $deviceInfo")
|
|
507
542
|
Log.e(TAG, "[Step 15] GL info: $glInfo")
|
|
508
|
-
|
|
543
|
+
scaledWatermark.recycle()
|
|
509
544
|
promise.reject(
|
|
510
545
|
"STEP15_TRANSFORMER_BUILD_ERROR",
|
|
511
546
|
"[Step 15] Failed to build/start transformer on ${Build.MANUFACTURER} ${Build.MODEL}: ${e.message}",
|