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) <noreply@anthropic.com>
47 lines
1.3 KiB
TypeScript
47 lines
1.3 KiB
TypeScript
import { contextBridge, ipcRenderer } from 'electron'
|
|
|
|
contextBridge.exposeInMainWorld('ruwioDesktop', {
|
|
platform: process.platform,
|
|
versions: {
|
|
electron: process.versions.electron,
|
|
chrome: process.versions.chrome,
|
|
node: process.versions.node,
|
|
},
|
|
onMenuCommand(callback: (command: string) => void) {
|
|
const listener = (_event: Electron.IpcRendererEvent, command: string) => callback(command)
|
|
ipcRenderer.on('ruwio:menu-command', listener)
|
|
return () => {
|
|
ipcRenderer.removeListener('ruwio:menu-command', listener)
|
|
}
|
|
},
|
|
renderMp4(options: {
|
|
scene: unknown
|
|
fps?: number
|
|
crf?: number
|
|
preset?: string
|
|
suggestedName?: string
|
|
}) {
|
|
return ipcRenderer.invoke('ruwio:render-mp4', options)
|
|
},
|
|
onRenderProgress(callback: (event: {
|
|
jobId: string
|
|
progress: number
|
|
done: boolean
|
|
error?: string
|
|
}) => void) {
|
|
const listener = (_event: Electron.IpcRendererEvent, payload: {
|
|
jobId: string
|
|
progress: number
|
|
done: boolean
|
|
error?: string
|
|
}) => callback(payload)
|
|
ipcRenderer.on('ruwio:render-progress', listener)
|
|
return () => {
|
|
ipcRenderer.removeListener('ruwio:render-progress', listener)
|
|
}
|
|
},
|
|
cancelRender(jobId: string) {
|
|
return ipcRenderer.invoke('ruwio:render-cancel', jobId)
|
|
},
|
|
})
|