slidesonnet 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slidesonnet-0.1.0/LICENSE +21 -0
- slidesonnet-0.1.0/PKG-INFO +383 -0
- slidesonnet-0.1.0/README.md +347 -0
- slidesonnet-0.1.0/pyproject.toml +77 -0
- slidesonnet-0.1.0/setup.cfg +4 -0
- slidesonnet-0.1.0/src/slidesonnet/__init__.py +3 -0
- slidesonnet-0.1.0/src/slidesonnet/actions.py +202 -0
- slidesonnet-0.1.0/src/slidesonnet/clean.py +289 -0
- slidesonnet-0.1.0/src/slidesonnet/cli.py +556 -0
- slidesonnet-0.1.0/src/slidesonnet/config.py +132 -0
- slidesonnet-0.1.0/src/slidesonnet/doctor.py +221 -0
- slidesonnet-0.1.0/src/slidesonnet/exceptions.py +25 -0
- slidesonnet-0.1.0/src/slidesonnet/hashing.py +99 -0
- slidesonnet-0.1.0/src/slidesonnet/init.py +69 -0
- slidesonnet-0.1.0/src/slidesonnet/models.py +222 -0
- slidesonnet-0.1.0/src/slidesonnet/parsers/__init__.py +0 -0
- slidesonnet-0.1.0/src/slidesonnet/parsers/base.py +24 -0
- slidesonnet-0.1.0/src/slidesonnet/parsers/beamer.py +361 -0
- slidesonnet-0.1.0/src/slidesonnet/parsers/expansion.py +169 -0
- slidesonnet-0.1.0/src/slidesonnet/parsers/marp.py +443 -0
- slidesonnet-0.1.0/src/slidesonnet/pipeline.py +849 -0
- slidesonnet-0.1.0/src/slidesonnet/playlist.py +63 -0
- slidesonnet-0.1.0/src/slidesonnet/preview.py +119 -0
- slidesonnet-0.1.0/src/slidesonnet/subtitles.py +344 -0
- slidesonnet-0.1.0/src/slidesonnet/tasks.py +423 -0
- slidesonnet-0.1.0/src/slidesonnet/templates/__init__.py +0 -0
- slidesonnet-0.1.0/src/slidesonnet/templates/env.txt +2 -0
- slidesonnet-0.1.0/src/slidesonnet/templates/example_playlist.yaml +27 -0
- slidesonnet-0.1.0/src/slidesonnet/templates/example_playlist_tex.yaml +27 -0
- slidesonnet-0.1.0/src/slidesonnet/templates/example_pronunciation.md +12 -0
- slidesonnet-0.1.0/src/slidesonnet/templates/example_slides_defs.md +27 -0
- slidesonnet-0.1.0/src/slidesonnet/templates/example_slides_defs.tex +31 -0
- slidesonnet-0.1.0/src/slidesonnet/templates/example_slides_intro.md +22 -0
- slidesonnet-0.1.0/src/slidesonnet/templates/example_slides_intro.tex +24 -0
- slidesonnet-0.1.0/src/slidesonnet/templates/gitignore.txt +8 -0
- slidesonnet-0.1.0/src/slidesonnet/tts/__init__.py +20 -0
- slidesonnet-0.1.0/src/slidesonnet/tts/base.py +37 -0
- slidesonnet-0.1.0/src/slidesonnet/tts/elevenlabs.py +114 -0
- slidesonnet-0.1.0/src/slidesonnet/tts/piper.py +103 -0
- slidesonnet-0.1.0/src/slidesonnet/tts/pronunciation.py +81 -0
- slidesonnet-0.1.0/src/slidesonnet/video/__init__.py +0 -0
- slidesonnet-0.1.0/src/slidesonnet/video/composer.py +444 -0
- slidesonnet-0.1.0/src/slidesonnet.egg-info/PKG-INFO +383 -0
- slidesonnet-0.1.0/src/slidesonnet.egg-info/SOURCES.txt +72 -0
- slidesonnet-0.1.0/src/slidesonnet.egg-info/dependency_links.txt +1 -0
- slidesonnet-0.1.0/src/slidesonnet.egg-info/entry_points.txt +2 -0
- slidesonnet-0.1.0/src/slidesonnet.egg-info/requires.txt +19 -0
- slidesonnet-0.1.0/src/slidesonnet.egg-info/top_level.txt +1 -0
- slidesonnet-0.1.0/tests/test_actions.py +213 -0
- slidesonnet-0.1.0/tests/test_beamer_parser.py +985 -0
- slidesonnet-0.1.0/tests/test_clean.py +331 -0
- slidesonnet-0.1.0/tests/test_cli.py +792 -0
- slidesonnet-0.1.0/tests/test_composer.py +865 -0
- slidesonnet-0.1.0/tests/test_config.py +276 -0
- slidesonnet-0.1.0/tests/test_doctor.py +450 -0
- slidesonnet-0.1.0/tests/test_dry_run.py +400 -0
- slidesonnet-0.1.0/tests/test_elevenlabs.py +136 -0
- slidesonnet-0.1.0/tests/test_elevenlabs_extended.py +107 -0
- slidesonnet-0.1.0/tests/test_expansion.py +144 -0
- slidesonnet-0.1.0/tests/test_hashing.py +161 -0
- slidesonnet-0.1.0/tests/test_init.py +147 -0
- slidesonnet-0.1.0/tests/test_marp_parser.py +1071 -0
- slidesonnet-0.1.0/tests/test_models.py +68 -0
- slidesonnet-0.1.0/tests/test_no_secrets.py +121 -0
- slidesonnet-0.1.0/tests/test_pipeline.py +513 -0
- slidesonnet-0.1.0/tests/test_piper.py +176 -0
- slidesonnet-0.1.0/tests/test_playlist.py +119 -0
- slidesonnet-0.1.0/tests/test_preflight.py +181 -0
- slidesonnet-0.1.0/tests/test_preview.py +237 -0
- slidesonnet-0.1.0/tests/test_pronunciation.py +165 -0
- slidesonnet-0.1.0/tests/test_showcase_build.py +108 -0
- slidesonnet-0.1.0/tests/test_strip_annotations.py +142 -0
- slidesonnet-0.1.0/tests/test_subtitles.py +392 -0
- slidesonnet-0.1.0/tests/test_tasks.py +1338 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Aviv Zohar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: slidesonnet
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Compile text-based presentations into narrated videos
|
|
5
|
+
Author: Aviv Zohar
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/avivz/slideSonnet
|
|
8
|
+
Project-URL: Source, https://github.com/avivz/slideSonnet
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Education
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Multimedia :: Video
|
|
16
|
+
Requires-Python: >=3.13
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: click>=8.0
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Requires-Dist: doit>=0.36
|
|
22
|
+
Requires-Dist: python-dotenv>=1.0
|
|
23
|
+
Requires-Dist: playwright>=1.40
|
|
24
|
+
Requires-Dist: rich>=13.0
|
|
25
|
+
Provides-Extra: piper
|
|
26
|
+
Requires-Dist: piper-tts>=1.4; extra == "piper"
|
|
27
|
+
Provides-Extra: elevenlabs
|
|
28
|
+
Requires-Dist: elevenlabs>=1.0; extra == "elevenlabs"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy; extra == "dev"
|
|
34
|
+
Requires-Dist: types-PyYAML; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# slideSonnet
|
|
38
|
+
|
|
39
|
+
Compile text-based slide presentations into narrated MP4 videos.
|
|
40
|
+
|
|
41
|
+
Write your slides in [MARP](https://marp.app/) Markdown or LaTeX Beamer, add narration with `<!-- say: -->` comments, and slideSonnet handles TTS synthesis, video composition, and assembly — with incremental builds that only re-synthesize changed slides.
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
lecture.yaml (playlist)
|
|
47
|
+
|
|
|
48
|
+
├── 01-intro/slides.md → [parse → TTS → compose] → module_01.mp4
|
|
49
|
+
├── animations/euler.mp4 → [passthrough] → module_02.mp4
|
|
50
|
+
├── 02-proofs/slides.tex → [parse → TTS → compose] → module_03.mp4
|
|
51
|
+
└── [assemble] ─────────────────────────────────────→ lecture.mp4
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
A **playlist** file chains modules together — MARP slides, Beamer slides, and pre-existing video files. Each module is built independently, then concatenated into the final video. [pydoit](https://pydoit.org/) manages the build graph with content-hash caching, so only changed slides trigger TTS.
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
### External dependencies
|
|
59
|
+
|
|
60
|
+
Install these system packages first:
|
|
61
|
+
|
|
62
|
+
| Tool | Required? | What it does | Install |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| **ffmpeg** | Yes | Video composition and concatenation | `sudo apt install ffmpeg` |
|
|
65
|
+
| **marp-cli** | Yes (for MARP slides) | Converts Markdown slides to PNG images | `npm install -g @marp-team/marp-cli` |
|
|
66
|
+
| **pdflatex + pdftoppm** | Only for Beamer | Compiles LaTeX and extracts slide images | `sudo apt install texlive-latex-base poppler-utils` |
|
|
67
|
+
|
|
68
|
+
After installing, run `slidesonnet doctor` to verify everything is set up correctly.
|
|
69
|
+
|
|
70
|
+
### Install slideSonnet
|
|
71
|
+
|
|
72
|
+
With [uv](https://docs.astral.sh/uv/) (recommended):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
uv tool install slidesonnet[piper]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
With [pipx](https://pipx.pypa.io/):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pipx install slidesonnet[piper]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The `[piper]` extra includes [Piper TTS](https://github.com/rhasspy/piper) for free local speech synthesis. Omit it if you plan to use ElevenLabs instead.
|
|
85
|
+
|
|
86
|
+
## Quick start
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Create an example project (MARP Markdown)
|
|
90
|
+
slidesonnet init md myproject
|
|
91
|
+
cd myproject
|
|
92
|
+
|
|
93
|
+
# Build the video
|
|
94
|
+
slidesonnet build lecture.yaml
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Showcase example
|
|
98
|
+
|
|
99
|
+
The `examples/showcase/` directory is a full-featured project that exercises every slideSonnet capability:
|
|
100
|
+
|
|
101
|
+
| Module | Format | Features demonstrated |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `part1.md` | MARP | Basic say, multiline say, multiple say blocks, nonarration |
|
|
104
|
+
| `part2.tex` | Beamer | `\say{}` with LaTeX, voice/pace overrides, `\nonarration`, `\slidesonnetskip` |
|
|
105
|
+
| `part3.md` | MARP | Voice presets, pace control, skip, pronunciation triggers |
|
|
106
|
+
| `animations/transition.mp4` | Video | Passthrough (no parsing/TTS) |
|
|
107
|
+
|
|
108
|
+
It also includes two pronunciation dictionaries (`pronunciation/general.md` and `pronunciation/names.md`) and a playlist with all configuration options (`lecture.yaml`).
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
cd examples/showcase
|
|
112
|
+
slidesonnet build lecture.yaml
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Writing slides
|
|
116
|
+
|
|
117
|
+
### MARP Markdown
|
|
118
|
+
|
|
119
|
+
Add narration with `<!-- say: -->` HTML comments:
|
|
120
|
+
|
|
121
|
+
```markdown
|
|
122
|
+
---
|
|
123
|
+
marp: true
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
# Introduction
|
|
127
|
+
|
|
128
|
+
<!-- say: Welcome to the lecture. Today we cover graph theory basics. -->
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
# Euler's Theorem
|
|
133
|
+
|
|
134
|
+
<!-- say(voice=alice): Let me explain this theorem carefully. -->
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
# Diagram
|
|
139
|
+
|
|
140
|
+
<!-- nonarration -->
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
# Hidden Notes
|
|
145
|
+
|
|
146
|
+
<!-- skip -->
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
| Annotation | Effect |
|
|
150
|
+
|---|---|
|
|
151
|
+
| `<!-- say: text -->` | Narrate with default voice |
|
|
152
|
+
| `<!-- say(voice=alice): text -->` | Narrate with a named voice preset |
|
|
153
|
+
| `<!-- nonarration -->` | Show slide with silence (uses global `silence_duration`) |
|
|
154
|
+
| `<!-- nonarration(5) -->` | Show slide with silence for 5 seconds (per-slide override) |
|
|
155
|
+
| `<!-- skip -->` | Omit slide from video entirely |
|
|
156
|
+
| *(none)* | Treated as silent, emits a warning |
|
|
157
|
+
|
|
158
|
+
Multi-line narration is supported. Slides with multiple `<!-- say: -->` directives are expanded into animated sub-slides with progressive fragment reveal — see [MARP documentation](docs/marp.md) for details.
|
|
159
|
+
|
|
160
|
+
### Beamer LaTeX
|
|
161
|
+
|
|
162
|
+
Use the `\say` command (defined as a no-op by `slidesonnet.sty` so LaTeX compiles normally):
|
|
163
|
+
|
|
164
|
+
```latex
|
|
165
|
+
\usepackage{slidesonnet}
|
|
166
|
+
|
|
167
|
+
\begin{frame}
|
|
168
|
+
\frametitle{Euler's Theorem}
|
|
169
|
+
\say{The sum of all vertex degrees equals twice the number of edges.}
|
|
170
|
+
\say[voice=alice]{Let me explain more carefully.}
|
|
171
|
+
\end{frame}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Beamer equivalents: `\say{}`, `\say[voice=alice]{}`, `\nonarration`, `\nonarration[5]` (per-slide duration override), `\slidesonnetskip`. Frames with `\pause` produce multiple sub-slides that can be narrated independently — see [Beamer documentation](docs/beamer.md) for details.
|
|
175
|
+
|
|
176
|
+
## Playlist format
|
|
177
|
+
|
|
178
|
+
A single `.yaml` file per presentation. Configuration and module list in pure YAML:
|
|
179
|
+
|
|
180
|
+
```yaml
|
|
181
|
+
title: Graph Theory Lecture 1
|
|
182
|
+
tts:
|
|
183
|
+
backend: piper
|
|
184
|
+
piper:
|
|
185
|
+
model: en_US-lessac-medium
|
|
186
|
+
elevenlabs:
|
|
187
|
+
api_key_env: ELEVENLABS_API_KEY
|
|
188
|
+
voice_id: pNInz6obpgDQGcFmaJgB
|
|
189
|
+
voices:
|
|
190
|
+
alice:
|
|
191
|
+
piper: en_US-amy-medium
|
|
192
|
+
elevenlabs: 21m00Tcm4TlvDq8ikWAM
|
|
193
|
+
pronunciation:
|
|
194
|
+
shared:
|
|
195
|
+
- pronunciation/cs-terms.md
|
|
196
|
+
- pronunciation/math-terms.md
|
|
197
|
+
# piper:
|
|
198
|
+
# - pronunciation/piper-hacks.md
|
|
199
|
+
# elevenlabs:
|
|
200
|
+
# - pronunciation/elevenlabs-hacks.md
|
|
201
|
+
video:
|
|
202
|
+
resolution: 1920x1080
|
|
203
|
+
fps: 24
|
|
204
|
+
crf: 23
|
|
205
|
+
pad_seconds: 1.5
|
|
206
|
+
pre_silence: 1.0
|
|
207
|
+
silence_duration: 3.0
|
|
208
|
+
crossfade: 0.5
|
|
209
|
+
modules:
|
|
210
|
+
- 01-intro/slides.md
|
|
211
|
+
- animations/euler.mp4
|
|
212
|
+
- 02-proofs/slides.tex
|
|
213
|
+
- 03-summary/slides.md
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
- Module type is auto-detected from extension (`.md` → MARP, `.tex` → Beamer, `.mp4` / `.mkv` / `.webm` / `.mov` → video passthrough)
|
|
217
|
+
- Lines starting with `//` are comments (filtered before YAML parsing)
|
|
218
|
+
- Video files are used as-is
|
|
219
|
+
|
|
220
|
+
## Pronunciation files
|
|
221
|
+
|
|
222
|
+
Reusable `.md` files with `**word**: replacement` pairs:
|
|
223
|
+
|
|
224
|
+
```markdown
|
|
225
|
+
# CS Pronunciation Guide
|
|
226
|
+
|
|
227
|
+
## People
|
|
228
|
+
**Dijkstra**: DYKE-struh
|
|
229
|
+
**Euler**: OY-ler
|
|
230
|
+
|
|
231
|
+
## Terms
|
|
232
|
+
**adjacency**: uh-JAY-suhn-see
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Replacements are word-boundary aware (won't change "Eulerian") and case-insensitive. Reference them in the playlist under `pronunciation:`.
|
|
236
|
+
|
|
237
|
+
### Per-backend pronunciation
|
|
238
|
+
|
|
239
|
+
Pronunciation workarounds that fix one TTS engine often break another. You can specify separate files per backend:
|
|
240
|
+
|
|
241
|
+
```yaml
|
|
242
|
+
pronunciation:
|
|
243
|
+
shared:
|
|
244
|
+
- pronunciation/names.md
|
|
245
|
+
piper:
|
|
246
|
+
- pronunciation/piper-hacks.md
|
|
247
|
+
elevenlabs:
|
|
248
|
+
- pronunciation/elevenlabs-hacks.md
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
When building with `--tts piper`, the effective dictionary is `shared + piper`. With `--tts elevenlabs`, it's `shared + elevenlabs`. Backend-specific entries override shared entries for the same word.
|
|
252
|
+
|
|
253
|
+
The flat list format still works and is treated as `shared`:
|
|
254
|
+
|
|
255
|
+
```yaml
|
|
256
|
+
pronunciation:
|
|
257
|
+
- pronunciation/names.md
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Voice presets
|
|
261
|
+
|
|
262
|
+
Define named voices in the playlist. Each preset can map to different voice IDs per TTS backend, so `--tts piper` and `--tts elevenlabs` both resolve correctly:
|
|
263
|
+
|
|
264
|
+
```yaml
|
|
265
|
+
voices:
|
|
266
|
+
alice:
|
|
267
|
+
piper: en_US-amy-medium
|
|
268
|
+
elevenlabs: 21m00Tcm4TlvDq8ikWAM
|
|
269
|
+
bob:
|
|
270
|
+
piper: en_US-joe-medium
|
|
271
|
+
elevenlabs: pNInz6obpgDQGcFmaJgB
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
A simple string value is also supported — it is used as-is regardless of backend:
|
|
275
|
+
|
|
276
|
+
```yaml
|
|
277
|
+
voices:
|
|
278
|
+
alice: en_US-amy-medium
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Then use presets per-slide: `<!-- say(voice=alice): ... -->`. If a preset has no mapping for the active backend, the slide falls back to the default voice with a warning.
|
|
282
|
+
|
|
283
|
+
## API keys
|
|
284
|
+
|
|
285
|
+
For ElevenLabs, store keys in a `.env` file at the project root (auto-loaded at build time):
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
ELEVENLABS_API_KEY=sk-xxx-your-key
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The playlist references env var names, never values: `api_key_env: ELEVENLABS_API_KEY`.
|
|
292
|
+
|
|
293
|
+
## CLI reference
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
slidesonnet build lecture.yaml # build video + SRT subtitles
|
|
297
|
+
slidesonnet build lecture.yaml --tts piper # override TTS backend
|
|
298
|
+
slidesonnet build lecture.yaml --no-srt # build without generating subtitles
|
|
299
|
+
slidesonnet build lecture.yaml --dry-run # show what would be built (no TTS/FFmpeg)
|
|
300
|
+
slidesonnet preview lecture.yaml # quick build with local Piper TTS
|
|
301
|
+
slidesonnet subtitles lecture.yaml # regenerate SRT from cached audio
|
|
302
|
+
slidesonnet preview-slide slides.md 3 # play one slide's audio
|
|
303
|
+
slidesonnet preview-slide slides.md 3 -p lecture.yaml # with playlist config
|
|
304
|
+
slidesonnet init md myproject # MARP Markdown project
|
|
305
|
+
slidesonnet init tex myproject # Beamer LaTeX project
|
|
306
|
+
slidesonnet list lecture.yaml # list slides with cache status per slide
|
|
307
|
+
slidesonnet clean lecture.yaml # clean cache (keeps API audio by default)
|
|
308
|
+
slidesonnet doctor # check installed dependencies
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Incremental builds
|
|
312
|
+
|
|
313
|
+
TTS audio is cached by content hash of the narration text, not by slide number. This means:
|
|
314
|
+
|
|
315
|
+
- **No changes** → entire build is skipped
|
|
316
|
+
- **Edit one slide** → only that slide's audio is re-synthesized
|
|
317
|
+
- **Insert a slide** → existing slides hit the cache, only the new slide triggers TTS
|
|
318
|
+
- **Change voice preset** → affected slides rebuild (voice is part of the hash)
|
|
319
|
+
|
|
320
|
+
Use `--dry-run` (or `-n`) to see what a build would do without making any API calls:
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
$ slidesonnet build lecture.yaml --dry-run
|
|
324
|
+
8 narrated slides: 5 cached, 3 need TTS (~1,200 characters via elevenlabs)
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
This is especially useful before ElevenLabs builds to estimate API usage and cost.
|
|
328
|
+
|
|
329
|
+
Build artifacts live in `cache/` next to the playlist file. Add it to `.gitignore`.
|
|
330
|
+
|
|
331
|
+
## Subtitles
|
|
332
|
+
|
|
333
|
+
Every build automatically generates an SRT subtitle file alongside the video (`lecture.srt` next to `lecture.mp4`). The subtitles use the original narration text (before pronunciation substitutions) and are timed to match the audio.
|
|
334
|
+
|
|
335
|
+
Long narrations are split into subtitle-sized chunks at sentence boundaries, then clause boundaries, then word boundaries — each chunk timed proportionally by character count.
|
|
336
|
+
|
|
337
|
+
Use the SRT file as a starting point for translation or editing with any subtitle tool. To skip generation, pass `--no-srt`. To regenerate from cache without rebuilding:
|
|
338
|
+
|
|
339
|
+
```
|
|
340
|
+
slidesonnet subtitles lecture.yaml
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Project layout
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
my-course/
|
|
347
|
+
├── lecture.yaml # playlist + config
|
|
348
|
+
├── pronunciation/
|
|
349
|
+
│ └── cs-terms.md
|
|
350
|
+
├── 01-intro/slides.md # MARP module
|
|
351
|
+
├── 02-proofs/slides.tex # Beamer module
|
|
352
|
+
├── animations/euler.mp4 # video module
|
|
353
|
+
├── .env # API keys (gitignored)
|
|
354
|
+
├── lecture.mp4 # final output video
|
|
355
|
+
├── lecture.srt # auto-generated subtitles
|
|
356
|
+
├── cache/ # build artifacts (gitignored)
|
|
357
|
+
│ ├── audio/ # TTS cache (content-addressed)
|
|
358
|
+
│ ├── 01-intro/
|
|
359
|
+
│ │ ├── slides/ # extracted PNGs + manifest
|
|
360
|
+
│ │ ├── utterances/ # text sent to TTS (for debugging)
|
|
361
|
+
│ │ └── segments/ # per-slide video segments
|
|
362
|
+
│ └── .doit.db
|
|
363
|
+
└── .gitignore
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Development
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
git clone https://github.com/avivz/slideSonnet.git
|
|
370
|
+
cd slideSonnet
|
|
371
|
+
python3 -m venv .venv
|
|
372
|
+
source .venv/bin/activate
|
|
373
|
+
pip install -e ".[piper,dev]"
|
|
374
|
+
|
|
375
|
+
make test-unit # unit tests only (fast, no external tools)
|
|
376
|
+
make test # all tests (requires ffmpeg, marp, pdflatex, piper)
|
|
377
|
+
make lint # ruff check + format
|
|
378
|
+
make typecheck # mypy --strict
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## License
|
|
382
|
+
|
|
383
|
+
MIT
|