MiniFrames turns a web page into a video
MiniFrames renders an HTML page to MP4 on a virtual clock and makes no AI calls. Its companion tools made the 93-second showcase on my own computers.
You write one HTML page, and MiniFrames captures every frame in Chromium and joins them with FFmpeg into an MP4, MOV or WebM file. I built it between October 6 and 13, 2026. Its companion tools made the showcase below, from the narration in my cloned voice to the 5-second clips.
The page holds still while Chromium films it
A browser page runs on wall-clock time, so a capture that
takes longer than a frame would miss animation. MiniFrames
replaces requestAnimationFrame, timers,
Date, performance.now and
Math.random with a virtual clock. Time moves
only when the renderer seeks to the next frame, and
randomness is seeded. Chromium captures each frame as a PNG
over the DevTools protocol and pipes it to FFmpeg.
Time stands still inside a frame. A loop that waits for
Date.now() to change never ends, so a page has
to animate from the clock rather than poll it. In return,
the captured frames match byte for byte between stepped and
skipped seeks, and between one and four render workers.
Four commands cover the loop
miniframes check --dir examples/gsap-promo
miniframes snapshot --dir examples/gsap-promo
miniframes render --dir my-video --out ~/Desktop/promo.webm --crf 25
check looks at every frame for cut-off and
overlapping text, and exits with an error code when it finds
some. snapshot saves up to 14 stills and a
contact sheet to review. preview plays the page
in your browser with sound, lets you scrub it and reloads
when you save. render writes the file in one of
three formats.
| Format | Video | Sound | Transparent |
|---|---|---|---|
| MP4 (default) | H.264, CRF 18 | AAC | No |
| MOV | ProRes 4444 | PCM | Yes |
| WebM | VP9, CRF 30 | Opus | Yes |
The models live in the companions
I kept every model out of the renderer. A model can answer differently on each run, and a renderer that must give the same frames every time can't depend on one. Each companion writes files, such as a WAV, a timeline or a still, and the page reads them like any other asset. A bad take gets redone without touching the render.
| Companion | What it made for the showcase | Model |
|---|---|---|
| mf-narrate | The narration, in a clone of my voice | Qwen3-TTS 1.7B Base |
| mf-transcribe | A blind listening check of every narration clip | whisper-large-v3-mlx |
| mf-voxcpm2 |
The cameo line,
In a world where web pages dream of the big screen. |
VoxCPM2 |
| mf-music | A 132 BPM chiptune bed, ducked 12 dB under the voice | None, it is seeded |
| mf-images | Four stills | Qwen-Image 2.1 Turbo |
| mf-video | Three 5-second clips with their own sound | LTX-2.5 distilled |
| mf-page | The page, built from the narration and music | None |
| mf-frames | A ranked list of frames most likely to look wrong | Clef-Flash 8-bit |
The narration ran on my Mac. The cameo voice, the stills and the clips ran on my Windows PC with an RTX 4090. Every model in the showcase ran on those two machines.
What the showcase run recorded
The script has 29 phrases in 10 scenes, and mf-narrate made 37 attempts to voice them. Six phrases were retried. One retry fixed "FFmpeg" heard back as "FMPEG", and another fixed a pace of 5.50 words a second against a limit of 5.29. mf-transcribe then recognised all 29 clips blind.
Each still took 38 to 45 seconds, and each clip took 92 to
99 seconds. mf-frames scanned 186 frames at half-second
spacing in 163 seconds and flagged six moments to look at.
check finished with no errors and no warnings.
The first file in the project folder is stamped 13:56, and
the final render 16:52.
Bugs from the build
- Sound landed 11 ms late. An input seek placed AAC audio after its cue, so each source is now trimmed by a filter instead, and an AAC test tone lands within 2 ms.
- A test render slowed from 6.9 to 9.1 seconds on one worker. A bisect found a change that synced clips before every step. Syncing skipped frames at the end of their step brought it back to 6.9 seconds.
- Ctrl-C could take 30 seconds to stop a render, because a capture in flight waited out its frame deadline. Interrupting now ends every wait at once, and the tester's page stops in 0.05 seconds.
Where it falls short
WebGL pages can render slowly. Web Audio, SVG animation (SMIL) and iframes run outside the virtual clock, so they won't stay in sync. The text check can't read text inside a canvas, an image or a video. Its security checks reduce exposure to a hostile page, but they don't sandbox it.
MiniFrames and its companions are private for now. If you want help building a tool like this for your team, start a brief.