From 6ad9809457e97776a6443d2dfc8f83f8145992a1 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 17 Feb 2026 19:32:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(video):=20=E4=BF=AE=E5=A4=8D=20detached=20A?= =?UTF-8?q?rrayBuffer=20=E5=AF=BC=E8=87=B4=E6=8B=BC=E6=8E=A5=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/public/js/video.js | 160 +++++++++++++++++++---------- app/static/public/pages/video.html | 2 +- 2 files changed, 106 insertions(+), 56 deletions(-) diff --git a/app/static/public/js/video.js b/app/static/public/js/video.js index f354f7f..dd6c065 100644 --- a/app/static/public/js/video.js +++ b/app/static/public/js/video.js @@ -676,8 +676,29 @@ } } + function toStableUint8(input) { + if (input instanceof Uint8Array) { + return new Uint8Array(input); + } + if (input instanceof ArrayBuffer) { + return new Uint8Array(input.slice(0)); + } + if (ArrayBuffer.isView(input)) { + const view = input; + const start = view.byteOffset || 0; + const end = start + (view.byteLength || 0); + return new Uint8Array(view.buffer.slice(start, end)); + } + throw new Error('binary_input_invalid'); + } + + function toStableArrayBuffer(input) { + return toStableUint8(input).buffer; + } + async function sha256Hex(bytes) { - const hashBuffer = await crypto.subtle.digest('SHA-256', bytes); + const stableBytes = toStableUint8(bytes); + const hashBuffer = await crypto.subtle.digest('SHA-256', stableBytes); return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, '0')).join(''); } @@ -686,15 +707,17 @@ if (!resp.ok) { throw new Error(`fetch_failed_${resp.status}`); } - return await resp.arrayBuffer(); + const raw = await resp.arrayBuffer(); + return raw.slice(0); } async function ffmpegWriteFile(ff, name, data) { + const stable = toStableUint8(data); if (typeof ff.writeFile === 'function') { - return await ff.writeFile(name, new Uint8Array(data)); + return await ff.writeFile(name, stable); } if (ff.FS) { - ff.FS('writeFile', name, new Uint8Array(data)); + ff.FS('writeFile', name, stable); return; } throw new Error('ffmpeg_writefile_unsupported'); @@ -703,17 +726,29 @@ async function ffmpegReadFile(ff, name) { if (typeof ff.readFile === 'function') { const out = await ff.readFile(name); - const view = out instanceof Uint8Array ? out : new Uint8Array(out); - return new Uint8Array(view); + return toStableUint8(out); } if (ff.FS) { const out = ff.FS('readFile', name); - const view = out instanceof Uint8Array ? out : new Uint8Array(out); - return new Uint8Array(view); + return toStableUint8(out); } throw new Error('ffmpeg_readfile_unsupported'); } + async function ffmpegDeleteFileSafe(ff, name) { + try { + if (typeof ff.deleteFile === 'function') { + await ff.deleteFile(name); + return; + } + if (ff.FS) { + ff.FS('unlink', name); + } + } catch (e) { + // ignore + } + } + async function ffmpegExec(ff, args) { if (typeof ff.exec === 'function') { return await ff.exec(args); @@ -1380,60 +1415,75 @@ async function extractFrameAtCurrentPoint(videoUrl) { const ff = await ensureFfmpeg(); const srcBuffer = await fetchArrayBuffer(videoUrl); - await ffmpegWriteFile(ff, 'edit_input.mp4', srcBuffer); - const seconds = (Math.max(0, lockedTimestampMs) / 1000).toFixed(3); - await ffmpegExec(ff, ['-y', '-ss', seconds, '-i', 'edit_input.mp4', '-frames:v', '1', 'edit_frame.png']); - const frameBytes = await ffmpegReadFile(ff, 'edit_frame.png'); - const frameHash = await sha256Hex(frameBytes); - const sourceHash = await sha256Hex(srcBuffer); - let binary = ''; - const chunk = 0x8000; - for (let i = 0; i < frameBytes.length; i += chunk) { - binary += String.fromCharCode(...frameBytes.subarray(i, i + chunk)); + try { + await ffmpegWriteFile(ff, 'edit_input.mp4', srcBuffer); + const seconds = (Math.max(0, lockedTimestampMs) / 1000).toFixed(3); + await ffmpegExec(ff, ['-y', '-ss', seconds, '-i', 'edit_input.mp4', '-frames:v', '1', 'edit_frame.png']); + const frameBytes = await ffmpegReadFile(ff, 'edit_frame.png'); + const frameHash = await sha256Hex(frameBytes); + const sourceHash = await sha256Hex(srcBuffer); + let binary = ''; + const chunk = 0x8000; + for (let i = 0; i < frameBytes.length; i += chunk) { + binary += String.fromCharCode(...frameBytes.subarray(i, i + chunk)); + } + const dataUrl = `data:image/png;base64,${btoa(binary)}`; + lastFrameHash = frameHash; + setEditMeta(); + return { + dataUrl, + frameHash, + sourceHash, + sourceBuffer: toStableArrayBuffer(srcBuffer), + }; + } finally { + await ffmpegDeleteFileSafe(ff, 'edit_input.mp4'); + await ffmpegDeleteFileSafe(ff, 'edit_frame.png'); } - const dataUrl = `data:image/png;base64,${btoa(binary)}`; - lastFrameHash = frameHash; - setEditMeta(); - return { - dataUrl, - frameHash, - sourceHash, - sourceBuffer: srcBuffer, - }; } async function concatVideosLocal(sourceBuffer, generatedVideoUrl) { const ff = await ensureFfmpeg(); const generatedBuffer = await fetchArrayBuffer(generatedVideoUrl); - await ffmpegWriteFile(ff, 'seg_a_source.mp4', sourceBuffer); - await ffmpegWriteFile(ff, 'seg_b_source.mp4', generatedBuffer); - const trimSeconds = (Math.max(0, lockedTimestampMs) / 1000).toFixed(3); - if (Number(trimSeconds) > 0) { - await ffmpegExec( - ff, - ['-y', '-i', 'seg_a_source.mp4', '-t', trimSeconds, '-c:v', 'libx264', '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-ar', '48000', '-ac', '2', 'seg_a.mp4'] - ); - await ffmpegExec( - ff, - ['-y', '-i', 'seg_b_source.mp4', '-c:v', 'libx264', '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-ar', '48000', '-ac', '2', 'seg_b.mp4'] - ); - await ffmpegWriteFile( - ff, - 'concat_list.txt', - new TextEncoder().encode("file 'seg_a.mp4'\nfile 'seg_b.mp4'\n") - ); - await ffmpegExec( - ff, - ['-y', '-f', 'concat', '-safe', '0', '-i', 'concat_list.txt', '-c:v', 'libx264', '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-ar', '48000', '-ac', '2', 'merged.mp4'] - ); - } else { - await ffmpegExec( - ff, - ['-y', '-i', 'seg_b_source.mp4', '-c:v', 'libx264', '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-ar', '48000', '-ac', '2', 'merged.mp4'] - ); + const sourceStable = toStableArrayBuffer(sourceBuffer); + try { + await ffmpegWriteFile(ff, 'seg_a_source.mp4', sourceStable); + await ffmpegWriteFile(ff, 'seg_b_source.mp4', generatedBuffer); + const trimSeconds = (Math.max(0, lockedTimestampMs) / 1000).toFixed(3); + if (Number(trimSeconds) > 0) { + await ffmpegExec( + ff, + ['-y', '-i', 'seg_a_source.mp4', '-t', trimSeconds, '-c:v', 'libx264', '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-ar', '48000', '-ac', '2', 'seg_a.mp4'] + ); + await ffmpegExec( + ff, + ['-y', '-i', 'seg_b_source.mp4', '-c:v', 'libx264', '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-ar', '48000', '-ac', '2', 'seg_b.mp4'] + ); + await ffmpegWriteFile( + ff, + 'concat_list.txt', + new TextEncoder().encode("file 'seg_a.mp4'\nfile 'seg_b.mp4'\n") + ); + await ffmpegExec( + ff, + ['-y', '-f', 'concat', '-safe', '0', '-i', 'concat_list.txt', '-c:v', 'libx264', '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-ar', '48000', '-ac', '2', 'merged.mp4'] + ); + } else { + await ffmpegExec( + ff, + ['-y', '-i', 'seg_b_source.mp4', '-c:v', 'libx264', '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-ar', '48000', '-ac', '2', 'merged.mp4'] + ); + } + const merged = await ffmpegReadFile(ff, 'merged.mp4'); + return new Blob([toStableUint8(merged)], { type: 'video/mp4' }); + } finally { + await ffmpegDeleteFileSafe(ff, 'seg_a_source.mp4'); + await ffmpegDeleteFileSafe(ff, 'seg_b_source.mp4'); + await ffmpegDeleteFileSafe(ff, 'seg_a.mp4'); + await ffmpegDeleteFileSafe(ff, 'seg_b.mp4'); + await ffmpegDeleteFileSafe(ff, 'concat_list.txt'); + await ffmpegDeleteFileSafe(ff, 'merged.mp4'); } - const merged = await ffmpegReadFile(ff, 'merged.mp4'); - return new Blob([merged], { type: 'video/mp4' }); } async function runSplice() { diff --git a/app/static/public/pages/video.html b/app/static/public/pages/video.html index 8d689cd..a0c8b79 100644 --- a/app/static/public/pages/video.html +++ b/app/static/public/pages/video.html @@ -274,7 +274,7 @@ - +