From 9d885b266c8433ea4f641b362edd7ddd2abdb2ea Mon Sep 17 00:00:00 2001 From: SHL0MS Date: Fri, 3 Apr 2026 22:51:25 -0400 Subject: [PATCH] feat(skills): add manim-video skill for mathematical and technical animations Production pipeline for creating 3Blue1Brown-style animated videos using Manim Community Edition. The agent handles the full workflow: creative planning, Python code generation, rendering, scene stitching, audio muxing, and iterative refinement. Modes: concept explainers, equation derivations, algorithm visualizations, data stories, architecture diagrams, paper explainers, 3D visualizations. 9 reference files, setup verification script, README. All API references verified against ManimCommunity/manim source. --- skills/creative/manim-video/README.md | 23 ++ skills/creative/manim-video/SKILL.md | 231 ++++++++++++++++++ .../manim-video/references/animations.md | 122 +++++++++ .../manim-video/references/camera-and-3d.md | 76 ++++++ .../manim-video/references/equations.md | 80 ++++++ .../manim-video/references/graphs-and-data.md | 91 +++++++ .../manim-video/references/mobjects.md | 106 ++++++++ .../manim-video/references/rendering.md | 93 +++++++ .../manim-video/references/scene-planning.md | 118 +++++++++ .../manim-video/references/troubleshooting.md | 135 ++++++++++ .../manim-video/references/visual-design.md | 119 +++++++++ skills/creative/manim-video/scripts/setup.sh | 14 ++ 12 files changed, 1208 insertions(+) create mode 100644 skills/creative/manim-video/README.md create mode 100644 skills/creative/manim-video/SKILL.md create mode 100644 skills/creative/manim-video/references/animations.md create mode 100644 skills/creative/manim-video/references/camera-and-3d.md create mode 100644 skills/creative/manim-video/references/equations.md create mode 100644 skills/creative/manim-video/references/graphs-and-data.md create mode 100644 skills/creative/manim-video/references/mobjects.md create mode 100644 skills/creative/manim-video/references/rendering.md create mode 100644 skills/creative/manim-video/references/scene-planning.md create mode 100644 skills/creative/manim-video/references/troubleshooting.md create mode 100644 skills/creative/manim-video/references/visual-design.md create mode 100755 skills/creative/manim-video/scripts/setup.sh diff --git a/skills/creative/manim-video/README.md b/skills/creative/manim-video/README.md new file mode 100644 index 000000000..4ed03d892 --- /dev/null +++ b/skills/creative/manim-video/README.md @@ -0,0 +1,23 @@ +# Manim Video Skill + +Production pipeline for mathematical and technical animations using [Manim Community Edition](https://www.manim.community/). + +## What it does + +Creates 3Blue1Brown-style animated videos from text prompts. The agent handles the full pipeline: creative planning, Python code generation, rendering, scene stitching, and iterative refinement. + +## Use cases + +- **Concept explainers** — "Explain how neural networks learn" +- **Equation derivations** — "Animate the proof of the Pythagorean theorem" +- **Algorithm visualizations** — "Show how quicksort works step by step" +- **Data stories** — "Animate our before/after performance metrics" +- **Architecture diagrams** — "Show our microservice architecture building up" + +## Prerequisites + +Python 3.10+, Manim CE (`pip install manim`), LaTeX, ffmpeg. + +```bash +bash skills/creative/manim-video/scripts/setup.sh +``` diff --git a/skills/creative/manim-video/SKILL.md b/skills/creative/manim-video/SKILL.md new file mode 100644 index 000000000..34e6f7e67 --- /dev/null +++ b/skills/creative/manim-video/SKILL.md @@ -0,0 +1,231 @@ +--- +name: manim-video +description: "Production pipeline for mathematical and technical animations using Manim Community Edition. Creates 3Blue1Brown-style explainer videos, algorithm visualizations, equation derivations, architecture diagrams, and data stories. Use when users request: animated explanations, math animations, concept visualizations, algorithm walkthroughs, technical explainers, 3Blue1Brown style videos, or any programmatic animation with geometric/mathematical content." +version: 1.0.0 +--- + +# Manim Video Production Pipeline + +## Creative Standard + +This is educational cinema. Every frame teaches. Every animation reveals structure. + +**Before writing a single line of code**, articulate the narrative arc. What misconception does this correct? What is the "aha moment"? What visual story takes the viewer from confusion to understanding? The user's prompt is a starting point — interpret it with pedagogical ambition. + +**Geometry before algebra.** Show the shape first, the equation second. Visual memory encodes faster than symbolic memory. When the viewer sees the geometric pattern before the formula, the equation feels earned. + +**First-render excellence is non-negotiable.** The output must be visually clear and aesthetically cohesive without revision rounds. If something looks cluttered, poorly timed, or like "AI-generated slides," it is wrong. + +**Opacity layering directs attention.** Never show everything at full brightness. Primary elements at 1.0, contextual elements at 0.4, structural elements (axes, grids) at 0.15. The brain processes visual salience in layers. + +**Breathing room.** Every animation needs `self.wait()` after it. The viewer needs time to absorb what just appeared. Never rush from one animation to the next. A 2-second pause after a key reveal is never wasted. + +**Cohesive visual language.** All scenes share a color palette, consistent typography sizing, matching animation speeds. A technically correct video where every scene uses random different colors is an aesthetic failure. + +## Prerequisites + +Run `scripts/setup.sh` to verify all dependencies. Requires: Python 3.10+, Manim Community Edition (`pip install manim`), LaTeX (`texlive-full` on Linux, `mactex` on macOS), and ffmpeg. + +## Modes + +| Mode | Input | Output | Reference | +|------|-------|--------|-----------| +| **Concept explainer** | Topic/concept | Animated explanation with geometric intuition | `references/scene-planning.md` | +| **Equation derivation** | Math expressions | Step-by-step animated proof | `references/equations.md` | +| **Algorithm visualization** | Algorithm description | Step-by-step execution with data structures | `references/graphs-and-data.md` | +| **Data story** | Data/metrics | Animated charts, comparisons, counters | `references/graphs-and-data.md` | +| **Architecture diagram** | System description | Components building up with connections | `references/mobjects.md` | +| **Paper explainer** | Research paper | Key findings and methods animated | `references/scene-planning.md` | +| **3D visualization** | 3D concept | Rotating surfaces, parametric curves, spatial geometry | `references/camera-and-3d.md` | + +## Stack + +Single Python script per project. No browser, no Node.js, no GPU required. + +| Layer | Tool | Purpose | +|-------|------|---------| +| Core | Manim Community Edition | Scene rendering, animation engine | +| Math | LaTeX (texlive/MiKTeX) | Equation rendering via `MathTex` | +| Video I/O | ffmpeg | Scene stitching, format conversion, audio muxing | +| TTS | ElevenLabs / Qwen3-TTS (optional) | Narration voiceover | + +## Pipeline + +``` +PLAN --> CODE --> RENDER --> STITCH --> AUDIO (optional) --> REVIEW +``` + +1. **PLAN** — Write `plan.md` with narrative arc, scene list, visual elements, color palette, voiceover script +2. **CODE** — Write `script.py` with one class per scene, each independently renderable +3. **RENDER** — `manim -ql script.py Scene1 Scene2 ...` for draft, `-qh` for production +4. **STITCH** — ffmpeg concat of scene clips into `final.mp4` +5. **AUDIO** (optional) — Add voiceover and/or background music via ffmpeg. See `references/rendering.md` +6. **REVIEW** — Render preview stills, verify against plan, adjust + +## Project Structure + +``` +project-name/ + plan.md # Narrative arc, scene breakdown + script.py # All scenes in one file + concat.txt # ffmpeg scene list + final.mp4 # Stitched output + media/ # Auto-generated by Manim + videos/script/480p15/ +``` + +## Creative Direction + +### Color Palettes + +| Palette | Background | Primary | Secondary | Accent | Use case | +|---------|-----------|---------|-----------|--------|----------| +| **Classic 3B1B** | `#1C1C1C` | `#58C4DD` (BLUE) | `#83C167` (GREEN) | `#FFFF00` (YELLOW) | General math/CS | +| **Warm academic** | `#2D2B55` | `#FF6B6B` | `#FFD93D` | `#6BCB77` | Approachable | +| **Neon tech** | `#0A0A0A` | `#00F5FF` | `#FF00FF` | `#39FF14` | Systems, architecture | +| **Monochrome** | `#1A1A2E` | `#EAEAEA` | `#888888` | `#FFFFFF` | Minimalist | + +### Animation Speed + +| Context | run_time | self.wait() after | +|---------|----------|-------------------| +| Title/intro appear | 1.5s | 1.0s | +| Key equation reveal | 2.0s | 2.0s | +| Transform/morph | 1.5s | 1.5s | +| Supporting label | 0.8s | 0.5s | +| FadeOut cleanup | 0.5s | 0.3s | +| "Aha moment" reveal | 2.5s | 3.0s | + +### Typography Scale + +| Role | Font size | Usage | +|------|-----------|-------| +| Title | 48 | Scene titles, opening text | +| Heading | 36 | Section headers within a scene | +| Body | 30 | Explanatory text | +| Label | 24 | Annotations, axis labels | +| Caption | 20 | Subtitles, fine print | + +### Fonts + +Always specify fonts explicitly — the default renders poorly. See `references/visual-design.md` for full recommendations. + +```python +Text("Title", font_size=48, font="Inter", weight=BOLD) # body text +Text("code()", font_size=24, font="JetBrains Mono") # monospaced +MathTex(r"\nabla L") # math (uses LaTeX) +``` + +### Per-Scene Variation + +Never use identical config for all scenes. For each scene: +- **Different dominant color** from the palette +- **Different layout** — don't always center everything +- **Different animation entry** — vary between Write, FadeIn, GrowFromCenter, Create +- **Different visual weight** — some scenes dense, others sparse + +## Workflow + +### Step 1: Plan (plan.md) + +Before any code, write `plan.md`. See `references/scene-planning.md` for the comprehensive template. + +### Step 2: Code (script.py) + +One class per scene. Every scene is independently renderable. + +```python +from manim import * + +BG = "#1C1C1C" +PRIMARY = "#58C4DD" +SECONDARY = "#83C167" +ACCENT = "#FFFF00" + +class Scene1_Introduction(Scene): + def construct(self): + self.camera.background_color = BG + title = Text("Why Does This Work?", font_size=48, color=PRIMARY) + self.add_subcaption("Why does this work?", duration=2) + self.play(Write(title), run_time=1.5) + self.wait(1.0) + self.play(FadeOut(title), run_time=0.5) +``` + +Key patterns: +- **Subtitles** on every animation: `self.add_subcaption("text", duration=N)` or `subcaption="text"` on `self.play()` +- **Shared color constants** at file top for cross-scene consistency +- **`self.camera.background_color`** set in every scene +- **Clean exits** — FadeOut all mobjects at scene end: `self.play(FadeOut(Group(*self.mobjects)))` + +### Step 3: Render + +```bash +manim -ql script.py Scene1_Introduction Scene2_CoreConcept # draft +manim -qh script.py Scene1_Introduction Scene2_CoreConcept # production +``` + +### Step 4: Stitch + +```bash +cat > concat.txt << 'EOF' +file 'media/videos/script/480p15/Scene1_Introduction.mp4' +file 'media/videos/script/480p15/Scene2_CoreConcept.mp4' +EOF +ffmpeg -y -f concat -safe 0 -i concat.txt -c copy final.mp4 +``` + +### Step 5: Review + +```bash +manim -ql --format=png -s script.py Scene2_CoreConcept # preview still +``` + +## Critical Implementation Notes + +### Raw Strings for LaTeX +```python +# WRONG: MathTex("\frac{1}{2}") +# RIGHT: +MathTex(r"\frac{1}{2}") +``` + +### buff >= 0.5 for Edge Text +```python +label.to_edge(DOWN, buff=0.5) # never < 0.5 +``` + +### FadeOut Before Replacing Text +```python +self.play(ReplacementTransform(note1, note2)) # not Write(note2) on top +``` + +### Never Animate Non-Added Mobjects +```python +self.play(Create(circle)) # must add first +self.play(circle.animate.set_color(RED)) # then animate +``` + +## Performance Targets + +| Quality | Resolution | FPS | Speed | +|---------|-----------|-----|-------| +| `-ql` (draft) | 854x480 | 15 | 5-15s/scene | +| `-qm` (medium) | 1280x720 | 30 | 15-60s/scene | +| `-qh` (production) | 1920x1080 | 60 | 30-120s/scene | + +Always iterate at `-ql`. Only render `-qh` for final output. + +## References + +| File | Contents | +|------|----------| +| `references/animations.md` | Core animations, rate functions, composition, `.animate` syntax, timing patterns | +| `references/mobjects.md` | Text, shapes, VGroup/Group, positioning, styling, custom mobjects | +| `references/visual-design.md` | 12 design principles, opacity layering, layout templates, color palettes | +| `references/equations.md` | LaTeX in Manim, TransformMatchingTex, derivation patterns | +| `references/graphs-and-data.md` | Axes, plotting, BarChart, animated data, algorithm visualization | +| `references/camera-and-3d.md` | MovingCameraScene, ThreeDScene, 3D surfaces, camera control | +| `references/scene-planning.md` | Narrative arcs, layout templates, scene transitions, planning template | +| `references/rendering.md` | CLI reference, quality presets, ffmpeg, voiceover workflow, GIF export | +| `references/troubleshooting.md` | LaTeX errors, animation errors, common mistakes, debugging | diff --git a/skills/creative/manim-video/references/animations.md b/skills/creative/manim-video/references/animations.md new file mode 100644 index 000000000..b0ca0ab73 --- /dev/null +++ b/skills/creative/manim-video/references/animations.md @@ -0,0 +1,122 @@ +# Animations Reference + +## Core Concept + +An animation is a Python object that computes intermediate visual states of a mobject over time. Animations are objects passed to `self.play()`, not functions. + +`run_time` controls seconds (default: 1). Always specify it explicitly for important animations. + +## Creation Animations + +```python +self.play(Create(circle)) # traces outline +self.play(Write(equation)) # simulates handwriting (for Text/MathTex) +self.play(FadeIn(group)) # opacity 0 -> 1 +self.play(GrowFromCenter(dot)) # scale 0 -> 1 from center +self.play(DrawBorderThenFill(sq)) # outline first, then fill +``` + +## Removal Animations + +```python +self.play(FadeOut(mobject)) # opacity 1 -> 0 +self.play(Uncreate(circle)) # reverse of Create +self.play(ShrinkToCenter(group)) # scale 1 -> 0 +``` + +## Transform Animations + +```python +# Transform -- modifies the original in place +self.play(Transform(circle, square)) +# After: circle IS the square (same object, new appearance) + +# ReplacementTransform -- replaces old with new +self.play(ReplacementTransform(circle, square)) +# After: circle removed, square on screen + +# TransformMatchingTex -- smart equation morphing +eq1 = MathTex(r"a^2 + b^2") +eq2 = MathTex(r"a^2 + b^2 = c^2") +self.play(TransformMatchingTex(eq1, eq2)) +``` + +**Critical**: After `Transform(A, B)`, variable `A` references the on-screen mobject. Variable `B` is NOT on screen. Use `ReplacementTransform` when you want to work with `B` afterwards. + +## The .animate Syntax + +```python +self.play(circle.animate.set_color(RED)) +self.play(circle.animate.shift(RIGHT * 2).scale(0.5)) # chain multiple +``` + +## Emphasis Animations + +```python +self.play(Indicate(mobject)) # brief yellow flash + scale +self.play(Circumscribe(mobject)) # draw rectangle around it +self.play(Flash(point)) # radial flash +self.play(Wiggle(mobject)) # shake side to side +``` + +## Rate Functions + +```python +self.play(FadeIn(mob), rate_func=smooth) # default: ease in/out +self.play(FadeIn(mob), rate_func=linear) # constant speed +self.play(FadeIn(mob), rate_func=rush_into) # start slow, end fast +self.play(FadeIn(mob), rate_func=rush_from) # start fast, end slow +self.play(FadeIn(mob), rate_func=there_and_back) # animate then reverse +``` + +## Composition + +```python +# Simultaneous +self.play(FadeIn(title), Create(circle), run_time=2) + +# AnimationGroup with lag +self.play(AnimationGroup(*[FadeIn(i) for i in items], lag_ratio=0.2)) + +# LaggedStart +self.play(LaggedStart(*[Write(l) for l in lines], lag_ratio=0.3, run_time=3)) + +# Succession (sequential in one play call) +self.play(Succession(FadeIn(title), Wait(0.5), Write(subtitle))) +``` + +## Updaters + +```python +tracker = ValueTracker(0) +dot = Dot().add_updater(lambda m: m.move_to(axes.c2p(tracker.get_value(), 0))) +self.play(tracker.animate.set_value(5), run_time=3) +``` + +## Subtitles + +```python +# Method 1: standalone +self.add_subcaption("Key insight", duration=2) +self.play(Write(equation), run_time=2.0) + +# Method 2: inline +self.play(Write(equation), subcaption="Key insight", subcaption_duration=2) +``` + +Manim auto-generates `.srt` subtitle files. Always add subcaptions for accessibility. + +## Timing Patterns + +```python +# Pause-after-reveal +self.play(Write(key_equation), run_time=2.0) +self.wait(2.0) + +# Dim-and-focus +self.play(old_content.animate.set_opacity(0.3), FadeIn(new_content)) + +# Clean exit +self.play(FadeOut(Group(*self.mobjects)), run_time=0.5) +self.wait(0.3) +``` diff --git a/skills/creative/manim-video/references/camera-and-3d.md b/skills/creative/manim-video/references/camera-and-3d.md new file mode 100644 index 000000000..71448ad60 --- /dev/null +++ b/skills/creative/manim-video/references/camera-and-3d.md @@ -0,0 +1,76 @@ +# Camera and 3D Reference + +## MovingCameraScene (2D Camera Control) + +```python +class ZoomExample(MovingCameraScene): + def construct(self): + circle = Circle(radius=2, color=BLUE) + self.play(Create(circle)) + # Zoom in + self.play(self.camera.frame.animate.set(width=4).move_to(circle.get_top()), run_time=2) + self.wait(2) + # Zoom back out + self.play(self.camera.frame.animate.set(width=14.222).move_to(ORIGIN), run_time=2) +``` + +### Camera Operations + +```python +self.camera.frame.animate.set(width=6) # zoom in +self.camera.frame.animate.set(width=20) # zoom out +self.camera.frame.animate.move_to(target) # pan +self.camera.frame.save_state() # save +self.play(Restore(self.camera.frame)) # restore +``` + +## ThreeDScene + +```python +class ThreeDExample(ThreeDScene): + def construct(self): + self.set_camera_orientation(phi=60*DEGREES, theta=-45*DEGREES) + axes = ThreeDAxes() + surface = Surface( + lambda u, v: axes.c2p(u, v, np.sin(u) * np.cos(v)), + u_range=[-PI, PI], v_range=[-PI, PI], resolution=(30, 30) + ) + surface.set_color_by_gradient(BLUE, GREEN, YELLOW) + self.play(Create(axes), Create(surface)) + self.begin_ambient_camera_rotation(rate=0.2) + self.wait(5) + self.stop_ambient_camera_rotation() +``` + +### Camera Control in 3D + +```python +self.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES) +self.move_camera(phi=45*DEGREES, theta=30*DEGREES, run_time=2) +self.begin_ambient_camera_rotation(rate=0.2) +``` + +### 3D Mobjects + +```python +sphere = Sphere(radius=1).set_color(BLUE).set_opacity(0.7) +cube = Cube(side_length=2, fill_color=GREEN, fill_opacity=0.5) +arrow = Arrow3D(start=ORIGIN, end=[2, 1, 1], color=RED) +# 2D text facing camera: +label = Text("Label", font_size=30) +self.add_fixed_in_frame_mobjects(label) +``` + +### Parametric Curves + +```python +helix = ParametricFunction( + lambda t: [np.cos(t), np.sin(t), t / (2*PI)], + t_range=[0, 4*PI], color=YELLOW +) +``` + +## When to Use 3D +- Surfaces, vector fields, spatial geometry, 3D transforms +## When NOT to Use 3D +- 2D concepts, text-heavy scenes, flat data (bar charts, time series) diff --git a/skills/creative/manim-video/references/equations.md b/skills/creative/manim-video/references/equations.md new file mode 100644 index 000000000..183691fb5 --- /dev/null +++ b/skills/creative/manim-video/references/equations.md @@ -0,0 +1,80 @@ +# Equations and LaTeX Reference + +## Basic LaTeX + +```python +eq = MathTex(r"E = mc^2") +eq = MathTex(r"f(x) &= x^2 + 2x + 1 \\ &= (x + 1)^2") # multi-line aligned +``` + +**Always use raw strings (`r""`).** + +## Step-by-Step Derivations + +```python +step1 = MathTex(r"a^2 + b^2 = c^2") +step2 = MathTex(r"a^2 = c^2 - b^2") +self.play(Write(step1), run_time=1.5) +self.wait(1.5) +self.play(TransformMatchingTex(step1, step2), run_time=1.5) +``` + +## Selective Color + +```python +eq = MathTex(r"a^2", r"+", r"b^2", r"=", r"c^2") +eq[0].set_color(RED) +eq[4].set_color(GREEN) +``` + +## Building Incrementally + +```python +parts = MathTex(r"f(x)", r"=", r"\sum_{n=0}^{\infty}", r"\frac{f^{(n)}(a)}{n!}", r"(x-a)^n") +self.play(Write(parts[0:2])) +self.wait(0.5) +self.play(Write(parts[2])) +self.wait(0.5) +self.play(Write(parts[3:])) +``` + +## Highlighting + +```python +highlight = SurroundingRectangle(eq[2], color=YELLOW, buff=0.1) +self.play(Create(highlight)) +self.play(Indicate(eq[4], color=YELLOW)) +``` + +## Annotation + +```python +brace = Brace(eq, DOWN, color=YELLOW) +label = brace.get_text("Fundamental Theorem", font_size=24) +self.play(GrowFromCenter(brace), Write(label)) +``` + +## Common LaTeX + +```python +MathTex(r"\frac{a}{b}") # fraction +MathTex(r"\alpha, \beta, \gamma") # Greek +MathTex(r"\sum_{i=1}^{n} x_i") # summation +MathTex(r"\int_{0}^{\infty} e^{-x} dx") # integral +MathTex(r"\vec{v}") # vector +MathTex(r"\lim_{x \to \infty} f(x)") # limit +``` + +## Derivation Pattern + +```python +class DerivationScene(Scene): + def construct(self): + self.camera.background_color = BG + s1 = MathTex(r"ax^2 + bx + c = 0") + self.play(Write(s1)) + self.wait(1.5) + s2 = MathTex(r"x^2 + \frac{b}{a}x + \frac{c}{a} = 0") + s2.next_to(s1, DOWN, buff=0.8) + self.play(s1.animate.set_opacity(0.4), TransformMatchingTex(s1.copy(), s2)) +``` diff --git a/skills/creative/manim-video/references/graphs-and-data.md b/skills/creative/manim-video/references/graphs-and-data.md new file mode 100644 index 000000000..c97396c43 --- /dev/null +++ b/skills/creative/manim-video/references/graphs-and-data.md @@ -0,0 +1,91 @@ +# Graphs, Plots, and Data Visualization + +## Axes + +```python +axes = Axes( + x_range=[-3, 3, 1], y_range=[-2, 2, 1], + x_length=8, y_length=5, + axis_config={"include_numbers": True, "font_size": 24} +) +axes.set_opacity(0.15) # structural element +x_label = axes.get_x_axis_label(r"x") +``` + +## Plotting + +```python +graph = axes.plot(lambda x: x**2, color=BLUE) +graph_label = axes.get_graph_label(graph, label=r"x^2", x_val=2) +area = axes.get_area(graph, x_range=[0, 2], color=BLUE, opacity=0.3) +``` + +## Animated Plotting + +```python +self.play(Create(graph), run_time=3) # trace the graph + +# Moving dot along curve +dot = Dot(color=YELLOW).move_to(axes.c2p(0, 0)) +self.play(MoveAlongPath(dot, graph), run_time=3) + +# Dynamic parameter +tracker = ValueTracker(1) +dynamic = always_redraw(lambda: axes.plot(lambda x: tracker.get_value() * x**2, color=BLUE)) +self.add(dynamic) +self.play(tracker.animate.set_value(3), run_time=2) +``` + +## Bar Charts + +```python +chart = BarChart( + values=[4, 6, 2, 8, 5], bar_names=["A", "B", "C", "D", "E"], + y_range=[0, 10, 2], bar_colors=[RED, GREEN, BLUE, YELLOW, PURPLE] +) +self.play(Create(chart), run_time=2) +self.play(chart.animate.change_bar_values([6, 3, 7, 4, 9])) +``` + +## Number Lines + +```python +nl = NumberLine(x_range=[0, 10, 1], length=10, include_numbers=True) +pointer = Arrow(nl.n2p(3) + UP * 0.5, nl.n2p(3), color=RED, buff=0) +tracker = ValueTracker(3) +pointer.add_updater(lambda m: m.put_start_and_end_on( + nl.n2p(tracker.get_value()) + UP * 0.5, nl.n2p(tracker.get_value()))) +self.play(tracker.animate.set_value(8), run_time=2) +``` + +## Animated Counters + +```python +counter = DecimalNumber(0, font_size=72, num_decimal_places=0) +self.play(counter.animate.set_value(1000), run_time=3, rate_func=rush_from) +``` + +## Algorithm Visualization Pattern + +```python +values = [5, 2, 8, 1, 9, 3] +bars = VGroup(*[ + Rectangle(width=0.6, height=v * 0.4, color=BLUE, fill_opacity=0.7) + for v in values +]).arrange(RIGHT, buff=0.2, aligned_edge=DOWN).move_to(ORIGIN) +self.play(LaggedStart(*[GrowFromEdge(b, DOWN) for b in bars], lag_ratio=0.1)) +# Highlight, swap, etc. +``` + +## Data Story Pattern + +```python +# Before/After comparison +before = BarChart(values=[3, 5, 2], bar_colors=[RED]*3).shift(LEFT * 3) +after = BarChart(values=[8, 9, 7], bar_colors=[GREEN]*3).shift(RIGHT * 3) +self.play(Create(before)); self.wait(1) +self.play(Create(after)); self.wait(1) +arrow = Arrow(before.get_right(), after.get_left(), color=YELLOW) +label = Text("+167%", font_size=36, color=YELLOW).next_to(arrow, UP) +self.play(GrowArrow(arrow), Write(label)) +``` diff --git a/skills/creative/manim-video/references/mobjects.md b/skills/creative/manim-video/references/mobjects.md new file mode 100644 index 000000000..069eee8fb --- /dev/null +++ b/skills/creative/manim-video/references/mobjects.md @@ -0,0 +1,106 @@ +# Mobjects Reference + +Everything visible on screen is a Mobject. They have position, color, opacity, and can be animated. + +## Text + +```python +title = Text("Hello World", font_size=48, color=BLUE) +eq = MathTex(r"E = mc^2", font_size=40) + +# Multi-part (for selective coloring) +eq = MathTex(r"a^2", r"+", r"b^2", r"=", r"c^2") +eq[0].set_color(RED) +eq[4].set_color(BLUE) + +# Mixed text and math +t = Tex(r"The area is $\pi r^2$", font_size=36) + +# Styled markup +t = MarkupText('Blue text', font_size=30) +``` + +**Always use raw strings (`r""`) for any string with backslashes.** + +## Shapes + +```python +circle = Circle(radius=1, color=BLUE, fill_opacity=0.5) +square = Square(side_length=2, color=RED) +rect = Rectangle(width=4, height=2, color=GREEN) +dot = Dot(point=ORIGIN, radius=0.08, color=YELLOW) +line = Line(LEFT * 2, RIGHT * 2, color=WHITE) +arrow = Arrow(LEFT, RIGHT, color=ORANGE) +rrect = RoundedRectangle(corner_radius=0.3, width=4, height=2) +brace = Brace(rect, DOWN, color=YELLOW) +``` + +## Positioning + +```python +mob.move_to(ORIGIN) # center +mob.move_to(UP * 2 + RIGHT) # relative +label.next_to(circle, DOWN, buff=0.3) # next to another +title.to_edge(UP, buff=0.5) # screen edge (buff >= 0.5!) +mob.to_corner(UL, buff=0.5) # corner +``` + +## VGroup vs Group + +**VGroup** is for collections of shapes (VMobjects only — Circle, Square, Arrow, Line, MathTex): +```python +shapes = VGroup(circle, square, arrow) +shapes.arrange(DOWN, buff=0.5) +shapes.set_color(BLUE) +``` + +**Group** is for mixed collections (Text + shapes, or any Mobject types): +```python +# Text objects are Mobjects, not VMobjects — use Group when mixing +labeled_shape = Group(circle, Text("Label").next_to(circle, DOWN)) +labeled_shape.move_to(ORIGIN) + +# FadeOut everything on screen (may contain mixed types) +self.play(FadeOut(Group(*self.mobjects))) +``` + +**Rule: if your group contains any `Text()` objects, use `Group`, not `VGroup`.** VGroup will raise a TypeError on Manim CE v0.20+. MathTex and Tex are VMobjects and work with VGroup. + +Both support `arrange()`, `arrange_in_grid()`, `set_opacity()`, `shift()`, `scale()`, `move_to()`. + +## Styling + +```python +mob.set_color(BLUE) +mob.set_fill(RED, opacity=0.5) +mob.set_stroke(WHITE, width=2) +mob.set_opacity(0.4) +mob.set_z_index(1) # layering +``` + +## Specialized Mobjects + +```python +nl = NumberLine(x_range=[-3, 3, 1], length=8, include_numbers=True) +table = Table([["A", "B"], ["C", "D"]], row_labels=[Text("R1"), Text("R2")]) +code = Code("example.py", tab_width=4, font_size=20, language="python") +highlight = SurroundingRectangle(target, color=YELLOW, buff=0.2) +bg = BackgroundRectangle(equation, fill_opacity=0.7, buff=0.2) +``` + +## Custom Mobjects + +```python +class NetworkNode(Group): + def __init__(self, label_text, color=BLUE, **kwargs): + super().__init__(**kwargs) + self.circle = Circle(radius=0.4, color=color, fill_opacity=0.3) + self.label = Text(label_text, font_size=20).move_to(self.circle) + self.add(self.circle, self.label) +``` + +## Constants + +Directions: `UP, DOWN, LEFT, RIGHT, ORIGIN, UL, UR, DL, DR` +Colors: `RED, BLUE, GREEN, YELLOW, WHITE, GRAY, ORANGE, PINK, PURPLE, TEAL, GOLD` +Frame: `config.frame_width = 14.222, config.frame_height = 8.0` diff --git a/skills/creative/manim-video/references/rendering.md b/skills/creative/manim-video/references/rendering.md new file mode 100644 index 000000000..f4c863393 --- /dev/null +++ b/skills/creative/manim-video/references/rendering.md @@ -0,0 +1,93 @@ +# Rendering Reference + +## Prerequisites + +```bash +manim --version # Manim CE +pdflatex --version # LaTeX +ffmpeg -version # ffmpeg +``` + +## CLI Reference + +```bash +manim -ql script.py Scene1 Scene2 # draft (480p 15fps) +manim -qm script.py Scene1 # medium (720p 30fps) +manim -qh script.py Scene1 # production (1080p 60fps) +manim -ql --format=png -s script.py Scene1 # preview still (last frame) +manim -ql --format=gif script.py Scene1 # GIF output +``` + +## Quality Presets + +| Flag | Resolution | FPS | Use case | +|------|-----------|-----|----------| +| `-ql` | 854x480 | 15 | Draft iteration (layout, timing) | +| `-qm` | 1280x720 | 30 | Preview (use for text-heavy scenes) | +| `-qh` | 1920x1080 | 60 | Production | + +**Text rendering quality:** `-ql` (480p15) produces noticeably poor text kerning and readability. For scenes with significant text, preview stills at `-qm` to catch issues invisible at 480p. Use `-ql` only for testing layout and animation timing. + +## Output Structure + +``` +media/videos/script/480p15/Scene1_Intro.mp4 +media/images/script/Scene1_Intro.png (from -s flag) +``` + +## Stitching with ffmpeg + +```bash +cat > concat.txt << 'EOF' +file 'media/videos/script/480p15/Scene1_Intro.mp4' +file 'media/videos/script/480p15/Scene2_Core.mp4' +EOF +ffmpeg -y -f concat -safe 0 -i concat.txt -c copy final.mp4 +``` + +## Add Voiceover + +```bash +# Mux narration +ffmpeg -y -i final.mp4 -i narration.mp3 -c:v copy -c:a aac -b:a 192k -shortest final_narrated.mp4 + +# Concat per-scene audio first +cat > audio_concat.txt << 'EOF' +file 'audio/scene1.mp3' +file 'audio/scene2.mp3' +EOF +ffmpeg -y -f concat -safe 0 -i audio_concat.txt -c copy full_narration.mp3 +``` + +## Add Background Music + +```bash +ffmpeg -y -i final.mp4 -i music.mp3 \ + -filter_complex "[1:a]volume=0.15[bg];[0:a][bg]amix=inputs=2:duration=shortest" \ + -c:v copy final_with_music.mp4 +``` + +## GIF Export + +```bash +ffmpeg -y -i scene.mp4 \ + -vf "fps=15,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \ + output.gif +``` + +## Aspect Ratios + +```bash +manim -ql --resolution 1080,1920 script.py Scene # 9:16 vertical +manim -ql --resolution 1080,1080 script.py Scene # 1:1 square +``` + +## Render Workflow + +1. Draft render all scenes at `-ql` +2. Preview stills at key moments (`-s`) +3. Fix and re-render only broken scenes +4. Stitch with ffmpeg +5. Review stitched output +6. Production render at `-qh` +7. Re-stitch + add audio diff --git a/skills/creative/manim-video/references/scene-planning.md b/skills/creative/manim-video/references/scene-planning.md new file mode 100644 index 000000000..f42b78f38 --- /dev/null +++ b/skills/creative/manim-video/references/scene-planning.md @@ -0,0 +1,118 @@ +# Scene Planning Reference + +## Narrative Arc Structures + +### Discovery Arc (most common) +1. Hook -- pose a question or surprising result +2. Intuition -- build visual understanding +3. Formalize -- introduce the equation/algorithm +4. Reveal -- the "aha moment" +5. Extend -- implications or generalizations + +### Problem-Solution Arc +1. Problem -- what's broken +2. Failed attempt -- obvious approach fails +3. Key insight -- the idea that works +4. Solution -- implement it +5. Result -- show improvement + +### Comparison Arc +1. Setup -- introduce two approaches +2. Approach A -- how it works +3. Approach B -- how it works +4. Contrast -- differences +5. Verdict -- which is better + +### Build-Up Arc (architecture/systems) +1. Component A -- first piece +2. Component B -- second piece +3. Connection -- how they interact +4. Scale -- add more pieces +5. Full picture -- zoom out + +## Scene Transitions + +### Clean Break (default) +```python +self.play(FadeOut(Group(*self.mobjects)), run_time=0.5) +self.wait(0.3) +``` + +### Carry-Forward +Keep one element, fade the rest. Next scene starts with it still on screen. + +### Transform Bridge +End scene with a shape, start next scene by transforming it. + +## Cross-Scene Consistency + +```python +# Shared constants at file top +BG = "#1C1C1C" +PRIMARY = "#58C4DD" +SECONDARY = "#83C167" +ACCENT = "#FFFF00" +TITLE_SIZE = 48 +BODY_SIZE = 30 +LABEL_SIZE = 24 +FAST = 0.8; NORMAL = 1.5; SLOW = 2.5 +``` + +## Scene Checklist + +- [ ] Background color set +- [ ] Subcaptions on every animation +- [ ] `self.wait()` after every reveal +- [ ] Text buff >= 0.5 for edge positioning +- [ ] No text overlap +- [ ] Color constants used (not hardcoded) +- [ ] Opacity layering applied +- [ ] Clean exit at scene end +- [ ] No more than 5-6 elements visible at once + +## Duration Estimation + +| Content | Duration | +|---------|----------| +| Title card | 3-5s | +| Concept introduction | 10-20s | +| Equation reveal | 15-25s | +| Algorithm step | 5-10s | +| Data comparison | 10-15s | +| "Aha moment" | 15-30s | +| Conclusion | 5-10s | + +## Planning Template + +```markdown +# [Video Title] + +## Overview +- **Topic**: [Core concept] +- **Hook**: [Opening question] +- **Aha moment**: [Key insight] +- **Target audience**: [Prerequisites] +- **Length**: [seconds/minutes] +- **Resolution**: 480p (draft) / 1080p (final) + +## Color Palette +- Background: #1C1C1C +- Primary: #58C4DD -- [purpose] +- Secondary: #83C167 -- [purpose] +- Accent: #FFFF00 -- [purpose] + +## Arc: [Discovery / Problem-Solution / Comparison / Build-Up] + +## Scene 1: [Name] (~Ns) +**Purpose**: [one sentence] +**Layout**: [FULL_CENTER / LEFT_RIGHT / GRID / PROGRESSIVE] + +### Visual elements +- [Mobject: type, position, color] + +### Animation sequence +1. [Animation] -- [what it reveals] (~Ns) + +### Subtitle +"[text]" +``` diff --git a/skills/creative/manim-video/references/troubleshooting.md b/skills/creative/manim-video/references/troubleshooting.md new file mode 100644 index 000000000..98c63fd2b --- /dev/null +++ b/skills/creative/manim-video/references/troubleshooting.md @@ -0,0 +1,135 @@ +# Troubleshooting + +## LaTeX Errors + +**Missing raw string** (the #1 error): +```python +# WRONG: MathTex("\\frac{1}{2}") -- \\f is form-feed +# RIGHT: MathTex(r"\frac{1}{2}") +``` + +**Unbalanced braces**: `MathTex(r"\frac{1}{2")` -- missing closing brace. + +**LaTeX not installed**: `which pdflatex` -- install texlive-full or mactex. + +**Missing package**: Add to preamble: +```python +tex_template = TexTemplate() +tex_template.add_to_preamble(r"\usepackage{mathrsfs}") +MathTex(r"\mathscr{L}", tex_template=tex_template) +``` + +## VGroup TypeError + +**Error:** `TypeError: Only values of type VMobject can be added as submobjects of VGroup` + +**Cause:** `Text()` objects are `Mobject`, not `VMobject`. Mixing `Text` with shapes in a `VGroup` fails on Manim CE v0.20+. + +```python +# WRONG: Text is not a VMobject +group = VGroup(circle, Text("Label")) + +# RIGHT: use Group for mixed types +group = Group(circle, Text("Label")) + +# RIGHT: VGroup is fine for shapes-only +shapes = VGroup(circle, square, arrow) + +# RIGHT: MathTex IS a VMobject — VGroup works +equations = VGroup(MathTex(r"a"), MathTex(r"b")) +``` + +**Rule:** If the group contains any `Text()`, use `Group`. If it's all shapes or all `MathTex`, `VGroup` is fine. + +**FadeOut everything:** Always use `Group(*self.mobjects)`, not `VGroup(*self.mobjects)`: +```python +self.play(FadeOut(Group(*self.mobjects))) # safe for mixed types +``` + +## Group save_state() / restore() Not Supported + +**Error:** `NotImplementedError: Please override in a child class.` + +**Cause:** `Group.save_state()` and `Group.restore()` are not implemented in Manim CE v0.20+. Only `VGroup` and individual `Mobject` subclasses support save/restore. + +```python +# WRONG: Group doesn't support save_state +group = Group(circle, Text("label")) +group.save_state() # NotImplementedError! + +# RIGHT: use FadeIn with shift/scale instead of save_state/restore +self.play(FadeIn(group, shift=UP * 0.3, scale=0.8)) + +# RIGHT: or save/restore on individual VMobjects +circle.save_state() +self.play(circle.animate.shift(RIGHT)) +self.play(Restore(circle)) +``` + +## letter_spacing Is Not a Valid Parameter + +**Error:** `TypeError: Mobject.__init__() got an unexpected keyword argument 'letter_spacing'` + +**Cause:** `Text()` does not accept `letter_spacing`. Manim uses Pango for text rendering and does not expose kerning controls on `Text()`. + +```python +# WRONG +Text("HERMES", letter_spacing=6) + +# RIGHT: use MarkupText with Pango attributes for spacing control +MarkupText('HERMES', font_size=18) +# Note: Pango letter_spacing is in 1/1024 of a point +``` + +## Animation Errors + +**Invisible animation** -- mobject never added: +```python +# WRONG: circle = Circle(); self.play(circle.animate.set_color(RED)) +# RIGHT: self.play(Create(circle)); self.play(circle.animate.set_color(RED)) +``` + +**Transform confusion** -- after Transform(A, B), A is on screen, B is not. Use ReplacementTransform if you want B. + +**Duplicate animation** -- same mobject twice in one play(): +```python +# WRONG: self.play(c.animate.shift(RIGHT), c.animate.set_color(RED)) +# RIGHT: self.play(c.animate.shift(RIGHT).set_color(RED)) +``` + +**Updater fights animation**: +```python +mob.suspend_updating() +self.play(mob.animate.shift(RIGHT)) +mob.resume_updating() +``` + +## Rendering Issues + +**Blurry output**: Using -ql (480p). Switch to -qm/-qh for final. + +**Slow render**: Use -ql during development. Reduce Surface resolution. Shorter self.wait(). + +**Stale output**: `manim -ql --disable_caching script.py Scene` + +**ffmpeg concat fails**: All clips must match resolution/FPS/codec. + +## Common Mistakes + +**Text clips at edge**: `buff >= 0.5` for `.to_edge()` + +**Overlapping text**: Use `ReplacementTransform(old, new)`, not `Write(new)` on top. + +**Too crowded**: Max 5-6 elements visible. Split into scenes or use opacity layering. + +**No breathing room**: `self.wait(1.5)` minimum after reveals, `self.wait(2.0)` for key moments. + +**Missing background color**: Set `self.camera.background_color = BG` in every scene. + +## Debugging Strategy + +1. Render a still: `manim -ql -s script.py Scene` -- instant layout check +2. Isolate the broken scene -- render only that one +3. Replace `self.play()` with `self.add()` to see final state instantly +4. Print positions: `print(mob.get_center())` +5. Clear cache: delete `media/` directory diff --git a/skills/creative/manim-video/references/visual-design.md b/skills/creative/manim-video/references/visual-design.md new file mode 100644 index 000000000..e8dc09fe3 --- /dev/null +++ b/skills/creative/manim-video/references/visual-design.md @@ -0,0 +1,119 @@ +# Visual Design Principles + +## 12 Core Principles + +1. **Geometry Before Algebra** — Show the shape first, the equation second. +2. **Opacity Layering** — PRIMARY=1.0, CONTEXT=0.4, GRID=0.15. Direct attention through brightness. +3. **One New Idea Per Scene** — Each scene introduces exactly one concept. +4. **Spatial Consistency** — Same concept occupies the same screen region throughout. +5. **Color = Meaning** — Assign colors to concepts, not mobjects. If velocity is blue, it stays blue. +6. **Progressive Disclosure** — Show simplest version first, add complexity incrementally. +7. **Transform, Don't Replace** — Use Transform/ReplacementTransform to show connections. +8. **Breathing Room** — `self.wait(1.5)` minimum after showing something new. +9. **Visual Weight Balance** — Don't cluster everything on one side. +10. **Consistent Motion Vocabulary** — Pick a small set of animation types and reuse them. +11. **Dark Background, Light Content** — #1C1C1C to #2D2B55 backgrounds maximize contrast. +12. **Intentional Empty Space** — Leave at least 15% of the frame empty. + +## Layout Templates + +### FULL_CENTER +One main element centered, title above, note below. +Best for: single equations, single diagrams, title cards. + +### LEFT_RIGHT +Two elements side by side at x=-3.5 and x=3.5. +Best for: equation + visual, before/after, comparison. + +### TOP_BOTTOM +Main element at y=1.5, supporting content at y=-1.5. +Best for: concept + examples, theorem + cases. + +### GRID +Multiple elements via `arrange_in_grid()`. +Best for: comparison matrices, multi-step processes. + +### PROGRESSIVE +Elements appear one at a time, arranged DOWN with aligned_edge=LEFT. +Best for: algorithms, proofs, step-by-step processes. + +### ANNOTATED_DIAGRAM +Central diagram with floating labels connected by arrows. +Best for: architecture diagrams, annotated figures. + +## Color Palettes + +### Classic 3B1B +```python +BG="#1C1C1C"; PRIMARY=BLUE; SECONDARY=GREEN; ACCENT=YELLOW; HIGHLIGHT=RED +``` + +### Warm Academic +```python +BG="#2D2B55"; PRIMARY="#FF6B6B"; SECONDARY="#FFD93D"; ACCENT="#6BCB77" +``` + +### Neon Tech +```python +BG="#0A0A0A"; PRIMARY="#00F5FF"; SECONDARY="#FF00FF"; ACCENT="#39FF14" +``` + +## Font Selection + +Manim's default `Text()` uses the system's default sans-serif font, which often renders with poor kerning. Always specify a font explicitly. + +### Recommended Fonts + +| Use case | Font | Fallback | +|----------|------|----------| +| Body text, titles | `"Inter"`, `"SF Pro Display"` | `"Helvetica Neue"`, `"Arial"` | +| Code, terminal | `"JetBrains Mono"`, `"SF Mono"` | `"Menlo"`, `"Courier New"` | +| Math labels | Use `MathTex` (renders via LaTeX, not system fonts) | — | + +```python +# Clean body text +title = Text("Gradient Descent", font_size=48, font="Inter", weight=BOLD) + +# Monospaced code +code_label = Text("loss.backward()", font_size=24, font="JetBrains Mono") + +# Math — always use MathTex, not Text +equation = MathTex(r"\nabla L = \frac{\partial L}{\partial w}") +``` + +### Font Availability + +Not all fonts are installed on all systems. Manim falls back silently to a default if the font is missing. Use widely available fonts: +- **macOS**: SF Pro Display, SF Mono, Menlo, Helvetica Neue +- **Linux**: DejaVu Sans, Liberation Sans, Ubuntu, Noto Sans +- **Cross-platform**: Inter (install via Google Fonts), JetBrains Mono (install from jetbrains.com) + +For maximum portability, use `"Helvetica Neue"` (body) and `"Menlo"` (code) — both available on macOS and have Linux equivalents. + +### Fine-Grained Text Control + +`Text()` does not support `letter_spacing` or kerning parameters. For fine control, use `MarkupText` with Pango attributes: + +```python +# Letter spacing (Pango units: 1/1024 of a point) +MarkupText('HERMES', font_size=18, font="Menlo") + +# Bold specific words +MarkupText('This is important', font_size=24) + +# Color specific words +MarkupText('Red warning', font_size=24) +``` + +### Text Rendering Quality + +Manim's text rendering quality depends heavily on output resolution. At `-ql` (480p), text kerning looks noticeably poor. Always preview text-heavy scenes at `-qm` (720p) or higher. See `references/rendering.md` for quality preset guidance. + +## Visual Hierarchy Checklist + +For every frame: +1. What is the ONE thing to look at? (brightest/largest) +2. What is context? (dimmed to 0.3-0.4) +3. What is structural? (dimmed to 0.15) +4. Enough empty space? (>15%) +5. All text readable at phone size? diff --git a/skills/creative/manim-video/scripts/setup.sh b/skills/creative/manim-video/scripts/setup.sh new file mode 100755 index 000000000..0e4676f24 --- /dev/null +++ b/skills/creative/manim-video/scripts/setup.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail +G="\033[0;32m"; R="\033[0;31m"; N="\033[0m" +ok() { echo -e " ${G}+${N} $1"; } +fail() { echo -e " ${R}x${N} $1"; } +echo ""; echo "Manim Video Skill — Setup Check"; echo "" +errors=0 +command -v python3 &>/dev/null && ok "Python $(python3 --version 2>&1 | awk '{print $2}')" || { fail "Python 3 not found"; errors=$((errors+1)); } +python3 -c "import manim" 2>/dev/null && ok "Manim $(manim --version 2>&1 | head -1)" || { fail "Manim not installed: pip install manim"; errors=$((errors+1)); } +command -v pdflatex &>/dev/null && ok "LaTeX (pdflatex)" || { fail "LaTeX not found (macOS: brew install --cask mactex-no-gui)"; errors=$((errors+1)); } +command -v ffmpeg &>/dev/null && ok "ffmpeg" || { fail "ffmpeg not found"; errors=$((errors+1)); } +echo "" +[ $errors -eq 0 ] && echo -e "${G}All prerequisites satisfied.${N}" || echo -e "${R}$errors prerequisite(s) missing.${N}" +echo ""