11 KiB
Electron Rendering Integration Plan
Date: 2026-06-05
Verdict
Yes, the MP4 rendering part can be included in the Electron app.
The current render server is already made from desktop-compatible pieces:
- Node orchestration in
server/server.js - a browser render surface in
server/render.html - frame capture through Chromium
- MP4 encoding through
ffmpeg - progress, cancellation, temp files, validation, and cleanup around the job
The main caveat is that the server cannot be bundled as-is and expected to work in a packaged desktop app. It currently assumes:
puppeteer-coreis installed in the server package- Chromium exists at
PUPPETEER_EXECUTABLE_PATHor/usr/bin/chromium ffmpegexists onPATH- the Vue app talks to
http://localhost:6000withVITE_RENDER_SERVER - Electron Builder packages only
dist/,dist-electron/, and rootpackage.json
So the feature is feasible, but the right implementation is to move the render job into the Electron main process behind IPC, not to expose an HTTP render server from the desktop app.
Recommended Architecture
Use Electron as the local render host:
- Vue renderer keeps owning project state and export UI.
- Preload exposes a narrow desktop render API on
window.ruwioDesktop. - Electron main validates render requests, starts/cancels jobs, captures frames, runs
ffmpeg, and returns a saved MP4 path or file bytes. - The standalone
server/package remains available for web or self-hosted server deployments.
Preferred render surface:
- Use a hidden/offscreen Electron
BrowserWindowto load a packaged render page. - Call
webContents.executeJavaScriptorwebContents.sendto initialize the scene and seek each frame. - Use
webContents.capturePage()for PNG frame capture. - Pipe PNG frames into
ffmpegthe same wayserver/server.jsdoes today.
This avoids bundling a second Chromium through Puppeteer. Electron already ships Chromium, so the desktop build only needs to solve ffmpeg packaging.
Fallback/spike option:
- Start the existing Express render server as a child process bound to
127.0.0.1with a random token. - Point the renderer at that local URL.
- This is useful for a quick feasibility spike, but it keeps the localhost server, token plumbing, Puppeteer, and external Chromium dependency in the desktop app. It should not be the final architecture.
Dependency Strategy
Chromium
Do not add a separate Chromium binary for the first native implementation. Use Electron's bundled Chromium through a hidden render window.
Only keep Puppeteer for the standalone server unless a spike proves Electron capture cannot match render quality or performance.
ffmpeg
ffmpeg is the real packaged-app dependency.
Implementation options:
- Bundle per-platform
ffmpegbinaries asextraResources. - Use a maintained npm package that supplies platform-specific binaries.
- Support a user-configured/system
ffmpegpath as a fallback.
Recommended first production path:
- Add an internal resolver such as
electron/ffmpegPath.cts. - In development, prefer
process.env.FFMPEG_PATH, thenffmpegonPATH. - In packaged builds, prefer
process.resourcesPath/bin/<platform>/ffmpeg. - Show a friendly export error if no usable binary is found.
Implementation Plan
Phase 1: Extract Shared Render Validation
Goal: keep server and desktop limits identical.
Tasks:
- Move request validation and playback range helpers out of
server/server.jsinto a shared module. - Keep the module Node-compatible and browser-safe where practical.
- Export:
validateRenderRequest(scene, options)playbackRange(scene)- render limits and allowed ffmpeg presets
- Update
server/server.jsto use the shared validator. - Add focused tests for invalid dimensions, fps, duration, frame count, layers, keyframes,
crf, and preset.
Acceptance:
- Existing server behavior is unchanged.
- Desktop rendering can reuse the same validation before launching any render surface or child process.
Phase 2: Add Desktop Render IPC Contract
Goal: let the Vue app request MP4 rendering without knowing whether the backend is HTTP or Electron.
Tasks:
- Extend
src/types/desktop.d.tswith a render API:
renderMp4(options: {
scene: Scene
fps?: number
crf?: number
preset?: string
suggestedName?: string
}): Promise<{ path: string } | null>
onRenderProgress(callback: (event: {
jobId: string
progress: number
done: boolean
error?: string
}) => void): () => void
cancelRender(jobId: string): Promise<void>
- Add preload wrappers that call
ipcRenderer.invokeand subscribe to progress events. - Add main-process handlers:
ruwio:render-mp4ruwio:render-cancel
- Validate every IPC payload in main before starting a job.
- Keep job IDs and abort controllers in main, not in the Vue renderer.
Acceptance:
- A renderer call can start a fake/stub render and receive progress.
- Cancel reaches the main process.
- Invalid payloads fail before any child process or hidden window is created.
Phase 3: Build The Electron Render Worker
Goal: replace Puppeteer with Electron's own Chromium for desktop exports.
Tasks:
- Create an Electron-only render worker module, for example
electron/renderWorker.cts. - Package a render page:
- first pass: reuse
server/render.htmlas an extra resource - later pass: consider generating a render page from the shared browser renderer to reduce drift
- first pass: reuse
- Create a hidden
BrowserWindowper job or a serialized reusable worker window:show: falsewebPreferences.sandbox: true- no Node integration
- restricted navigation
- Load the render page from a local file URL.
- Initialize the scene.
- For each frame:
- seek to the target time
- wait for the render function to settle
- capture the page or canvas bounds
- write PNG bytes to
ffmpeg.stdin - emit progress over IPC
- On cancel or timeout:
- abort the frame loop
- kill
ffmpeg - close the worker window
- remove temp files
Acceptance:
- A tiny scene renders to MP4 in
npm run desktop:dev. - Progress updates reach
ExportDialog. - Cancel stops capture and
ffmpeg. - Temp directories are removed on success, failure, and cancel.
Phase 4: Package ffmpeg
Goal: make MP4 export work in packaged desktop builds.
Tasks:
- Choose the binary source:
- committed/release-managed binaries under a
vendor/orbin/folder, or - npm package binaries copied into Electron Builder resources
- committed/release-managed binaries under a
- Add Electron Builder
extraResourcesentries for:- Linux ffmpeg
- Windows ffmpeg.exe
- macOS ffmpeg
- render worker HTML/assets
- Add
ffmpegpath resolution for dev and packaged builds. - Verify executable permissions on macOS/Linux packaged output.
- Document licensing obligations for the selected
ffmpegbuild.
Acceptance:
npm run desktop:packincludes the render page and ffmpeg.- The unpacked app exports MP4 without a separately installed render server.
- Missing ffmpeg produces a clear user-facing error.
Phase 5: Switch ExportDialog To Desktop Rendering
Goal: use native rendering in Electron and keep the current server path for the web app.
Tasks:
- Add
src/lib/desktopRenderExport.ts. - In
ExportDialog.vue, route MP4 export as:- if
window.ruwioDesktop?.renderMp4exists: use desktop IPC render - else: use current
downloadFromServer
- if
- Reuse current progress UI and cancellation behavior.
- Save the finished MP4 through a native save dialog from main.
- Keep
VITE_RENDER_SERVERsupport for browser builds and remote/self-hosted workflows.
Acceptance:
- Desktop users do not need
cd server && npm run start. - Browser users can still use the external render server.
- The MP4 button reports a helpful error when no server is configured in browser mode.
Phase 6: Keep Standalone Server Working
Goal: avoid breaking existing server deployments.
Tasks:
- Keep
server/server.jsas an Express wrapper around shared validation/render helpers where useful. - Keep
server/render.htmlor replace it only after desktop and server render outputs are compared. - Preserve
/health,/render,/events,/file, andDELETEbehavior. - Add a smoke test or manual check for server MP4 export after shared refactors.
Acceptance:
cd server && npm run startstill starts the local render server.- The Vue app can still render through
VITE_RENDER_SERVERandVITE_RENDER_TOKEN.
Phase 7: Verification Matrix
Run these before calling the integration complete:
npm run buildnpm run desktop:packnpm run test:render-servernpm run test:render-server:e2e- desktop dev MP4 export of a small scene
- desktop packaged MP4 export of a small scene
- cancel an MP4 export after progress starts
- export a scene with image assets
- export a scene with text/fonts
- export a scene at non-default fps
- export a scene with hidden layers skipped
- server-mode MP4 export still works
Manual platform checks:
- Linux AppImage
- Windows NSIS build
- macOS DMG build
Risks And Mitigations
- ffmpeg packaging varies by platform: solve with explicit binary resolution and an unpacked-app smoke check before installer work.
- render drift between
server/render.htmlandsrc/lib/render.ts: keep parity tests, then consider generating the desktop/server render page from shared code. - large scenes can freeze the desktop app: keep render work in main-controlled worker windows, serialize jobs initially, enforce the same render limits as the server.
- IPC can move large payloads: scenes are JSON-sized today, but avoid sending frame buffers over IPC; frame capture and
ffmpegpiping should stay in main. - untrusted project assets reach Chromium: keep the hidden render window sandboxed, restrict navigation, block external network requests unless explicitly allowed, and keep existing project validation work moving.
Suggested First Pull Request
Keep the first PR small and proof-oriented:
- Extract shared render validation from
server/server.js. - Add a stubbed desktop render IPC contract.
- Add
desktopRenderExport.tsand route the MP4 button to desktop IPC when available. - Return a fake progress/done result in development or behind a temporary flag.
That proves the app integration and cancellation shape before dealing with frame capture and ffmpeg packaging.
Suggested Second Pull Request
Implement the real Electron render worker:
- Hidden render window.
- Frame capture loop.
ffmpegstdin piping.- Temp directory cleanup.
- Dev-only
ffmpegpath resolution.
Suggested Third Pull Request
Make it shippable:
- Package ffmpeg and render worker assets.
- Verify
desktop:pack. - Add packaged-app manual test notes.
- Document ffmpeg licensing and update README desktop export notes.