Skip to content
All writing

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 showcase, 93 seconds with sound. The narration, music, stills and clips all came from the companion tools.

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.