harelcain commited on
Commit
7ccf135
·
verified ·
1 Parent(s): 44f463d

Upload video-io.ts

Browse files
Files changed (1) hide show
  1. web/src/lib/video-io.ts +40 -1
web/src/lib/video-io.ts CHANGED
@@ -11,6 +11,12 @@ import { embedWatermark } from '@core/embedder.js';
11
  let ffmpegInstance: FFmpeg | null = null;
12
  let ffmpegLoaded = false;
13
 
 
 
 
 
 
 
14
  /** Get or initialize the shared FFmpeg instance */
15
  async function getFFmpeg(onLog?: (msg: string) => void): Promise<FFmpeg> {
16
  if (ffmpegInstance && ffmpegLoaded) return ffmpegInstance;
@@ -86,6 +92,22 @@ export async function streamExtractAndEmbed(
86
  config: WatermarkConfig,
87
  comparisonSample: number,
88
  onProgress?: (phase: string, current: number, total: number) => void
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  ): Promise<StreamEmbedResult> {
90
  const t0 = performance.now();
91
 
@@ -368,7 +390,7 @@ export interface AttackYPlanes {
368
  * Write blob to ffmpeg FS, run attack filter, decode output to Y planes.
369
  * Only processes `maxFrames` frames to keep WASM encoding fast.
370
  */
371
- async function runAttackPipeline(
372
  blob: Blob,
373
  filterArgs: string[],
374
  outputWidth: number,
@@ -420,6 +442,23 @@ async function runAttackPipeline(
420
  return { yPlanes, width: outputWidth, height: outputHeight };
421
  }
422
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  /** Attack: re-encode at a given CRF quality level */
424
  export async function attackReencode(blob: Blob, crf: number, width: number, height: number): Promise<AttackYPlanes> {
425
  return runAttackPipeline(blob, ['-crf', String(crf)], width, height);
 
11
  let ffmpegInstance: FFmpeg | null = null;
12
  let ffmpegLoaded = false;
13
 
14
+ /** Reset the FFmpeg singleton so the next getFFmpeg() call creates a fresh instance */
15
+ function resetFFmpeg() {
16
+ ffmpegInstance = null;
17
+ ffmpegLoaded = false;
18
+ }
19
+
20
  /** Get or initialize the shared FFmpeg instance */
21
  async function getFFmpeg(onLog?: (msg: string) => void): Promise<FFmpeg> {
22
  if (ffmpegInstance && ffmpegLoaded) return ffmpegInstance;
 
92
  config: WatermarkConfig,
93
  comparisonSample: number,
94
  onProgress?: (phase: string, current: number, total: number) => void
95
+ ): Promise<StreamEmbedResult> {
96
+ try {
97
+ return await _streamExtractAndEmbed(videoUrl, payload, key, config, comparisonSample, onProgress);
98
+ } catch (e) {
99
+ resetFFmpeg();
100
+ throw e;
101
+ }
102
+ }
103
+
104
+ async function _streamExtractAndEmbed(
105
+ videoUrl: string,
106
+ payload: Uint8Array,
107
+ key: string,
108
+ config: WatermarkConfig,
109
+ comparisonSample: number,
110
+ onProgress?: (phase: string, current: number, total: number) => void
111
  ): Promise<StreamEmbedResult> {
112
  const t0 = performance.now();
113
 
 
390
  * Write blob to ffmpeg FS, run attack filter, decode output to Y planes.
391
  * Only processes `maxFrames` frames to keep WASM encoding fast.
392
  */
393
+ async function runAttackPipelineOnce(
394
  blob: Blob,
395
  filterArgs: string[],
396
  outputWidth: number,
 
442
  return { yPlanes, width: outputWidth, height: outputHeight };
443
  }
444
 
445
+ /** Run attack pipeline with one retry on WASM crash */
446
+ async function runAttackPipeline(
447
+ blob: Blob,
448
+ filterArgs: string[],
449
+ outputWidth: number,
450
+ outputHeight: number,
451
+ maxFrames: number = 30,
452
+ ): Promise<AttackYPlanes> {
453
+ try {
454
+ return await runAttackPipelineOnce(blob, filterArgs, outputWidth, outputHeight, maxFrames);
455
+ } catch (e) {
456
+ console.warn('ffmpeg.wasm crashed, reinitializing and retrying:', e);
457
+ resetFFmpeg();
458
+ return await runAttackPipelineOnce(blob, filterArgs, outputWidth, outputHeight, maxFrames);
459
+ }
460
+ }
461
+
462
  /** Attack: re-encode at a given CRF quality level */
463
  export async function attackReencode(blob: Blob, crf: number, width: number, height: number): Promise<AttackYPlanes> {
464
  return runAttackPipeline(blob, ['-crf', String(crf)], width, height);