# mexicat/pdoom-video: the code-rendered music video and its rendering pipeline

> A TypeScript + three.js music video renderer built for a single song. Every frame is a deterministic function of song time, so the browser preview and the offline 1080p or 4K export use an identical renderer.

**mexicat/pdoom-video** — Code-rendered music video for "I'm Upping My P(doom)"

- Repository: https://github.com/mexicat/pdoom-video
- Stars: 2,427 · Forks: 266
- Language: TypeScript
- License: MIT
- Published: 2026-10-11 · Updated: 2026-10-11 · Language: en
- Canonical page: https://hysenlabs.com/projects/mexicat-pdoom-video

## A renderer for one song, not a reusable framework

pdoom-video is a code-rendered music video for the song 'I'm Upping My P(doom)'. Every frame is a deterministic function of song time, which means the live browser preview and the offline 1080p60 or 4K60 export run through identical rendering code.

This video was made with Claude (Opus 5.5) in Claude Code: the concept, lyric alignment, audio analysis, all 17 scene modules and the renders were worked out there. A YouTube upload at https://www.youtube.com/watch?v=5EoO5413dBY is acknowledged as slightly outdated; a local render gives the current version. The song is credited to osmarks (lyrics) and MusicPerson (opening verse and chorus) and is not by the repository author.

## Preview setup: bun, Vite and keyboard controls at localhost:5173

Preview setup needs only bun and a recent Mac (for real-time rendering) and does not need Chrome or ffmpeg. From the app/ directory, install and start the dev server:

```bash
cd app
bun install
bunx vite
```

Open http://localhost:5173 and the video starts. A query parameter on the URL lets you jump to a time position, so ?t=23 starts playback at 23 seconds. Keyboard controls are: space to play or pause; left and right arrows to seek by 1 second (5 seconds with shift); comma and period to step one frame; brackets to jump to the previous or next scene; l to loop the current scene; h to hide the UI. Analysis tools in analysis/ need uv but the renderer does not, so these two installs are independent. Chrome and ffmpeg are only needed for the offline export.

## The 1080p render pipeline: two headless Chromes, 8-frame chunks and frame buffering

Offline rendering drives headless Chrome through playwright-core and pipes raw frames to ffmpeg. The standard 1080p render runs:

```bash
cd app
bun scripts/render.ts video --samples auto --shutter 0.2 --out ../out/pdoom.mp4
```

Output is 1920x1080 at 60 fps, x264 CRF 16 with AAC audio. Two headless Chrome instances (--workers 2) each render chunks of 8 frames (--chunk) and stream them to a single ffmpeg process in order. Workers run in turn, so the output is identical to a single-worker render. Frames that arrive faster than ffmpeg can encode them are held in memory up to --mem-gb (default 1 GB) and then spooled to disk up to --spool-gb (default 20 GB, in the system temp folder), which prevents the renderers from stalling while the encoder catches up on lighter scenes.

## The adaptive per-tile sampler: 12, 36, 108 and 324 sub-frames depending on motion

Motion blur is computed by averaging sub-frames spread over a shutter window. The default --shutter 0.2 means each frame averages sub-frames sampled across a fifth of the frame time.

With --samples auto, the sampler assigns a count per 32x32 tile of the output frame rather than to the whole frame. Still tiles get 12 sub-frames; tiles with ordinary camera motion get 36; whips, slams and fast zooms get 108 or 324. A tile stops early once additional sub-frames would not change its pixel values by more than --tol levels out of 255 (default 3), so a fast object over a still background costs proportionally more only in the tiles it occupies. --refine frame switches to a single count for the whole frame, which the project documentation calls the previous behaviour. --samples 4 gives a fixed 4 sub-frames for a quick draft.

## 4K rendering: 57 minutes on an M5 Pro and 670 Mbit/s at CRF 16

The 4K render scales to a true 3840x2160 output, not an upscale: every layer, line and shader is drawn at physical resolution. The 4K command adds --scale 2 and adjusted encoding options:

```bash
cd app
bun scripts/render.ts video --scale 2 --samples auto --shutter 0.2 --x264 aq-mode=3:rc-lookahead=30 --out ../out/pdoom-4k.mp4
```

The full song takes about 57 minutes on an M5 Pro. Memory peaks at about 23 GB: up to 6 GB per headless Chrome (two workers, so up to 12 GB total) and ffmpeg at about 3.5 GB for most of the render, rising to about 10 GB near the end as the encoder's lookahead fills. At the default CRF 16 the output runs at about 670 Mbit/s, which is about 13 GB for the song and roughly 8 times the size of the 1080p file. Lowering to CRF 18 gives about 450 Mbit/s, and CRF 20 gives about 230 Mbit/s.

## The audio analysis pipeline and the pre-built timing JSON files

Two pre-committed files supply the renderer with all timing data: data/lyrics.json (word-level and some syllable-level timings) and data/audio.json (tempo at 132.007 BPM, beats, downbeats, sections, drum and vocal onsets and loudness envelopes). Both are already in the repository, so a fresh render does not need Python or the analysis tools.

Regenerating them requires stems and intermediate files that are not in the repository. Regeneration uses Demucs (htdemucs_ft) for stem separation, CTC forced alignment cross-checked with Whisper for word timings, and a separate beat/onset analysis. Running the two scripts produces both JSON files:

```bash
cd analysis
uv run python align.py
uv run python analyze.py
```

Model downloads add about 4 GB of weights to analysis/.cache/, which can be deleted afterwards. Intermediate files go to analysis/work/, also not committed.

## 17 named scenes and why this codebase is hard to adapt for a different song

pdoom-video has 17 scene modules: open, loss, prompt, hook, room, shoggoth, spacetime, ascent, bureau, leftturn, paperclips, fuse, stack, dense, loom, ilya and outro. Each is a separate TypeScript file under src/scenes/. Scene windows are anchored in src/timeline.ts to specific lyric lines and snapped to the beat grid derived from the 132.007 BPM tempo and the committed audio.json.

A keyframe-based tool like After Effects or Blender's animation system would store animation state as explicit keyframe values at specified times. pdoom-video stores no keyframes: each scene is a pure function that takes a time offset and returns geometry and shader state. Every motion curve is written as code rather than as draggable handles, and someone wanting to use the renderer for a different song would need to replace the timing data, rewrite the timeline anchors and replace or rewrite each of the 17 scenes.

## Conclusion

pdoom-video is worth studying as an engineering example of deterministic video rendering: because every frame is a function of song time only, the browser preview and the offline export produce identical results from the same code. The song-specific data, the 17 scene modules and the lyric-anchored timeline make it impractical to adapt for a different track without rewriting most of the project. For the 4K export, plan for bun, Chrome and ffmpeg, about 23 GB of RAM and close to an hour of render time on an M5 Pro. Start with the preview at localhost:5173 to confirm your environment before attempting an export.

## FAQ

### What is mexicat/pdoom-video?

A code-rendered music video for the song 'I'm Upping My P(doom)', built in TypeScript and three.js. Every frame is a deterministic function of song time, so the browser preview and the offline export use the same renderer. The video was made with Claude (Opus 5.5) in Claude Code.

### What are the requirements to run or render pdoom-video?

The browser preview needs bun only. The offline renderer also needs Google Chrome (driven headlessly through playwright-core) and ffmpeg with libx264. The audio analysis re-run needs uv, but the renderer does not.

### How long does the 4K render of pdoom-video take?

About 57 minutes on an M5 Pro. Memory peaks at about 23 GB. The output at the default CRF 16 runs at about 670 Mbit/s, roughly 13 GB for the full song.

### Can I use pdoom-video for my own music video?

The codebase is under the MIT license, so the code is reusable. In practice, the 17 scene modules, the timing data and the timeline in src/timeline.ts are all specific to this one song, so adapting it for a different track requires replacing most of the project.

### How do I render pdoom-video at 1080p?

From the app/ directory, run: bun scripts/render.ts video --samples auto --shutter 0.2 --out ../out/pdoom.mp4. Output is 1920x1080 at 60 fps with x264 CRF 16 encoding.

## Sources

- [Issues](https://github.com/mexicat/pdoom-video/issues)
- [License: MIT](https://github.com/mexicat/pdoom-video/blob/main/LICENSE)
- [mexicat/pdoom-video on GitHub](https://github.com/mexicat/pdoom-video)
- [README](https://github.com/mexicat/pdoom-video/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mexicat-pdoom-video
