Turn any math formula, paper, or textbook page (PDF, image, or LaTeX) into a study-guide walkthrough and 3Blue1Brown-style animated video
Convert mathematical or technical content into two linked outputs:
$OUT_DIR/walkthrough_final.md — a study guide with plain-language explanations, concrete examples, and embedded animation screenshots$OUT_DIR/video/final_video.mp4 — a 3Blue1Brown-style Manim animation that visualizes the contentEvery run produces its own timestamped directory under output/ so previous results are never overwritten.
/math2video <input> [--narrate] [--hq]
<input>: path to a PDF, image (.png/.jpg/.jpeg), or a quoted LaTeX string--narrate: generate TTS narration using macOS say and merge into video--hq: render at 1080p60; default is 480p15 for fast iterationRun once per machine after cloning:
bash .claude/skills/manim/scripts/setup_env.sh
Verify the environment:
bash .claude/skills/manim/scripts/setup_env.sh --check
If it fails, run setup_env.sh first, then re-check.
Determine the output directory. Derive it from the input and the current timestamp:
paper.pdf → paperlatex_YYYYMMDD_HHMMSSoutput/<basename>_<datetime>/Examples:
paper.pdf → output/paper_20260411_143022/
slide.png → output/slide_20260411_143022/
"E = mc^2" → output/latex_20260411_143022/
Call this $OUT_DIR throughout the rest of the pipeline. Create it now:
mkdir -p $OUT_DIR/frames $OUT_DIR/video
LaTeX string input: use it directly, skip to Phase 3.
Image input (.png, .jpg, .jpeg): use the Read tool to view the file. Analyze all visible content: text, formulas, diagrams, labels, notation.
PDF input:
Extract text and embedded images:
.manim-env/bin/python .claude/skills/math2video/scripts/extract_content.py <input.pdf> --output $OUT_DIR/content.json
Read $OUT_DIR/content.json. For every path listed under "images", use the Read tool to view that image file visually.
Synthesize text extraction + visual inspection into a complete understanding of the material.
Content analysis — record all of the following:
Think carefully as a pedagogy expert. Produce $OUT_DIR/storyboard.json.
Pedagogical principles — apply these strictly:
Scene sequencing pattern:
motivation → intuition/geometry → definition → formula (piece by piece) → worked example → generalization
Storyboard format — write exactly this JSON schema:
{
"title": "...",
"source": "...",
"scenes": [
{
"id": 1,
"class_name": "Scene01Intro",
"type": "title_card",
"narration": "Text read aloud during this scene.",
"walkthrough_section": "Introduction",
"animation_intent": "Precise description of what appears and how it animates."
}
]
}
class_name naming rule: Scene + zero-padded two-digit ID + CamelCase topic, e.g. Scene03AttentionWeights.
Scene types:
| Type | Use for |
|---|---|
title_card | Section headers, topic introductions |
formula_build | Formulas that appear and are colored piece by piece |
concept_explain | Definitions + supporting diagram or annotation |
example_walkthrough | Concrete numeric or worked examples |
graph_plot | Functions, loss curves, distributions, geometry |
geometric_transform | Linear maps, rotations, projections, embeddings |
comparison | Side-by-side two approaches or before/after |
proof_steps | Step-by-step derivations with intermediate lines |
outro | Summary, key takeaways |
Write $OUT_DIR/walkthrough.md as a self-contained study guide.
Standard of clarity: a reader with zero prior exposure to this material must be able to understand everything. No concept may be used before it is explained. No notation may appear before it is defined.
Depth requirements — apply to every section:
Per-section structure:
## Section Name
One or two sentences of motivation — why does this matter, what problem does it solve.
[SCENE_1]
### Core idea
Intuitive explanation in plain language. Analogies welcome.
### Definition
**Term** (formal definition in plain English, then the formula):
$$\text{Formula here in LaTeX}$$
where $x$ is ..., $y$ is ..., etc.
[SCENE_2]
### Example
Work through a concrete numeric example step by step. Show every arithmetic step.
[SCENE_3]
### Why it works
Explain the intuition behind the formula. Connect back to the geometric or physical picture.
Rules for [SCENE_N] markers:
id in the storyboardWrite $OUT_DIR/animation.py. One Scene class per storyboard entry.
Consult these files for API details and patterns:
.claude/skills/manim/examples.md — common patterns.claude/skills/manim/reference.md — full API referenceFile structure:
from manim import *
# ── Shared palette ─────────────────────────────────────────────────────────
TITLE_COLOR = GOLD
FORMULA_COLOR = WHITE
HIGHLIGHT = YELLOW
ACCENT = TEAL
EXAMPLE_COLOR = GREEN_B
# ══ Scene 01 ════════════════════════════════════════════════════════════════
class Scene01Intro(Scene):
def construct(self):
# Beat 1: ...
...
# ══ Scene 02 ════════════════════════════════════════════════════════════════
class Scene02FormulaName(Scene):
def construct(self):
...
3Blue1Brown aesthetic — follow these exactly:
Write() for formulas building in, FadeIn(mob, shift=UP*0.3) for supporting textIndicate() and Circumscribe() to draw attention to specific termsReplacementTransform or TransformFromCopy, never all at onceLaggedStart(..., lag_ratio=0.15) for lists, bullet reveals, multiple objectsMovingCameraScene with self.camera.frame.animate.scale(0.5).move_to(mob) to zoom into detailself.wait(1) — never let a scene cut abruptlySelf-explanatory rule — the video must be fully understandable without any audio:
Text object at the bottom edge. Fade it in at the start of each beat and fade it out before the next beat's caption appears:
caption = Text("Attention assigns a weight to each input token.",
font_size=28, color=GRAY_B).to_edge(DOWN, buff=0.4)
self.play(FadeIn(caption))
self.wait(3)
self.play(FadeOut(caption))
Brace below (or above) each sub-expression and call .get_text("plain-English label").set_color(ACCENT). Never leave a symbol unexplained on screen.key = Text("Key idea: larger dot product → more attention", font_size=30, color=YELLOW)
box = SurroundingRectangle(key, color=YELLOW, buff=0.2)
self.play(FadeIn(key), Create(box))
self.wait(3)
MathTex below the previous one, with a short Text annotation to the right explaining what changed (e.g., "expand", "substitute", "simplify").Layout and pacing rules — non-negotiable:
self.play(FadeOut(old_group)) or self.play(FadeOut(*self.mobjects)) to clear everything..shift() to position multiple items. Use VGroup(*items).arrange(DOWN, buff=0.5) or .arrange(RIGHT, buff=0.4) so spacing is computed automatically and nothing overlaps.self.wait(2). After a full beat, self.wait(3). The audience needs time to read and absorb. Err on the side of too slow.Text() or MathTex() label next to a colored dot or brace.Brace labels, never pass color= to .get_text(). Set color separately: brace.get_text("label").set_color(TEAL) — not brace.get_text("label", color=TEAL).Timestamps file — write one per scene at $OUT_DIR/<class_name>_timestamps.txt:
# Scene01Intro beat timestamps (seconds)
0.0
2.0
4.5
Each line is the time (in seconds from scene start) at which a beat ends — these are the frame extraction points. Add them up from your run_time= and self.wait() values as you write the scene.
Quality suffix reference (needed for paths in Phase 6/7):
| Flag | Output dir suffix |
|---|---|
-ql (default) | 480p15 |
-qm | 720p30 |
-qh | 1080p60 |
-qk | 2160p60 |
Run the static analyser on $OUT_DIR/animation.py before spending time rendering:
.manim-env/bin/python .claude/skills/math2video/scripts/sanity_check.py \
--code $OUT_DIR/animation.py
The script checks each Scene class for:
FadeOut between beats (net add > 3).to_edge(UP) or .move_to(ORIGIN) in the same beat without clearing — items pile upself.wait()If any issues are reported: fix $OUT_DIR/animation.py and re-run the check until it exits cleanly (exit code 0) before proceeding to Phase 6.
Determine the quality flag: l (default) or h (if --hq was passed).
For each scene class in $OUT_DIR/animation.py, run:
bash .claude/skills/manim/scripts/render.sh $OUT_DIR/animation.py <ClassName> [l|h]
After each render, extract key frames:
bash .claude/skills/manim/scripts/extract_frames.sh \
media/videos/animation/<quality_suffix>/<ClassName>.mp4 \
--timestamps $OUT_DIR/<ClassName>_timestamps.txt
Then move frames into the output tree:
mv media/frames/<ClassName> $OUT_DIR/frames/
After all scenes are rendered, run the frame density check:
.manim-env/bin/python .claude/skills/math2video/scripts/sanity_check.py \
--frames $OUT_DIR/frames
A frame flagged as "crowded" (>25% non-black pixels) means too much is on screen — fix that scene in $OUT_DIR/animation.py and re-render it before continuing.
--narrate)After all scenes are rendered, generate narration audio and merge it into each scene video:
.manim-env/bin/python .claude/skills/math2video/scripts/tts.py \
$OUT_DIR/storyboard.json \
media/videos/animation/<quality_suffix> \
$OUT_DIR/video
This writes $OUT_DIR/video/<ClassName>_narrated.mp4 for each scene that has narration text.
Use narrated videos (if they exist) and non-narrated videos (for scenes without narration) when building the final concat list in Phase 8.
Step 1 — Replace [SCENE_N] markers with screenshots:
.manim-env/bin/python .claude/skills/math2video/scripts/assemble.py \
$OUT_DIR/walkthrough.md \
$OUT_DIR/storyboard.json \
$OUT_DIR/frames \
$OUT_DIR/walkthrough_final.md
Step 2 — Stitch into final video (list scenes in storyboard order):
bash .claude/skills/manim/scripts/concat.sh $OUT_DIR/video/final_video.mp4 \
$OUT_DIR/video/Scene01Intro_narrated.mp4 \
$OUT_DIR/video/Scene02Formula_narrated.mp4 \
...
If --narrate was not passed, use the raw rendered mp4s from media/videos/animation/<quality_suffix>/ instead.
Each run creates a separate directory:
output/
paper_20260411_143022/ ← one directory per run
storyboard.json # shared pipeline spine
content.json # extracted PDF content (if PDF input)
walkthrough.md # draft with [SCENE_N] markers
walkthrough_final.md # final study guide with embedded screenshots
animation.py # Manim source (editable)
Scene01Intro_timestamps.txt
Scene02Formula_timestamps.txt
...
frames/
Scene01Intro/
frame_000.000.png
frame_002.000.png
Scene02Formula/
...
video/
Scene01Intro_narrated.mp4 # (if --narrate)
...
final_video.mp4
paper_20260412_091500/ ← previous run untouched
...
setup_env.sh again), undefined variable (fix the Python), MathTex syntax error (check LaTeX string). Fix and re-render that scene only.$OUT_DIR/storyboard.json and re-run tts.py.