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.
Files changed (74) hide show
  1. slidesonnet-0.1.0/LICENSE +21 -0
  2. slidesonnet-0.1.0/PKG-INFO +383 -0
  3. slidesonnet-0.1.0/README.md +347 -0
  4. slidesonnet-0.1.0/pyproject.toml +77 -0
  5. slidesonnet-0.1.0/setup.cfg +4 -0
  6. slidesonnet-0.1.0/src/slidesonnet/__init__.py +3 -0
  7. slidesonnet-0.1.0/src/slidesonnet/actions.py +202 -0
  8. slidesonnet-0.1.0/src/slidesonnet/clean.py +289 -0
  9. slidesonnet-0.1.0/src/slidesonnet/cli.py +556 -0
  10. slidesonnet-0.1.0/src/slidesonnet/config.py +132 -0
  11. slidesonnet-0.1.0/src/slidesonnet/doctor.py +221 -0
  12. slidesonnet-0.1.0/src/slidesonnet/exceptions.py +25 -0
  13. slidesonnet-0.1.0/src/slidesonnet/hashing.py +99 -0
  14. slidesonnet-0.1.0/src/slidesonnet/init.py +69 -0
  15. slidesonnet-0.1.0/src/slidesonnet/models.py +222 -0
  16. slidesonnet-0.1.0/src/slidesonnet/parsers/__init__.py +0 -0
  17. slidesonnet-0.1.0/src/slidesonnet/parsers/base.py +24 -0
  18. slidesonnet-0.1.0/src/slidesonnet/parsers/beamer.py +361 -0
  19. slidesonnet-0.1.0/src/slidesonnet/parsers/expansion.py +169 -0
  20. slidesonnet-0.1.0/src/slidesonnet/parsers/marp.py +443 -0
  21. slidesonnet-0.1.0/src/slidesonnet/pipeline.py +849 -0
  22. slidesonnet-0.1.0/src/slidesonnet/playlist.py +63 -0
  23. slidesonnet-0.1.0/src/slidesonnet/preview.py +119 -0
  24. slidesonnet-0.1.0/src/slidesonnet/subtitles.py +344 -0
  25. slidesonnet-0.1.0/src/slidesonnet/tasks.py +423 -0
  26. slidesonnet-0.1.0/src/slidesonnet/templates/__init__.py +0 -0
  27. slidesonnet-0.1.0/src/slidesonnet/templates/env.txt +2 -0
  28. slidesonnet-0.1.0/src/slidesonnet/templates/example_playlist.yaml +27 -0
  29. slidesonnet-0.1.0/src/slidesonnet/templates/example_playlist_tex.yaml +27 -0
  30. slidesonnet-0.1.0/src/slidesonnet/templates/example_pronunciation.md +12 -0
  31. slidesonnet-0.1.0/src/slidesonnet/templates/example_slides_defs.md +27 -0
  32. slidesonnet-0.1.0/src/slidesonnet/templates/example_slides_defs.tex +31 -0
  33. slidesonnet-0.1.0/src/slidesonnet/templates/example_slides_intro.md +22 -0
  34. slidesonnet-0.1.0/src/slidesonnet/templates/example_slides_intro.tex +24 -0
  35. slidesonnet-0.1.0/src/slidesonnet/templates/gitignore.txt +8 -0
  36. slidesonnet-0.1.0/src/slidesonnet/tts/__init__.py +20 -0
  37. slidesonnet-0.1.0/src/slidesonnet/tts/base.py +37 -0
  38. slidesonnet-0.1.0/src/slidesonnet/tts/elevenlabs.py +114 -0
  39. slidesonnet-0.1.0/src/slidesonnet/tts/piper.py +103 -0
  40. slidesonnet-0.1.0/src/slidesonnet/tts/pronunciation.py +81 -0
  41. slidesonnet-0.1.0/src/slidesonnet/video/__init__.py +0 -0
  42. slidesonnet-0.1.0/src/slidesonnet/video/composer.py +444 -0
  43. slidesonnet-0.1.0/src/slidesonnet.egg-info/PKG-INFO +383 -0
  44. slidesonnet-0.1.0/src/slidesonnet.egg-info/SOURCES.txt +72 -0
  45. slidesonnet-0.1.0/src/slidesonnet.egg-info/dependency_links.txt +1 -0
  46. slidesonnet-0.1.0/src/slidesonnet.egg-info/entry_points.txt +2 -0
  47. slidesonnet-0.1.0/src/slidesonnet.egg-info/requires.txt +19 -0
  48. slidesonnet-0.1.0/src/slidesonnet.egg-info/top_level.txt +1 -0
  49. slidesonnet-0.1.0/tests/test_actions.py +213 -0
  50. slidesonnet-0.1.0/tests/test_beamer_parser.py +985 -0
  51. slidesonnet-0.1.0/tests/test_clean.py +331 -0
  52. slidesonnet-0.1.0/tests/test_cli.py +792 -0
  53. slidesonnet-0.1.0/tests/test_composer.py +865 -0
  54. slidesonnet-0.1.0/tests/test_config.py +276 -0
  55. slidesonnet-0.1.0/tests/test_doctor.py +450 -0
  56. slidesonnet-0.1.0/tests/test_dry_run.py +400 -0
  57. slidesonnet-0.1.0/tests/test_elevenlabs.py +136 -0
  58. slidesonnet-0.1.0/tests/test_elevenlabs_extended.py +107 -0
  59. slidesonnet-0.1.0/tests/test_expansion.py +144 -0
  60. slidesonnet-0.1.0/tests/test_hashing.py +161 -0
  61. slidesonnet-0.1.0/tests/test_init.py +147 -0
  62. slidesonnet-0.1.0/tests/test_marp_parser.py +1071 -0
  63. slidesonnet-0.1.0/tests/test_models.py +68 -0
  64. slidesonnet-0.1.0/tests/test_no_secrets.py +121 -0
  65. slidesonnet-0.1.0/tests/test_pipeline.py +513 -0
  66. slidesonnet-0.1.0/tests/test_piper.py +176 -0
  67. slidesonnet-0.1.0/tests/test_playlist.py +119 -0
  68. slidesonnet-0.1.0/tests/test_preflight.py +181 -0
  69. slidesonnet-0.1.0/tests/test_preview.py +237 -0
  70. slidesonnet-0.1.0/tests/test_pronunciation.py +165 -0
  71. slidesonnet-0.1.0/tests/test_showcase_build.py +108 -0
  72. slidesonnet-0.1.0/tests/test_strip_annotations.py +142 -0
  73. slidesonnet-0.1.0/tests/test_subtitles.py +392 -0
  74. 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