manim-skill · diff
git:20260126.efb82d2 to git:20260712.17bec1f
89 added, 130 removed. Audit B to A.
---
name: manim-skill
- description: Create mathematical animations with Manim Community Edition(manimce). Generates distinctive, production-grade animations that avoid generic "AI slop" aesthetics. Use when user wants to animate concepts, equations, illustrate proofs, visualize algorithms, create math explainers, or produce 3Blue1Brown-style videos.
+ description: Create mathematical animations with Manim Community Edition(manimce), with optional TTS voiceover and synced subtitles. Generates distinctive, production-grade animations that avoid generic "AI slop" aesthetics. Use when user wants to animate concepts, equations, illustrate proofs, visualize algorithms, create math explainers, or produce 3Blue1Brown-style videos.
---
- ## Project Structure
-
- Create a dedicated folder for each animation project **in the user's current working directory** (NOT in this skill's base directory). Use this flat structure:
+ You are a Manim animation expert. Create production-grade animations with Manim Community Edition (manimce).
- ```
- <project-name>/
- ├── plan.md # Planning document (Phase 1)
- ├── script.py # Manim code (Phase 2)
- ├── concat.txt # ffmpeg scene list (Phase 3)
- ├── final.mp4 # Stitched output (Phase 3)
- └── media/ # Auto-generated by manim
- └── videos/
- └── script/
- └── 480p15/ # or 1080p60
- ├── Scene1_Name.mp4
- ├── Scene2_Name.mp4
- └── ...
- ```
+ ## Contract
- **Before starting**: Create the project folder and work from within it. All commands should be run from the project directory.
+ - Treat every prompt as an animation request. Never stop to ask the user to review a plan unless they explicitly asked for that. Continue until `video.mp4` exists; if blocked, return the exact blocker plainly.
+ - Always output exactly `plan.md`, `script.py`, and `video.mp4` — never other names, regardless of aspect ratio or mode.
- ```bash
- mkdir -p <project-name> && cd <project-name>
- ```
+ ## Session Storage
- ## Workflow Overview
+ **NEVER write artifacts into the user's working directory.** Every video is a flat session folder:
```
- Plan → Code → Render → Iterate
+ SESSION_DIR = ${MANIM_SKILL_HOME:-~/.manim-skill}/<project-slug>/
+ ├── plan.md # plan + SubtitleSpec
+ ├── voiceover.mp3 # concatenated TTS audio
+ ├── timestamps.json # per-subtitle timing
+ ├── script.py # Manim code
+ ├── concat.txt # ffmpeg scene list
+ ├── video.mp4 # final deliverable
+ └── media/ # manim output
```
- ### Phase 1: Plan
-
- **If the user has already used plan mode in claude code or provided detailed requirements, write that in `plan.md` and skip to Phase 2. Otherwise, you MUST carefully plan the video structure before writing code.**
-
- Before writing any Manim code, wrtie `plan.md`:
-
- ```markdown
- # [Video Title]
-
- ## Overview
- - **Topic**: [Core concept]
- - **Hook**: [Opening question/mystery]
- - **Target Audience**: [Prerequisites]
- - **Estimated Length**: [X minutes]
- - **Key Insight**: [The "aha moment"]
- - **Resolution**: [480p(default), 1080p]
- - **Aspect Ratio**: [16:9(default) / 9:16 / 1:1]
-
- ## Narrative Arc
- [2-3 sentences describing the journey from confusion to understanding]
-
- ---
-
- ## Scene 1: [Scene Name]
- **Duration**: ~X seconds
- **Purpose**: [What this scene accomplishes]
-
- ### Visual Elements
- - [List of mobjects needed]
- - [Animations to use]
- - [Camera movements]
+ `<project-slug>` is a short kebab-case topic name (append `-2`, `-3`, ... on collision). `TOOLS_DIR` is the `tools/` directory next to this SKILL.md. When the video is about the user's code or files, read those from their original location — only outputs go in `SESSION_DIR`.
- ### Content
- [Detailed description of what happens, what's shown, what's explained]
+ **Setup — run once at the very start**, then stay in `SESSION_DIR` for every phase so outputs land there; substitute `<TOOLS_DIR>` in the commands below with its absolute path (the tools are invoked in place, never copied):
- ### Voiceover
- - **Text**: "[Exact script or key points]"
- - **Sync Points**: "[phrase]" → syncs with [animation]
+ ```bash
+ SESSION_DIR="${MANIM_SKILL_HOME:-$HOME/.manim-skill}/<project-slug>"
+ mkdir -p "$SESSION_DIR"
+ cd "$SESSION_DIR"
+ ```
- ### Technical Notes
- - [Specific Manim classes/methods to use]
- - [Any tricky implementations to note]
+ **Voiceover is on by default.** Skip TTS (Phases 2 and 5, subtitles, and `add_subcaption()`) only if the user asks for no TTS/narration/voiceover/captions, or the local `kokoro` package is not installed (then tell the user voiceover was skipped and that `uv add "kokoro>=0.9.4" soundfile` enables it).
- ---
+ ## Workflow: Plan → TTS → Code → Render → Mux → View
- ## Scene 2: [Scene Name]
- ...
+ ### Phase 1: Plan
- ---
+ Write `plan.md`: title; overview (topic, hook, audience, estimated length, key insight, aspect ratio — 16:9 default); narrative arc; one section per scene (name, ~duration, purpose, visual elements, content); color palette. If the user already provided detailed requirements, record them in `plan.md` and move on.
- ## Transitions & Flow
- [Notes on how scenes connect, recurring visual motifs]
+ End `plan.md` with the SubtitleSpec — **this exact format is parsed by `tts-generate.py`** (omit the whole section when TTS is off, and don't use `add_subcaption()` in code). Kokoro speaks ≈2.5 words/second — size the lines to the target video length:
- ## Shared Elements
- - [Recurring mobjects across scenes, e.g., "coordinate axes reappear in scenes 2, 4, 6"]
- - [Visual motifs, e.g., "blue highlight for key terms"]
+ ```
+ subtitles:
+ - First voiceover line.
+ - Second voiceover line.
+ - Each entry becomes one TTS clip.
+ ```
- ## Color Palette
- - Primary: [color] - used for [purpose]
- - Secondary: [color] - used for [purpose]
- - Accent: [color] - used for [purpose]
- - Background: [color]
+ ### Phase 2: TTS
```
+ python "<TOOLS_DIR>/tts-generate.py" --plan plan.md
+ ```
- ### Phase 2: Code
+ Local Kokoro voice `af_heart` by default; `--voice <name>` (e.g. `am_adam`, `bf_emma`) for another. Unchanged lines are served from `.tts-cache/`.
- **BEFORE writing any code, you MUST invoke the `manimce-best-practices` skill using the Skill tool, then read the relevant rule files it references (e.g., `rules/scenes.md`, `rules/animations.md`) based on what your video needs.**
+ Produces `voiceover.mp3` and `timestamps.json`. The run prints how many clips it found — confirm it matches your SubtitleSpec line count (a mismatch means the block didn't parse; fix the format in `plan.md`). **Read `timestamps.json` before writing code** — each `duration_s` is the exact time budget for that subtitle's segment.
- Write Manim Community Edition code in `script.py`.
+ ### Phase 3: Code
- #### Code Structure
- 1. **One class per scene** - Name scenes descriptively: `Scene1_Introduction`, `Scene2_DerivePDE`
- 2. Always add subtitles to your animations with `add_subcaption()`
- - Manim generates a `.srt` file automatically
- - The viewer handles subtitle display and lets users toggle/resize them
+ Write `script.py`:
+ 1. **One class per scene**, named descriptively (`Scene1_Introduction`, `Scene2_DerivePDE`) — this is what enables selective re-render during feedback and chapter navigation in the viewer.
+ 2. **Render config lives in `script.py`** — set `config.pixel_width`, `config.pixel_height`, `config.frame_width`, `config.frame_height`, `config.frame_rate` there (it determines output paths). Iteration defaults unless the user asks otherwise:
+ - `16:9` → 854×480, frame 16×9, 15 fps
+ - `9:16` → 480×854, frame 9×16, 15 fps
+ - `1:1` → 480×480, frame 8×8, 15 fps
+ Keep the pixel and frame aspect ratios matched.
+ 3. **Each SubtitleSpec line becomes exactly one `self.add_subcaption(text, duration=duration_s)` call — same text, same order** (a scene may hold several consecutive segments). Never burn subtitles in as text mobjects: `add_subcaption()` emits the per-scene `.srt` files the linter and viewer depend on. Inline each `duration_s` from `timestamps.json` as a literal, set explicit `run_time=` on every `play()`, keep total scheduled time (including `wait()` and `move_camera()`) within the budget, and end each segment with `self.wait(max(0, dur - used))`.
+ 4. **`MathTex(...)` for formulas; prefer `Tex(...)` over `Text(...)` for prose** (Pango can produce odd letter spacing).
+
```python
from manim import *
- ## Scene1_Introduction
class Scene1_Introduction(Scene):
def construct(self):
- ## Scene1_Introduction.title
- title = Text("My Topic", font_size=48, color=BLUE)
- self.add_subcaption("Introducing our topic", duration=2)
- self.play(Write(title))
- self.wait(1)
+ title = Tex("My Topic", font_size=48, color=BLUE)
- ## Scene1_Introduction.fadeout
- self.play(FadeOut(title), subcaption="Let's begin", subcaption_duration=1)
+ dur = 2.8 # duration_s from timestamps.json
+ anim_rt = 1.5
+ self.add_subcaption("Introduction to My Topic", duration=dur)
+ self.play(Write(title), run_time=anim_rt)
+ self.wait(max(0, dur - anim_rt))
```
- ### Phase 3: Render
+ ### Phase 4: Render
- Use `manim` CLI to render scenes. Multiple scenes can be rendered in parallel with one command.
+ Lint first — render only when it passes:
- ```bash
- manim -q<quality> [--media_dir <output_dir>] <script.py> Scene1 Scene2 Scene3 ...
```
-
- **Quality flags:**
- - `-ql` - Low quality (480p15, fastest for testing. Use by default)
- - `-qh` - High quality (1080p60, only when user explicitly requests)
+ python "<TOOLS_DIR>/lint-subtitles.py" script.py
+ ```
- **Output location:**
- `/media/videos/<script_name>/<quality>/SceneName.mp4`
+ ```
+ manim script.py Scene1_Introduction Scene2_Main ...
+ ```
- Default `/media` is located in current directory.
+ Scenes land in `media/videos/script/<pixel_height>p<frame_rate:g>/SceneName.mp4`. Write `concat.txt` listing them in order (the viewer needs it for chapters):
- **If any scene fails:** Read the error, fix the code, re-render only the failed scenes.
+ ```
+ file 'media/videos/script/480p15/Scene1_Introduction.mp4'
+ file 'media/videos/script/480p15/Scene2_Main.mp4'
+ ```
- #### Stitch scenes with ffmpeg
+ ```
+ ffmpeg -y -f concat -safe 0 -i concat.txt -c copy -movflags +faststart video_silent.mp4
+ ```
- Once all scenes render successfully, combine them. All paths are relative to the project folder:
+ **Without voiceover**, write `video.mp4` here instead and skip Phase 5.
- ```bash
- # Create concat list (run from project folder)
- cat > concat.txt << 'EOF'
- file 'media/videos/script/480p15/Scene1_Intro.mp4'
- file 'media/videos/script/480p15/Scene2_Main.mp4'
- file 'media/videos/script/480p15/Scene3_Conclusion.mp4'
- EOF
+ ### Phase 5: Mux
- # Combine videos
- ffmpeg -y -f concat -safe 0 -i concat.txt -c copy final.mp4
```
-
- **Result**: `final.mp4` is created in the project folder alongside `script.py` and `concat.txt`.
+ ffmpeg -y -i video_silent.mp4 -i voiceover.mp3 \
+ -map 0:v:0 -map 1:a:0 \
+ -c:v copy -c:a aac \
+ -movflags +faststart video.mp4
+ ```
- ### Phase 4: Iterate on User Feedback
+ ### Phase 6: View & Feedback
- Launch the viewer for user to review and provide feedback (run from project folder):
+ Launch the viewer from `SESSION_DIR`, in the background:
- ```bash
- python3 ../tools/video_viewer.py final.mp4 --order concat.txt [--script script.py]
```
+ python3 <TOOLS_DIR>/video_viewer.py video.mp4 --order concat.txt --script script.py --plan plan.md
+ ```
- - `--order`: Video order file (concat.txt, required for chapters)
- - `--script`: Manim script (enables high-quality download option)
+ It prints `VIDEO_READY http://localhost:<port>` and opens the browser. Tell the user the URL and the `SESSION_DIR` path.
- When user provides feedback:
- 1. Identify which scenes need changes
- 2. Modify the code
- 3. Re-render only affected scenes
- 4. Re-stitch the final video
- 5. Run the viewer command again
+ The viewer's Capture button (`T`) drops timestamped lines into the feedback panel, e.g. `[0:07] Scene3_Identity: the c² label overlaps the triangle`. When feedback references a moment like that, extract that exact frame (`ffmpeg -y -ss 7 -i video.mp4 -vframes 1 f.png`) and Read it before touching code — seeing what the user saw beats guessing.
+
+ Then iterate:
+
+ 1. If voiceover lines changed: edit the SubtitleSpec, re-run Phase 2 (unchanged lines are cache hits; `python "<TOOLS_DIR>/tts-generate.py" --bust <index or text fragment>` force-regenerates one clip), and update `script.py` to the new `timestamps.json`.
+ 2. Fix the named scenes, lint, re-render **only the affected scenes**, re-mux.
+ 3. The viewer serves the new `video.mp4` on reload; restart it only if it was stopped.