devlog.

Project · Video rendering

ShowReel

A Rust crate that turns a description of a film — a timeline, a camera, typography, transitions — into a deterministic mp4. No browser, no Node, no headless Chromium to render it: tiny-skia and rustybuzz draw the pixels and the glyphs, ffmpeg encodes. The same renderer also compiles to WebAssembly, so a film scrubs and edits in a browser tab with no server behind it at all.

12.0
ms/frame, the finished film
4.3
ms/frame, 48 MP camera, 20 threads
12,800
Lines of Rust, 28 modules
11
CLI subcommands, one library underneath

The example film: 1920×1080 at 60fps, 2082 frames, 20 cores. Two renders of the same film give byte-identical PNGs and byte-identical mp4s.

ShowReel's own demo, authored as a plain film file and rendered by showreel render — not edited by hand afterward. Seven scene changes, no two transitions alike: a camera pulling back over a 57-megapixel still, a push-in over a decoded video clip, the same frame ungraded and then graded, three depth planes cut from one flat image, and a title built from filled vector glyphs, over two audio tracks mixed together. The file behind every shot, examples/showreel_demo.film.jsonc in the repository, reads top to bottom the way the reel plays.
The browser editor: a film's timeline, scenes and layers down the left, a video clip's properties down the right, and the live frame in the middle showing a camera layer callout reading 23.7 MP, source, this shot, over a title reading THE CAMERA. An Export video button sits top right.
The wasm build isn't a viewer — it edits. Add a clip, set its trim, pick a transition, retype a title, reorder scenes, and save the changed film back out as JSON.

What it does

Everything here is built and shipping.

Authoring

A value, not a macro DSL

  • A film is a Rust builder or a JSON tree — the same tree either way, written by hand, generated, or handed over by a tool
  • A timeline is Scene (Transition Scene)* — two transitions in a row cannot be written down, in either form
  • showreel new scaffolds a working starter film that needs no assets at all
  • .jsonc takes comments and a trailing comma; a plain .json file still loads exactly as before

The camera

Holds steady over 48 megapixels

  • Geometrically-interpolated pan, zoom and hold, keyframed by Shots, over a mip-backed still
  • A 6832×7024 source renders to 1080p at 4.3 ms/frame across 20 threads — cost tracks the output size, not the source
  • Nearest-neighbour resampling when the camera magnifies, so pixel art stays sharp instead of turning to mush
  • Content::Parallax: several depth planes share one authored camera move, each departing from it by its own fraction

On screen

Glyphs as filled paths

  • Real shaping and kerning, tracking in ems, tabular figures so a counter doesn't jitter as it ticks
  • Nine transitions — cut, dissolve, fade-through-colour, wipe, slide, push, iris, zoom, cross-blur — each composable with any easing curve or spring
  • Titles, lower-thirds, callouts that point at things, counters that count
  • A pull-up that lifts a region of the frame, dims the rest, and annotates it; a progress bar that fills without drawing digits

Audio

Tracks, ducking, a clip's own mix

  • Any audio file placed and trimmed on the film's own clock, with fades, gain, several tracks mixed
  • A clip's own soundtrack joins the mix by default — mute, duck or fade it independently
  • Reaches both the full-quality master and the 720p/30fps mobile cut
  • Won't loop a clip's audio to match a looping picture, and drops it rather than play it out of sync when the clip runs at another speed — documented, not silent

Automation

A studio, an API, an agent

  • showreel studio: a scrubber, live reload, and the film's structure in a browser, behind an opt-in feature flag
  • Four HTTP endpoints riding that same server — info, check, still, render — so a script can drive it without shelling out to the CLI
  • showreel mcp: five tools over the official rmcp SDK, for an agent already in the same process tree
  • One implementation of "render a still" underneath all three surfaces, not three

No server

Compiled to WebAssembly

  • showreel web-pack turns a film into a static directory any file host can serve — the renderer compiled to wasm32-unknown-unknown
  • Scrubs and edits there exactly as the native studio does, down to reordering scenes and saving the changed film back out
  • Dropping a clip in decodes it client-side with no ffmpeg, through the browser's own <video> decoder
  • Exports a real WebM — VideoEncoder (WebCodecs) plus a hand-rolled muxer, verified round-trip against ffprobe

What it costs

Measured on the machine that renders it — a 20-core box, the build in this repository. Every figure names its own thread count.

Measurement Result
Whole example film, full quality (2082 frames, 20 cores)12.0 ms/frame · 83 fps
— the 48 MP camera pull-back alone (660 frames)15.2 ms/frame · 66 fps
— a 0.35-scale preview pass3.1 ms/frame · 318 fps
48 MP camera: crop & resample from full res, 1 thread142.1 ms/frame
48 MP camera: mip pyramid, 1 thread42.2 ms/frame
48 MP camera: mip pyramid, 20 threads4.3 ms/frame
Parallax, 3 planes at 1080p, 1 thread~45 ms/frame
— one ordinary still+camera layer, for comparison~15 ms/frame
Cross-blur dissolve, full-frame blur pass~170 ms/call
x264 encode, crf 17 preset slow, ~3.15 cores12.1 ms/frame
Serial render vs. 20-thread parallelbyte-identical

The pyramid is what makes the camera figure hold: choosing an already-shrunk level before resampling makes cost track the output area instead of the source area, near- constant whatever the zoom. It is built once at load — 523 ms for the 48 MP source — and holds 192 MB resident. The cross-blur cost is per call, twice a transition frame, independent of blur radius; a short transition keeps it bounded, a film-length one would not.

The compiled wasm build mid-scrub in a browser tab, showing all 226 Pokémon Blue maps in one picture with a counter reading 226 maps in this picture, timecode 0:14.9 of 0:34.7, at 1920x1080, 60fps.
The same 48-megapixel pull-back the camera figures above are built from, scrubbing in a browser tab — no server behind this frame at all.

Where the gaps are

Milestones

28 Aug 2026

Generated music, timed to the film

A track's source can now be a synthesised chiptune instead of a file — no bed to license, and fit: "film" lands the last downbeat on the final frame. Read it →

28 Aug 2026

Six of seven, already built

A study of a specific YouTube editor's technique found ShowReel already had almost everything the look needed. The one clean gap was small. Read it →

28 Aug 2026

The browser became an editor

Export without ffmpeg: a real VideoEncoder and a hand-rolled WebM muxer, verified round-trip against ffprobe.

27 Aug 2026

Compiled to WebAssembly

The renderer runs in a browser tab. A film scrubs with no server behind it, and a dropped clip decodes client-side with no ffmpeg.

27 Aug 2026

The number that could have sunk it

A 120-frame pull-back over a 48-megapixel still came back at 4.3 ms a frame, settling whether to build a camera at all. Read it →

Writing


Where these numbers come from

Every timing figure is ShowReel's own, measured on the machine that rendered it, with the exact command recorded beside it in the source repository. Line and module counts are counted directly from the crate's own src/ tree.

ShowReel ships no game assets of its own — the worked example's Pokémon Blue stills belong to whoever owns that cartridge and are not in the crate. The screenshots here are of the crate's own tooling, the browser editor and the wasm build, which the project is free to show.

← All projects