From 518d43855b66c424d3b03ff72523cfb63fdbc41a Mon Sep 17 00:00:00 2001
From: th
Date: Sat, 6 Jun 2026 20:41:25 +0200
Subject: [PATCH] Add desktop/server MP4 export pipeline, hardening, and render
tests (v1.3.0)
Desktop MP4 export renders frames from a hidden BrowserWindow via
capturePage, pipes PNGs to a bundled ffmpeg (libx264/yuv420p/+faststart),
and writes the file through an atomic temp-file swap. It shares scene
validation (shared/renderValidation.cjs) and the render.html renderer with
the standalone Puppeteer server, which is hardened in parallel.
Render worker & main (electron/):
- abortable render loop with an inactivity watchdog and cancel plumbing
- atomic temp-file + rename so a cancel/failure never truncates a
pre-existing target (#7); stdin EPIPE / early-exit / backpressure-hang
handled (#8)
- HiDPI frames normalized to canvas size (#10); odd dimensions padded to
even (#13, also server); backgroundThrottling disabled (#12)
- reject canvases larger than the display (#11); single-render guard;
isolated render session that blocks network requests (#5)
Tests:
- Electron harnesses (scripts/verify-*.cjs) for capture, export, cancel,
timeout, assets, background, capture-fidelity, resolution, odd-dims,
aggregated by verify-desktop-render.cjs
- server smoke + e2e and shared validation unit tests
- headless hook (src/devtools/headless.ts) to run the in-app suite under
browser/electron hosts; pixel-diff util + capture fixtures (test/)
- dev_docs plans: integration, fixes (#7-#13), and the e2e/capture strategy
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.gitignore | 5 +
README.md | 22 ++
bin/README.md | 16 +
.../electron-e2e-and-capture-test-plan.md | 366 ++++++++++++++++++
dev_docs/electron-rendering-fix-plan.md | 356 +++++++++++++++++
.../electron-rendering-integration-plan.md | 291 ++++++++++++++
.../electron-rendering-integration-plan_2.md | 308 +++++++++++++++
dev_docs/electron-rendering-manual-checks.md | 121 ++++++
electron/ffmpegPath.cts | 30 ++
electron/main.cts | 127 +++++-
electron/preload.cts | 29 ++
electron/renderWorker.cts | 309 +++++++++++++++
package-lock.json | 4 +-
package.json | 27 +-
scripts/stage-ffmpeg.cjs | 65 ++++
scripts/verify-assets.cjs | 109 ++++++
scripts/verify-bg.cjs | 89 +++++
scripts/verify-cancel.cjs | 100 +++++
scripts/verify-capture-fidelity.cjs | 120 ++++++
scripts/verify-capture.cjs | 138 +++++++
scripts/verify-desktop-render.cjs | 39 ++
scripts/verify-export.cjs | 125 ++++++
scripts/verify-odd-dims.cjs | 66 ++++
scripts/verify-resolution.cjs | 58 +++
scripts/verify-timeout.cjs | 88 +++++
server/package.json | 4 +-
server/render.e2e.test.cjs | 310 +++++++++++++++
server/render.html | 15 +-
server/server.js | 83 +---
server/server.smoke.test.cjs | 139 +++++++
shared/renderValidation.cjs | 120 ++++++
shared/renderValidation.test.cjs | 62 +++
src/components/ExportDialog.vue | 32 +-
src/devtools/headless.ts | 53 +++
src/devtools/testRunner.ts | 6 +-
src/lib/desktopRenderExport.ts | 65 ++++
src/main.ts | 4 +
src/types/desktop.d.ts | 16 +
src/version.ts | 2 +-
test/fixtures/scenes/capture-basic.json | 33 ++
test/lib/pixelDiff.cjs | 41 ++
41 files changed, 3907 insertions(+), 86 deletions(-)
create mode 100644 bin/README.md
create mode 100644 dev_docs/electron-e2e-and-capture-test-plan.md
create mode 100644 dev_docs/electron-rendering-fix-plan.md
create mode 100644 dev_docs/electron-rendering-integration-plan.md
create mode 100644 dev_docs/electron-rendering-integration-plan_2.md
create mode 100644 dev_docs/electron-rendering-manual-checks.md
create mode 100644 electron/ffmpegPath.cts
create mode 100644 electron/renderWorker.cts
create mode 100644 scripts/stage-ffmpeg.cjs
create mode 100644 scripts/verify-assets.cjs
create mode 100644 scripts/verify-bg.cjs
create mode 100644 scripts/verify-cancel.cjs
create mode 100644 scripts/verify-capture-fidelity.cjs
create mode 100644 scripts/verify-capture.cjs
create mode 100644 scripts/verify-desktop-render.cjs
create mode 100644 scripts/verify-export.cjs
create mode 100644 scripts/verify-odd-dims.cjs
create mode 100644 scripts/verify-resolution.cjs
create mode 100644 scripts/verify-timeout.cjs
create mode 100644 server/render.e2e.test.cjs
create mode 100644 server/server.smoke.test.cjs
create mode 100644 shared/renderValidation.cjs
create mode 100644 shared/renderValidation.test.cjs
create mode 100644 src/devtools/headless.ts
create mode 100644 src/lib/desktopRenderExport.ts
create mode 100644 test/fixtures/scenes/capture-basic.json
create mode 100644 test/lib/pixelDiff.cjs
diff --git a/.gitignore b/.gitignore
index 486841b..fd6b08e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,8 @@ release
/ruwio.png
.codex
misc/
+
+# Locally staged ffmpeg binaries. Release-managed binaries can be force-added.
+/bin/linux/ffmpeg
+/bin/darwin/ffmpeg
+/bin/win32/ffmpeg.exe
diff --git a/README.md b/README.md
index b4dab7f..8875b60 100644
--- a/README.md
+++ b/README.md
@@ -68,6 +68,18 @@ npm run desktop:build # build platform installer/package artifacts into release
app without creating an installer. `desktop:build` uses `electron-builder` and
targets AppImage on Linux, NSIS on Windows, and DMG on macOS.
+Desktop MP4 export runs inside Electron and uses ffmpeg from
+`resources/bin//` in packaged builds. The build scripts run
+`npm run stage:ffmpeg -- --optional`, which copies `FFMPEG_PATH` or `ffmpeg` on
+`PATH` into `bin//` when available. For release artifacts, replace
+that local copy with redistributable static ffmpeg binaries for Linux, Windows,
+and macOS.
+
+If no bundled binary is present, MP4 export still falls back to `FFMPEG_PATH`
+or system `ffmpeg` and reports a clear error when neither can be executed.
+Before shipping bundled ffmpeg, document the binary source, version, configured
+codecs, and GPL/LGPL obligations for that build.
+
## MP4 render server
MP4 export is handled by the optional render server in `server/`. It binds to
@@ -96,6 +108,16 @@ The included `docker-compose.yml` keeps the published host port on
`127.0.0.1:6000` and requires `RENDER_TOKEN` in the environment before it will
start.
+Render-server checks:
+
+```bash
+npm run test:render-server # health/auth/validation route smoke
+npm run test:render-server:e2e # renders a tiny MP4 and verifies it with ffprobe
+```
+
+The E2E check requires Chromium, ffmpeg, and ffprobe. Set
+`PUPPETEER_EXECUTABLE_PATH` when Chromium is not at `/usr/bin/chromium`.
+
## Stack
- Vue 3 (` |