Spaces:
Running
Running
Upload video-io.ts
Browse files- 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
|
| 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);
|