Rendering twenty-two hours of math from code
explainer-videos builds feature-length mathematics, physics, machine learning, and markets films entirely from Python with Manim Community and local neural text-to-speech. No video files or timeline projects live in git: every frame, equation, simulation, and subtitle track compiles from source.
A film is a package
videos/<series>/<name>/ and compiles from numerical checkpoints, scene scripts, and cached neural speech into a chaptered master.Rendering a sixty-minute film in parallel exposes two bottlenecks in stock Manim: concurrent scene processes race on shared LaTeX files, and frame-by-frame numeric readouts fill SVG_HASH_TO_MOB_MAP past 6 GB of RAM. tools/build.py gives every scene worker its own TeX directory, orders jobs longest-first so a short scene never waits behind a long tail, and replaces Manim’s unbounded SVG dictionary with a 400-entry LRU cache.
Twenty-four films, frame by frame
Each barcode strip compresses a full render into 300 vertical slices against the #0E1117 background, making a film’s structure visible before pressing play. You can spot the Kármán vortex street in Navier–Stokes where blue velocity gives way to orange vorticity, the rainbow phase portrait of ζ(s) in The Riemann Hypothesis, the orange attention matrices and purple MLP blocks in How LLMs Work, and the domain-colored complex discs opening Analysis, Built Part 7.
The voice is the clock
Contraction (videos/analysis/analysis4/s05_contraction.py) rendered in English (144.8 s) and Chinese (151.3 s) from the same scene class. The 57 self.play() segments run for the exact same 58.6 s in both languages; the 25 vo.wait_until() holds stretch or contract around each language's word timestamps.Scenes never hard-code pause durations. Instead, narration strings carry inline <bookmark mark='...'/> tags that VoiceoverScene strips before synthesis and maps to the exact start timestamp of the following word:
with self.voiceover(
"<bookmark mark='a'/> To solve cosine of x equals x, just iterate ... "
"<bookmark mark='b'/> On the graph, bounce between the curve and the diagonal ..."
) as vo:
self.play(FadeIn(head[0]), Create(ax), Create(dg), FadeIn(dl))
vo.wait_until("a")
self.play(Create(cg), FadeIn(cl))
vo.wait_until("b")
self.play(Create(web), run_time=4.0, rate_func=linear)
Because self.play() durations are fixed while vo.wait_until() absorbs the difference in speech cadence, translating a film requires zero timeline editing. Switching from the English cobweb to the Chinese render, jumping to the shrinking Cauchy discs in Rudin 9.23, or zooming ×127 into the √2 ∉ ℚ hole runs the exact same 82 Manim segments in lockstep with each voice.
One color per concept, one IPA rule per term
explainer/style.py (referenced 11,085 times across 705 scene files) and the 513 regex-to-IPA pronunciation rules in explainer/tts.py (firing 2,808 times across the narration corpus).Across all 705 scene files, mobjects never pass raw hex strings; they import semantic roles from explainer/style.py—tolerance bands in gold (C.EPS) and holes in coral (C.HOLE) across Analysis, Built, or queries, keys, values, and attention across the ML films. On the audio side, a 513-rule pronunciation lexicon injects inline IPA into Kokoro’s G2P front-end so Poisson is never read as “poison”, the differential dy never becomes “die”, and F = m a stays two letters instead of “Emma”.
Hunting dead air
tools/dead_air.py downscales every frame to 320×180 and flags runs lasting at least 1.5 s where fewer than 0.30% of pixels differ from #0E1117—catching scenes where a vo.wait_until() hold sat before the drawing call instead of after it.Watching twenty hours of 4K renders at real-time speed to check whether an equation appeared on time does not scale. tools/dead_air.py streams a render through ffmpeg at 320×180 and measures the fraction of pixels brighter than the #0E1117 background, flagging any stretch of 1.5 seconds or longer below 0.30% (a lone header or bare axes). Running it across the series caught places where a vo.wait_until() call sat before self.play()—leaving 12.4 seconds of bare title before the router load replay, 14.5 seconds before the μP scaling card, or 8.0 seconds before the chain-of-thought faithfulness schematic—and cut sparse stretches across the eleven series films by 91%.