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>
34 lines
1.2 KiB
JSON
34 lines
1.2 KiB
JSON
{
|
|
"canvas": { "width": 160, "height": 120, "background": "#f8fafc" },
|
|
"duration": 1,
|
|
"assets": [
|
|
{ "id": "rect", "kind": "rect", "name": "Rect", "width": 42, "height": 28, "fill": "#ef4444", "stroke": "#111827", "strokeWidth": 2 },
|
|
{ "id": "circle", "kind": "circle", "name": "Circle", "radius": 18, "fill": "#22c55e", "stroke": "#0f172a", "strokeWidth": 2 }
|
|
],
|
|
"layers": [
|
|
{
|
|
"id": "layer-rect",
|
|
"name": "Rect",
|
|
"assetId": "rect",
|
|
"visible": true,
|
|
"base": { "x": 36, "y": 62, "scaleX": 1, "scaleY": 1, "rotation": 0, "opacity": 1 },
|
|
"keyframes": [
|
|
{ "id": "rect-0", "time": 0, "easing": "linear", "props": { "x": 36, "rotation": 0 } },
|
|
{ "id": "rect-1", "time": 1, "easing": "linear", "props": { "x": 112, "rotation": 18 } }
|
|
]
|
|
},
|
|
{
|
|
"id": "layer-circle",
|
|
"name": "Circle",
|
|
"assetId": "circle",
|
|
"visible": true,
|
|
"base": { "x": 88, "y": 44, "scaleX": 1, "scaleY": 1, "rotation": 0, "opacity": 0.82 },
|
|
"keyframes": [
|
|
{ "id": "circle-0", "time": 0, "easing": "linear", "props": { "y": 44 } },
|
|
{ "id": "circle-1", "time": 1, "easing": "linear", "props": { "y": 78 } }
|
|
]
|
|
}
|
|
],
|
|
"trackOrder": ["layer-circle", "layer-rect"]
|
|
}
|