SlidesToLectures
Turn a directory of slideshow PDFs into:
- Bookmarked PDFs: a copy of each deck with a
[N] Slide titlebookmark per topic, plus one combined PDF of every deck. - Slide text: a Markdown file per deck with one
## [N] Titlesection per slide, from the PDF's text layer or transcribed from slide images by Claude. - Lecture transcripts: a spoken, word-for-word lecture per deck, written by Claude to cover every point on every slide.
- Narrated audio: an MP3 per lecture with a chapter per slide, using Speechify text-to-speech.
Slides are numbered across the whole set (from the numbers printed on them when present), so bookmarks, text, lectures and audio chapters all line up.
Install
uv tool install "slidestolectures[tts]"
or pip install "slidestolectures[tts]". Leave off [tts] if you don't need audio. Requires Python 3.11+.
- The
visionandlecturesteps call the Claude API: setANTHROPIC_API_KEY. - The
ttsstep needsSPEECHIFY_API_KEYand ffmpeg on yourPATH.
Quick start
slides-to-lectures toc --input slides # out/pdfs/ and out/combined/
slides-to-lectures text --input slides # out/text/ (or: vision, to transcribe slide images with Claude)
slides-to-lectures lecture # out/lectures/
slides-to-lectures tts # out/audio/
Every command takes --input DIR (the slideshow PDFs), --out DIR (default out), --plugin and --config. Source PDFs are never modified. Run slides-to-lectures <command> --help for each command's options:
| Command | What it does | Notable options |
|---|---|---|
toc |
Bookmarked copy of each deck, plus a combined PDF | --dry-run prints the bookmarks |
text |
Slide text from the PDF text layer | |
vision |
Slide text transcribed from slide images by Claude; cached per slide | --model |
lecture |
Lecture transcript per deck from its slide text | --model, --force |
tts |
Narrated MP3 per lecture; audio cached per request | --voice, --rps, --concurrency, --force |
toc, text and vision accept deck names to process only some decks; lecture and tts accept Markdown files.
Configuration
Put a slidestolectures.toml in the directory you run from (or pass --config), so you don't have to repeat options:
plugin = "my_plugin.py" # optional, see below
input = "slides"
output = "out"
[output_dirs] # optional: put individual outputs elsewhere
pdfs = "bookmarked"
[settings] # plugin settings
course = "Introduction to Biology (BIO 101)"
audience = "a class of first-year undergraduates"
footer = "Example University"
Settings:
| Setting | Default | Used for |
|---|---|---|
course |
none | What the lectures teach, in the lecture prompt |
audience |
"a class" |
Who the lecture is presented to |
relevance |
"in practice" |
The lecture explains why each point matters relevance |
footer |
none | Footer text on every slide, which vision leaves out |
vision_model, lecture_model |
claude-opus-5-5 |
Claude models |
tts_voice |
henry |
Speechify voice |
numbering |
"continuous" |
For decks without printed slide numbers: "continuous" across decks, or "per-deck" |
footer_y, title_min_size, fallback_min_size, fallback_max_y |
480, 36, 24, 150 | Layout thresholds in points for a 540pt-tall slide, scaled to each page |
Plugins
When your slides need more than settings, such as particular filenames, deck titles, or fixes for slides whose title is an image, write a plugin: a subclass of slidestolectures.Plugin that overrides the hooks you need. Call super() to wrap the default behavior.
from slidestolectures import Plugin
class BiologyPlugin(Plugin):
course = "Introduction to Biology (BIO 101)"
def deck_title(self, path, doc):
return path.stem.split(" - ", 1)[-1] # "Week 3 - Cell Division" -> "Cell Division"
def slide_title(self, deck, number, detected):
if number == 42:
return "The Krebs Cycle" # this slide's title is an image
return super().slide_title(deck, number, detected)
Use it with --plugin my_plugin.py, --plugin my_plugin.py:BiologyPlugin, --plugin package.module:BiologyPlugin, or plugin = ... in the config. See examples/example_plugin.py.
| Hook | Default |
|---|---|
decks(paths) |
Every PDF in the input directory, sorted by name |
deck_title(path, doc) |
The filename, with _ as spaces |
output_name(deck) |
<filename>_<first>-<last> |
combined_name(decks) / should_combine(decks) |
<input dir>_<first>-<last>; combine when there's more than one deck |
find_slide_number(lines, page) |
The number at the end of the footer |
detect_title(lines, page) / slide_body(lines, title, page) |
Largest text near the top / the rest as Markdown |
slide_title(deck, number, detected) |
The detected title |
is_continuation(title) |
Titles with "cont'd" or "continued" get no bookmark |
vision_prompt(), lecture_system(), lecture_prompt(title, slides) |
Built from the settings |
Development
uv sync --all-extras
uv run pytest
uv run ruff check .
With mise, mise run install, mise run test, mise run lint and mise run build do the same.
Releasing
Bump version in pyproject.toml, then publish a GitHub release tagged v<version>. The Release workflow builds the package and publishes it to PyPI with trusted publishing.
License
Metadata
Release files for slidestolectures 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| slidestolectures-0.1.0.tar.gz | 20.8 kB | Without provenance | Details for slidestolectures-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| slidestolectures-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for slidestolectures-0.1.0-py3-none-any.whl |
Total release size: 46.2 kB
Release files / slidestolectures-0.1.0.tar.gz
| Download URL | slidestolectures-0.1.0.tar.gz |
|---|---|
| Size | 20.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0628b915ce6c944ec5e45846563a87cb20d6696acf719d5e8dabf2b8d9b19723
|
|
BLAKE2b-256 checksum How to use checksums |
96043d57ad5ed39c6c26e1c933095cd93d30a639c5ba6274dd8c0809dfa6ed13
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / slidestolectures-0.1.0-py3-none-any.whl
| Download URL | slidestolectures-0.1.0-py3-none-any.whl |
|---|---|
| Size | 25.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
534b2b91825483ed0b62f5e89ed10f8d9b6c3e5ce52eccbdcc20a11b4521ae2c
|
|
BLAKE2b-256 checksum How to use checksums |
832bb4541b065fa782e7861cde0758556e17b863207961b228dcd1fdd1cdec3e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for slidestolectures 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.