Skip to content

Commit b61247e

Browse files
committed
feat: auto-install animations with bundled ffmpeg
- Move manim, Pillow from optional [animations] to main dependencies - Add imageio-ffmpeg for cross-platform ffmpeg bundling - Add --no-animations CLI flag to skip animation processing (for CI) - Update README to reflect animations are included by default - Update sample slides with improved manim/animate DSL explanations - Regenerate gallery screenshots for animation slides
1 parent b2d77b7 commit b61247e

File tree

11 files changed

+78
-50
lines changed

11 files changed

+78
-50
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,13 @@ jobs:
7878
run: pip install -e .
7979

8080
- name: Compile sample presentation (HTML)
81-
run: cdl-slides compile examples/sample_presentation.md --format html
81+
run: cdl-slides compile examples/sample_presentation.md --format html --no-animations
8282

8383
- name: Verify HTML output exists
8484
run: test -f examples/sample_presentation.html
8585

8686
- name: Compile sample presentation (PDF)
87-
run: cdl-slides compile examples/sample_presentation.md --format pdf
87+
run: cdl-slides compile examples/sample_presentation.md --format pdf --no-animations
8888

8989
- name: Verify PDF output exists
9090
run: test -f examples/sample_presentation.pdf

README.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Compile Markdown files into beautiful CDL-themed [Marp](https://marp.app/) prese
3232
- **Bundled theme**: Complete CDL/Dartmouth-branded theme with custom fonts, colors, and layouts
3333
- **Smart preprocessing**: Auto-splits long code blocks and tables across slides
3434
- **Flow diagrams**: Simple ```` ```flow ```` syntax for pipeline diagrams
35-
- **Manim animations**: Embed animated equations and visualizations with ```` ```manim ```` blocks (optional)
35+
- **Manim animations**: Embed animated equations and visualizations with ```` ```manim ```` blocks (included by default)
3636
- **Auto-scaling**: Automatically adjusts font size for dense slides
3737
- **Syntax highlighting**: Code blocks with line numbers via Pygments
3838
- **Math support**: KaTeX for inline and display equations
@@ -252,17 +252,11 @@ Use the ```` ```flow ```` syntax for simple pipeline diagrams:
252252

253253
Available colors: `green`, `blue`, `navy`, `teal`, `orange`, `red`, `violet`, `yellow`, `gray`.
254254

255-
### Manim Animations (Optional)
255+
### Manim Animations
256256

257257
Embed animated math visualizations using [Manim Community](https://www.manim.community/). Animations are rendered to transparent GIFs and embedded in slides.
258258

259-
**Installation:**
260-
261-
```bash
262-
pip install cdl-slides[animations]
263-
```
264-
265-
This installs `manim` and `Pillow`. You also need `ffmpeg` installed on your system.
259+
Animation support is included by default when you install `cdl-slides`. FFmpeg is bundled automatically via `imageio-ffmpeg` — no system installation required.
266260

267261
**Usage:**
268262

@@ -344,7 +338,7 @@ fade-in eq1
344338

345339
**Available colors:** blue, red, green, yellow, orange, white, black
346340

347-
Note: The animate DSL requires the same `cdl-slides[animations]` install as manim blocks.
341+
Note: The animate DSL is included by default with `cdl-slides` — no extra installation needed.
348342

349343
### Scale Directives
350344

28.9 KB
Loading
18.3 KB
Loading

examples/sample_presentation.html

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,22 +1102,24 @@ <h1 id="flow-diagrams">Flow diagrams</h1>
11021102
</div>
11031103
</section>
11041104
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="16" data-class="scale-85" data-theme="cdl-theme" lang="en-US" class="scale-85" style="--class:scale-85;--theme:cdl-theme;" data-transition-back="{&quot;name&quot;:&quot;fade&quot;,&quot;duration&quot;:&quot;0.25s&quot;,&quot;builtinFallback&quot;:true}" data-transition="{&quot;name&quot;:&quot;fade&quot;,&quot;duration&quot;:&quot;0.25s&quot;,&quot;builtinFallback&quot;:true}">
1105-
<h1 id="manim-animations-optional">Manim animations (optional)</h1>
1105+
<h1 id="manim-animations">Manim animations</h1>
11061106
<div style="display: flex; gap: 1.5em;">
11071107
<div style="flex: 1;">
11081108
<p><img src="animations/WaveAnimation_51150ee0.gif" alt="" style="height:380px;" /></p>
11091109
</div>
11101110
<div style="flex: 1;">
1111-
<div class="note-box" data-title="Setup">
1112-
<p><code>pip install cdl-slides[animations]</code></p>
1111+
<div class="note-box" data-title="How it works">
1112+
<p>CDL-slides automatically transpiles <code>```manim</code> blocks to Python, renders to MP4, and converts to transparent GIF. No manual steps needed!</p>
11131113
</div>
1114-
<div class="example-box" data-title="Markdown syntax">
1115-
<pre style="margin: 0; font-size: 0.7em; background: rgba(0,105,62,0.08); padding: 0.3em; border-radius: 4px; white-space: pre-wrap;">&#96;&#96;&#96;manim
1114+
<div class="example-box" data-title="Markdown source">
1115+
<pre style="margin: 0; font-size: 0.65em; background: rgba(0,105,62,0.08); padding: 0.3em; border-radius: 4px; white-space: pre-wrap;">&#96;&#96;&#96;manim
11161116
# scene: WaveAnimation
11171117
# height: 380
11181118
class WaveAnimation(Scene):
11191119
def construct(self):
1120-
...
1120+
axes = Axes(...)
1121+
wave = axes.plot(np.sin)
1122+
self.play(Create(axes))
11211123
&#96;&#96;&#96;</pre>
11221124
</div>
11231125
</div>
@@ -1130,15 +1132,15 @@ <h1 id="animate-dsl">Animate DSL</h1>
11301132
<p><img src="animations/AnimateScene_c963e8d5.gif" alt="" style="height:380px;" /></p>
11311133
</div>
11321134
<div style="flex: 1;">
1133-
<div class="tip-box" data-title="Simplified">
1134-
<p>No Python needed! Transpiles to manim.</p>
1135+
<div class="note-box" data-title="DSL features">
1136+
<p>No Python needed! Also supports: <code>create circle/square/arrow</code>, <code>transform A -&gt; B</code>, positions like <code>above/below/left-of</code>. See <a href="https://github.com/ContextLab/cdl-slides#animate-dsl-simplified-animation-syntax">README</a>.</p>
11351137
</div>
1136-
<div class="example-box" data-title="Markdown syntax">
1137-
<pre style="margin: 0; font-size: 0.7em; background: rgba(0,105,62,0.08); padding: 0.3em; border-radius: 4px; white-space: pre-wrap;">&#96;&#96;&#96;animate
1138+
<div class="example-box" data-title="Markdown source">
1139+
<pre style="margin: 0; font-size: 0.65em; background: rgba(0,105,62,0.08); padding: 0.3em; border-radius: 4px; white-space: pre-wrap;">&#96;&#96;&#96;animate
11381140
height: 380
1139-
write equation "E = mc^2" as eq
1141+
write equation "..." as integral
11401142
wait 0.5
1141-
fade-in eq
1143+
fade-in integral
11421144
&#96;&#96;&#96;</pre>
11431145
</div>
11441146
</div>

examples/sample_presentation.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ KaTeX is used for math rendering. Use `$...$` for inline and `$$...$$` for displ
252252

253253
---
254254

255-
# Manim animations (optional)
255+
# Manim animations
256256
<!-- _class: scale-85 -->
257257

258258
<div style="display: flex; gap: 1.5em;">
@@ -276,20 +276,22 @@ class WaveAnimation(Scene):
276276
</div>
277277
<div style="flex: 1;">
278278

279-
<div class="note-box" data-title="Setup">
279+
<div class="note-box" data-title="How it works">
280280

281-
`pip install cdl-slides[animations]`
281+
CDL-slides automatically transpiles ` ```manim ` blocks to Python, renders to MP4, and converts to transparent GIF. No manual steps needed!
282282

283283
</div>
284284

285-
<div class="example-box" data-title="Markdown syntax">
285+
<div class="example-box" data-title="Markdown source">
286286

287-
<pre style="margin: 0; font-size: 0.7em; background: rgba(0,105,62,0.08); padding: 0.3em; border-radius: 4px; white-space: pre-wrap;">&#96;&#96;&#96;manim
287+
<pre style="margin: 0; font-size: 0.65em; background: rgba(0,105,62,0.08); padding: 0.3em; border-radius: 4px; white-space: pre-wrap;">&#96;&#96;&#96;manim
288288
# scene: WaveAnimation
289289
# height: 380
290290
class WaveAnimation(Scene):
291291
def construct(self):
292-
...
292+
axes = Axes(...)
293+
wave = axes.plot(np.sin)
294+
self.play(Create(axes))
293295
&#96;&#96;&#96;</pre>
294296

295297
</div>
@@ -316,19 +318,19 @@ fade-in integral
316318
</div>
317319
<div style="flex: 1;">
318320

319-
<div class="tip-box" data-title="Simplified">
321+
<div class="note-box" data-title="DSL features">
320322

321-
No Python needed! Transpiles to manim.
323+
No Python needed! Also supports: `create circle/square/arrow`, `transform A -> B`, positions like `above/below/left-of`. See [README](https://github.com/ContextLab/cdl-slides#animate-dsl-simplified-animation-syntax).
322324

323325
</div>
324326

325-
<div class="example-box" data-title="Markdown syntax">
327+
<div class="example-box" data-title="Markdown source">
326328

327-
<pre style="margin: 0; font-size: 0.7em; background: rgba(0,105,62,0.08); padding: 0.3em; border-radius: 4px; white-space: pre-wrap;">&#96;&#96;&#96;animate
329+
<pre style="margin: 0; font-size: 0.65em; background: rgba(0,105,62,0.08); padding: 0.3em; border-radius: 4px; white-space: pre-wrap;">&#96;&#96;&#96;animate
328330
height: 380
329-
write equation "E = mc^2" as eq
331+
write equation "..." as integral
330332
wait 0.5
331-
fade-in eq
333+
fade-in integral
332334
&#96;&#96;&#96;</pre>
333335

334336
</div>

pyproject.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,19 @@ classifiers = [
3030
dependencies = [
3131
"click>=8.0",
3232
"pygments>=2.15",
33+
# Animation support (included by default)
34+
"manim>=0.18.0",
35+
"Pillow>=9.0",
36+
"imageio-ffmpeg>=0.5.1", # Bundled ffmpeg binary
3337
]
3438

3539
[project.optional-dependencies]
3640
dev = [
3741
"pytest>=7.0",
3842
"ruff>=0.4",
3943
]
40-
animations = [
41-
"manim>=0.18.0",
42-
"Pillow>=9.0",
43-
]
44+
# Kept for backwards compatibility - animations now included by default
45+
animations = []
4446

4547
[project.urls]
4648
Homepage = "https://www.context-lab.com"

src/cdl_slides/cli.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ def main() -> None:
9393
default=None,
9494
help="Custom theme directory.",
9595
)
96+
@click.option(
97+
"--no-animations",
98+
is_flag=True,
99+
default=False,
100+
help="Skip processing of manim and animate blocks (useful for CI).",
101+
)
96102
def compile(
97103
input_file: Path,
98104
output: Optional[Path],
@@ -102,6 +108,7 @@ def compile(
102108
no_split: bool,
103109
keep_temp: bool,
104110
theme_dir: Optional[Path],
111+
no_animations: bool,
105112
) -> None:
106113
"""Compile a Markdown file into a CDL-themed Marp presentation."""
107114
from cdl_slides.compiler import CompilationError, compile_presentation
@@ -118,6 +125,7 @@ def compile(
118125
no_split=no_split,
119126
keep_temp=keep_temp,
120127
theme_dir=theme_dir,
128+
skip_animations=no_animations,
121129
)
122130
except FileNotFoundError as exc:
123131
click.echo(click.style(f"Error: {exc}", fg="red"), err=True)

src/cdl_slides/compiler.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def compile_presentation(
9191
no_split: bool = False,
9292
keep_temp: bool = False,
9393
theme_dir: Optional[Path] = None,
94+
skip_animations: bool = False,
9495
) -> dict:
9596
"""Compile a Markdown file into a CDL-themed Marp presentation.
9697
@@ -156,6 +157,7 @@ def compile_presentation(
156157
max_lines=max_lines,
157158
max_table_rows=max_table_rows,
158159
no_split=no_split,
160+
skip_animations=skip_animations,
159161
)
160162

161163
results: Dict[str, object] = {

src/cdl_slides/manim_renderer.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,19 @@
4545
except ImportError:
4646
pass
4747

48-
# Check for FFmpeg availability
49-
FFMPEG_AVAILABLE = shutil.which("ffmpeg") is not None
48+
# Check for FFmpeg availability via imageio-ffmpeg (bundled) or system PATH
49+
FFMPEG_PATH: Optional[str] = None
50+
FFMPEG_AVAILABLE = False
51+
try:
52+
import imageio_ffmpeg
53+
54+
FFMPEG_PATH = imageio_ffmpeg.get_ffmpeg_exe()
55+
FFMPEG_AVAILABLE = True
56+
except (ImportError, RuntimeError):
57+
_system_ffmpeg = shutil.which("ffmpeg")
58+
if _system_ffmpeg:
59+
FFMPEG_PATH = _system_ffmpeg
60+
FFMPEG_AVAILABLE = True
5061

5162

5263
def check_dependencies() -> Tuple[bool, list]:
@@ -59,7 +70,7 @@ def check_dependencies() -> Tuple[bool, list]:
5970
if not MANIM_AVAILABLE:
6071
missing.append("manim (pip install manim)")
6172
if not FFMPEG_AVAILABLE:
62-
missing.append("ffmpeg (brew install ffmpeg / apt install ffmpeg)")
73+
missing.append("ffmpeg (pip install imageio-ffmpeg)")
6374
if not PIL_AVAILABLE:
6475
missing.append("Pillow (pip install Pillow)")
6576
return len(missing) == 0, missing
@@ -293,10 +304,11 @@ def convert_mp4_to_gif(
293304
palette_path = gif_path.with_suffix(".palette.png")
294305

295306
try:
307+
ffmpeg_cmd = FFMPEG_PATH or "ffmpeg"
296308
# Step 1: Generate palette with transparency support
297309
vf_palette = f"fps={fps},scale={scale_width}:-1:flags=lanczos,palettegen=reserve_transparent=1:stats_mode=diff"
298310
subprocess.run(
299-
["ffmpeg", "-y", "-i", str(mp4_path), "-vf", vf_palette, str(palette_path)],
311+
[ffmpeg_cmd, "-y", "-i", str(mp4_path), "-vf", vf_palette, str(palette_path)],
300312
capture_output=True,
301313
check=True,
302314
)
@@ -307,7 +319,7 @@ def convert_mp4_to_gif(
307319
)
308320
subprocess.run(
309321
[
310-
"ffmpeg",
322+
ffmpeg_cmd,
311323
"-y",
312324
"-i",
313325
str(mp4_path),

0 commit comments

Comments
 (0)