SVG to MP4 Animation
SVG to MP4 Animation
- video
Everyone loves watching a plotter draw - the lines appearing in order is pure ASMR. SVG to MP4 Animation makes that video without a plotter: it takes an SVG string and renders a line-drawing animation where each stroke is drawn in sequence, then returns it as a ComfyUI VIDEO. You can preview it in the graph, save it, or feed it to Video to Submission File and upload it to the plotter service. The output is a great proof-of-work to pair with a real plot.
The clever - and fiddly - part is how it renders. There's no server-side encoder here. The Python node sends the SVG plus your settings to the browser, where JavaScript renders each frame using WebCodecs' VideoEncoder and muxes the result into an MP4 with the mp4-muxer library. The browser then POSTs the finished file back to a route the pack registers on ComfyUI's server, and only then does the node unblock and return. It's rendering on your GPU-accelerated browser tab instead of the Python process, which is why it's fast - and also why it has a hard dependency on a live tab.
How it works
The node figures out the drawing order and speed from your settings. speed_px_per_sec (default 400) is how fast the "pen" travels along the lines, and the code will silently raise it if the animation would exceed max_duration_seconds (default 30) - that's the cap you set to stop a huge drawing from producing a marathon video. fps (default 30), out_width/out_height (default 1280×720), bg_color (default #fafafa), stroke_width (default 1) and freeze_final_seconds (default 1, a pause at the end so the finished drawing is readable) round out the controls.
The render itself is dash-offset trickery: each path gets a stroke-dash offset that animates as its time slice arrives, so lines grow along their length in plot order. The node blocks on a threading event until the browser reports the MP4 back, with a generous timeout (animation length + freeze + two minutes of slack).
The two things that catch people:
- The ComfyUI tab must stay open. "Timed out waiting for browser to render MP4. Is the ComfyUI tab open?" is the actual error message. Background tabs usually survive, but closing the tab kills the job.
svgexpects aforceInputstring - wire it from Optimize SVG with vpype or anywhere you have SVG markup, not from a text-file path.
Install
It's in the ComfyUI PenPlotter pack. ComfyUI Manager → search "ComfyUI PenPlotter", or:
cd ComfyUI/custom_nodes
git clone https://github.com/wouterverweirder/comfyui-penplotter
Restart ComfyUI. The browser-side pieces (mp4-muxer.mjs, the render scripts) are bundled in the pack's web/ directory. Python-side you get the usual requirements - numpy>=2.3.4, opencv-python, numba, vpype, websockets, requests, qrcode[pil], AxiDraw API - with websockets doing the heavy lifting for the browser round-trip.
Common issues
- "Timed out waiting for browser to render MP4" - the tab was closed, backgrounded too aggressively, or the browser blocked the encoder. Keep the tab focused and rerun.
- "SVG parse error" - your SVG is malformed for the browser's parser. Run it through Optimize SVG with vpype first; it normalizes a lot of sloppy markup.
- Rendering looks wrong (lines popping in out of order) - that's usually an SVG with groups/transforms the simple parser mishandles. Flatten transforms before animating.
- Cancel hangs - the pack's interrupt hook wakes waiting MP4 jobs, so Cancel should return promptly. If it doesn't, close the browser tab; the Python side has a timeout.
This is the pack's most ambitious node - encoding video in the browser from inside ComfyUI is genuinely novel - and the price of that cleverness is that it needs your browser to cooperate. When it works, it's the most shareable thing the pack produces.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| svg | STRING | — | |
| speed_px_per_secopt | INT | 4001–10000 | — |
| max_duration_secondsopt | FLOAT | 30.01–600 | — |
| fpsopt | INT | 301–120 | — |
| out_widthopt | INT | 128016–7680 | — |
| out_heightopt | INT | 72016–4320 | — |
| bg_coloropt | STRING | #fafafa | — |
| freeze_final_secondsopt | FLOAT | 1.00–60 | — |
| stroke_widthopt | FLOAT | 1.00–100 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| video | VIDEO | — |