expressive-audiobook
Copyright and legal rights. This tool is for books and texts you have the legal right to use: your own writing, public-domain works, or material you have a license or the rightsholder's permission to convert. It is not made for, and we do not endorse, copying, converting, sharing or publishing copyrighted books without proper rights and permissions. Do not upload or distribute audio made from a work unless you have the right to. You are responsible for how you use it, and for following the laws and the terms of the services you connect (Gemini, ElevenLabs and others).
Turn a PDF, Markdown or text file you own into a spoken chapter. You pick the book and the delivery. A style profile (or one line of direction) is the only place literary judgment lives: pace, vocal tags, character voices, and how diagrams and formulas should be spoken.
Work happens in a project: one folder per book under projects/<name>/ that holds the extracted chapters, your settings, the narration style, and every generated file. Import once, tune, then generate as often as you like. Every step is resumable.
Google is optional. Gemini is the zero-setup default, but you can run everything locally instead: a local language model such as Ollama reads the book, and Orpheus (on Apple Silicon) speaks it with laughs, sighs and pauses, with no API key at all. You can also mix: for example Ollama for reading and Gemini for speech. See Run without Google. ElevenLabs is optional too, and only used if you pick it for sound effects.
With Gemini and no voice setup, speech uses the prebuilt voice Schedar (labeled "Even" in the Gemini voice list), which suits long-form narration.
What you need
- macOS or Linux
- Python 3.11 or newer
- A way to read and speak the book, either one of:
- a Gemini API key from Google AI Studio (the default, nothing else to install), or
- local models, with no Google account: Ollama (or another OpenAI-compatible server) for reading and Orpheus for speech (see Run without Google).
Optional:
- An ElevenLabs API key, only if you choose ElevenLabs for sound effects. Music and effects are made locally by default, so nothing needs ElevenLabs.
- ffmpeg, to also write MP3 or M4A copies of the audio (
brew install ffmpeg). Without it, chapter and sample audio are ordinary WAV files written by Python. - ffmpeg and
numpy(pip install -e ".[sound]") for mixing background music and sound effects; a local Stable Audio model makes the sounds (see below). - Tesseract, only for scanned pages. Install the
tesseractbinary andpip install -e ".[ocr]", then set"overrides": {"ingest": {"ocr_enabled": true}}in the project'sproject.json.
Install
With Homebrew (macOS):
brew install olostan/tap/audiobook
This installs a small launcher plus ffmpeg and uv. The first run downloads the app's Python packages and caches them. Or install the latest from source:
git clone https://github.com/olostan/expressive-audiobook.git
cd expressive-audiobook
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env
Open .env and set GEMINI_API_KEY to the key you created in AI Studio. ELEVENLABS_API_KEY can stay empty, and so can GEMINI_API_KEY if you run local models. The process environment wins over .env. The key is never read from a committed file.
Check the install, and your whole setup:
audiobook --version
audiobook doctor
audiobook doctor checks Python, which API keys you need for the engines you chose (and which you do not), whether Ollama or an Orpheus server is reachable and has your model, whether Orpheus is installed, and the optional tools (ffmpeg, numpy, Tesseract, Stable Audio). Each problem comes with the command that fixes it, and the exit code is 1 when something required is missing. Keys are never printed. Add --online to confirm a Gemini key is accepted (one free request), and --project NAME to check against a project's own settings.
Hear an example
▶️ Listen to audio sample (MP3)
examples/pip-and-the-lantern holds a short children's story, the narrated MP3 (about four minutes, with background music and sound effects), the style import wrote for it, and the settings used. Open the folder to see how to make the same thing from the source text.
Quick start: import, tune, prepare, generate
audiobook import --source ~/Downloads/book.pdf --name mybook
audiobook import --source ~/stories/pip.md --name pip # Markdown or plain text works too
-
Import. Reads the book into chapters (JSON), skips Contents pages, then asks Gemini to read the opening and write a narration style. It prints what it found and what you can change, and (in a terminal) asks you to confirm the suggested delivery and whether you want background music and sound effects. No speech is generated and almost nothing is spent.
-
Tune (optional). Edit
projects/mybook/project.json(voice, expressiveness, pace, pauses) andprojects/mybook/style.md(narrator persona, traits, pronunciation, how to speak formulas and figures, free notes). -
Prepare (optional). Writes the narration for chapters: the spoken text, delivery style, and inline tags such as
<chuckle>and<short pause>. Review it inprep/review/chapter-NNN.md, or editprep/outputs/chunks/*.jsonby hand.audiobook prepare --project mybook --chapter 4 audiobook prepare --project mybook --chapter 4-6 audiobook prepare --project mybook --all
-
Generate. Prepares anything not yet prepared, speaks it, and assembles audio.
audiobook generate --project mybook --chapter 4 --sample 30 # first 30 seconds of chapter 4 audiobook generate --project mybook --chapter 4 # the whole chapter audiobook generate --project mybook --all # the book (also writes the whole-book file) audiobook status --project mybook # progress per chapter
--project can be left out when projects/ holds only one project. gen is short for generate. Add --dry-run to see the estimate without any API call, --force to redo cached results, --quiet (before the command) for plain output. Colors turn off automatically when output is not a terminal or NO_COLOR is set.
Markdown and text files
--source takes a .pdf, .md or .txt file (--pdf still works).
- Markdown: chapters are the headings. A single top heading names the book; the next level down becomes the chapters. YAML front matter (
title,author) is read. Links, emphasis, images, code blocks, tables and comments are stripped; list items and block quotes are read as paragraphs. - Plain text: chapters are lines like
Chapter 3,Prologue,IV,2. The Hillor ALL-CAPS titles, with a short line after a bareChapter 3taken as its title.* * *scene breaks are dropped. - No headings at all: a long file is cut into parts of about 3,000 words at scene breaks and paragraph boundaries; a short one is a single chapter.
- A
NAME: linescript format is read as dialogue with speakers. Quoted dialogue in prose is left to the narrator for now.
Add --non-interactive to skip every question (the suggestions and flags are used), or pass the answers directly: --music 0.4 --sfx 0.3 --audience kids.
Background music and sound effects
Both are optional and off by default: they cost generation time (or money) and suit stories more than technical books. Import suggests them for fiction and asks; you can also set them any time:
audiobook sound --project pip --music 0.4 --sfx 0.3 --audience kids
audiobook sound --project pip --music-style "soft piano and strings, like a bedtime story"
--musicis 0 (off) to 1 (very noticeable). It sets how far under the voice the music sits; it also ducks while the narrator speaks.--sfxis 0 (off) to 1 (very noticeable). It sets how many sound effects are suggested (about one per 1,200 words at 0.1 and one per 150 at 1).--audience kidsasks for gentler music and no frightening or sudden sounds.
On generate, a "music supervisor" pass (one text-model call) writes score/music.json: one shared palette for the book and a prompt for each chapter. Each chapter gets a few variants of its music, and the mixer joins them with long crossfades in a shuffled order, from a random start, with a slow level drift, so a short loop is not noticeable. Each chapter fades its music in before the first word and out after the last. A "sound designer" pass reads each chapter's prepared text and writes score/sfx-NNN.json: one-off sounds at turn boundaries and quiet ambience loops (rain, wind) under a range of turns. Both plans are plain JSON you can edit; a plan whose inputs have not changed is not rewritten. Changing a level only remixes; speech is never redone.
Sounds are made locally with Stable Audio 3 (free, a few seconds each on Apple Silicon) and kept in a library shared by every book, so a rain loop made for one book is reused by the next; the sound designer is shown what the library already holds and prefers it.
audiobook models install sa3 # one time, about 8 GB, into ~/.audiobook/models
audiobook library list # everything made so far
audiobook library play music-warm-felt-piano-73a3b5d1
audiobook library reject sfx-a-small-soft-splash-1fe6bfa9 # never mix this one again
audiobook library generate --kind sfx --prompt "a wooden door creaking open" --seconds 3
audiobook library add ~/Music/rain.flac --kind sfx --loop --title "Rain"
New sounds start as candidate. Everything except rejected is mixed; set sound.require_approval = true (in overrides) to mix only items you have approved. ElevenLabs can make the effects instead ("overrides": {"sound": {"sfx_provider": "elevenlabs"}}, with ELEVENLABS_API_KEY in .env). Mixing needs ffmpeg and numpy (pip install -e ".[sound]"). With music or effects on, chapters are written as 44.1 kHz stereo, and a --dry-run shows how many tracks and cues are planned.
Run without Google
Gemini is the default, not a requirement. Two settings pick the models, and each can be changed on its own:
| What | Setting | Choices |
|---|---|---|
| Reading the book (narration prep, style detection, music and effects planning) | text.provider |
gemini, or openai for any OpenAI-compatible server: Ollama, LM Studio, llama.cpp, vLLM or a hosted API |
| Speaking | tts.provider |
gemini, orpheus (runs on your Mac), or http (an OpenAI-style /v1/audio/speech server, for example Orpheus-FastAPI) |
With both set to local engines there is no API key anywhere.
ollama pull qwen3:14b # any chat model your machine can run
audiobook models install orpheus # one time, about 7 GB, Apple Silicon (uses mlx-audio)
Put the choice in a config file (used by import, which runs before a project exists) or in one project's project.json:
{ "overrides": { "text": { "provider": "openai", "model": "qwen3:14b" },
"tts": { "provider": "orpheus" } } }
# my-config.toml, used with: audiobook --config my-config.toml import --source book.pdf --name mybook
[text]
provider = "openai"
model = "qwen3:14b"
base_url = "http://localhost:11434/v1" # Ollama; LM Studio is :1234
[tts]
provider = "orpheus"
What changes with a local engine:
- Orpheus is expressive through tags and punctuation, not a style line. It performs
<laugh>,<chuckle>,<sigh>,<cough>,<sniffle>,<groan>,<yawn>and<gasp>. The prep step is told this and uses only those tags, CAPITALIZED emphasis and punctuation. The per-turnstyleis kept for your review but is not spoken.<short pause>becomes real silence oftts.orpheus.pause_ms. - Voices are Orpheus's eight (
tara,leah,jess,leo,dan,mia,zac,zoe). Setnarration.voiceto one, for example"leo". Gemini names left in a project fall back totarafor the narrator, and each character gets a stable different voice. Designing voices (voices design) is Gemini-only. - Speech runs one clip at a time and costs nothing;
--dry-runshows a cost of 0. - A smaller text model follows the instructions less closely. Prep checks every result (allowed tags, no dropped figures or notes) and asks the model to fix mistakes, so weak output costs time, not quality. Try a larger model if chunks keep failing. Page images are only sent when
text.vision = trueand the model can see. - Changing the engine changes the cache fingerprints, so only the affected work is redone.
Where things live
~/.audiobook/ (override with AUDIOBOOK_HOME) holds projects/, library/ and models/. A ./projects folder keeps being used while it exists and ~/.audiobook/projects does not.
Resuming and changing your mind
Stop with Ctrl-C at any time and run the same command again. Each chunk of narration and each speech clip is saved atomically with a fingerprint of everything that produced it: the text, style.md, the tuning, the voice, the models, and the prompt. Unchanged work is skipped (shown as ✓ cached). Change a setting and only the affected chunks are redone, and stale clips are removed. A sample reuses the same cache as the full chapter, so listening to 30 seconds first costs nothing extra later.
project.json
{
"narration": {
"voice": "Schedar",
"expressiveness": 0.6,
"pace": 0.3,
"pause_density": 0.8
},
"audio": { "gap_ms": null, "chapter_tail_ms": null },
"character_voices": { "Achilles": "Puck" },
"overrides": { "prep": { "temperature": 0.5 } }
}
| Setting | Values | Effect |
|---|---|---|
expressiveness |
0 to 1 | 0 reads plainly; 1 is fully performed. Sets how often turns get a style and inline tags (about 1.5 tags per 100 words at 0.6). A passage that comes back too flat is sent back once for more performance. |
pace |
0 to 1 | 0 is slowest, about 0.5 is conversational, 1 is fastest. Below 0.4 the narrator is asked for an unhurried style. |
pause_density |
0 to 1 | 0 means only punctuation pauses; 1 means frequent <short pause> tags (two in a row for a longer pause; the model sometimes speaks <long pause> aloud). Also sets the default silence between passages (250 ms at 0, 900 ms at 1) and at the end of a chapter. |
audio.format |
mp3, m4a, wav |
With mp3 or m4a and ffmpeg installed, each chapter, sample and book is written as a compressed, tagged file (title, track, album), about 6 times smaller than WAV (WAV is 24 kHz mono, about 170 MB per hour). Without ffmpeg you get a note and WAV only. Default mp3. |
audio.keep_wav |
true/false | Default false: the WAV is deleted once its MP3/M4A exists, and is not rebuilt while the compressed file is current. Set true to keep both. The per-passage speech clips in audio/clips/ are always kept, so nothing is paid for twice. |
gap_ms, chapter_tail_ms |
number or null |
Silence between passages and after a chapter. null follows pause_density. |
voice, lite |
a Gemini voice, true/false | Narrator voice; lite uses the cheaper speech model. |
character_voices |
speaker to voice | Voices for characters in dialogue. |
overrides |
any section of default.toml |
Per-book changes to models, chunk length, retries, and so on. |
The old words (low/medium/high, slow/measured/normal/brisk, minimal/normal/generous) still work and map to numbers.
Import suggests expressiveness, pace, and pauses from the book itself. audiobook import --restyle asks again.
Layout
projects/mybook/
project.json style.md
ingest/ manifest.json, chapters/NNN.json, page images
chunks/ prep/ (outputs, review/) tts/
audio/ clips/, chapter-NNN.mp3 (or .m4a/.wav), book.mp3
samples/ chapter-004-30s.mp3
score/ music.json, sfx-NNN.json (when music or effects are on)
projects/ is ignored by git. Importing the same file again keeps your edits and does nothing; a different file under the same name needs --force and deletes the generated files. audiobook projects lists your projects.
Choose a voice
You can skip this section and stay on Schedar.
Prebuilt names, Extended Voice Library ids, and designed voice_... ids all work. Use any of them as narration.voice in project.json, or pass them with --voice to the low-level commands. With a local engine the voices are different; see Run without Google. A single Capitalized word is checked against the documented prebuilt list, so a typo is rejected before a paid call. Library ids usually contain a hyphen or digit. Designed ids start with voice_.
List voices (the catalog returns prebuilt voices and any library or designed voices on your key, plus metadata such as language and gender when the API sends them):
audiobook voices list --language en-US
audiobook voices list --search narrator --language en-US --gender neutral
Hear three prebuilt voices on the same minute of text. Schedar is even, Charon is informative, and Sulafat is warm. Set narration.voice in project.json, then sample a chapter. Only the speech is redone, and the sample is overwritten, so copy it aside before trying the next voice.
audiobook generate --project mybook --chapter 1 --sample 60 # with "voice": "Schedar"
audiobook generate --project mybook --chapter 1 --sample 60 # after changing it to "Charon"
Create a designed voice from a description. The command calls POST /v1beta/voices and prints a voice_... id:
audiobook voices design --description "A calm adult narrator with an even pace and a warm tone"
Use that id as narration.voice in project.json (or --voice voice_... with the low-level commands). You can also design a voice in the AI Studio Voice Design page and copy the same kind of id. Temporary emotion belongs in the style string for a turn, not in the voice description. Describe age, timbre, accent, and baseline pace.
"lite": true in project.json (--lite on the low-level commands) switches speech to gemini-3.8-flash-lite-tts from the config. Text preparation stays on the text model.
Write a style profile
In a project the narration style is projects/<name>/style.md, written by import and yours to edit. For a reusable, structured file, or for the low-level commands, copy an example and edit it:
cp profiles/examples/neutral-narration.yaml my-style.yaml
neutral-narration.yaml keeps ordinary sentences quiet. playful-philosophical.yaml allows a little more color and names two invented speakers. Field notes are in docs/STYLE_PROFILE.md.
The low-level commands also take one line instead of a file. --style is either that line or a path ending in .yaml, .yml, or .toml; --style-profile is the same file form. When both are set, the file is the profile and --style is extra direction for that run. Nothing is written back to the file.
audiobook sample --pdf /path/to/book.pdf --chapter 1 --style "even, unhurried, almost no vocal tags" --sample-seconds 60
audiobook sample --pdf /path/to/book.pdf --chapter 1 --style profiles/examples/neutral-narration.yaml --sample-seconds 60
Low-level commands (without a project)
Everything above is the normal way to work. These older commands take a PDF directly and write to artifacts/ (or into a project when you pass --project). They are handy for one-off experiments and for running a single stage.
Find the chapter number
audiobook extract --pdf /path/to/book.pdf
audiobook chapters
audiobook chunks --pdf /path/to/book.pdf --chapter 1
audiobook chapters prints the 1-based index, the title, and the page range. audiobook chunks prints the 0-based chunk index, estimated seconds, speakers, and the opening words. Use a chunk index whose speakers are not only the narrator when you want the sample to include dialogue.
Estimate before you spend
audiobook estimate --pdf /path/to/book.pdf --chapter 1 --sample-seconds 60
audiobook run --pdf /path/to/book.pdf --chapter 1 --style "even and clear" --dry-run
audiobook run --pdf /path/to/book.pdf --chapter 1 --style "even and clear" --max-chunks 5 --dry-run
audiobook sample --pdf /path/to/book.pdf --chapter 1 --style "even and clear" --sample-seconds 60 --dry-run
The lines are chunk count, estimated audio minutes, and estimated TTS cost in USD from default.toml. Prices there are a dated copy of the public Gemini price for gemini-3.8-flash-tts. Change them when the price page changes. Text-prep image tokens are not included. --sample-seconds and --max-chunks limit the estimate to the chunks that would actually be spoken.
One-minute sample
audiobook sample --pdf /path/to/book.pdf --chapter 1 --style "even and clear" --sample-seconds 60
The same flags work on run:
audiobook run --pdf /path/to/book.pdf --chapter 1 --style "even and clear" --sample-seconds 60
Chunks are taken in order from the start of the chapter until their estimated duration reaches about 60 seconds. The chunk that crosses the limit is included. --sample-start is the 0-based chunk index from audiobook chunks (use the index that shows dialogue when you want to hear speakers).
audiobook sample --pdf /path/to/book.pdf --chapter 1 --style "let each speaker stay distinct" --sample-start 0 --sample-seconds 60
The command runs the full path on only those chunks: Gemini text prep (emotion, inline tags, style strings, figure, formula, and footnote handling, then the validator), Gemini speech, and assembly. It also writes the review for exactly those chunks.
Outputs (the PDF file name, lowercased, is the book folder):
artifacts/<book>/samples/<chapter>-<timestamp>.wav
artifacts/<book>/samples/<chapter>-<timestamp>.md
Play the WAV. Open the Markdown review beside it. Each chunk shows the source text, and each turn shows speaker, style, prepared text, and the rationale. Tags such as <chuckle> or <short pause> appear in the prepared text. An empty style is normal for a plain sentence.
Sample files are not the chapter cache. A later full chapter still prepares and speaks every chunk, including the ones you already sampled.
Full chapter
audiobook run --pdf /path/to/book.pdf --chapter 1 --style profiles/examples/neutral-narration.yaml
audiobook review --chapter 1
The chapter WAV is artifacts/audio/chapter-001.wav (the number matches the chapter index). audiobook review reads the full-chapter prep outputs, not the sample review.
--max-chunks 5 on run (without --sample-seconds) caps a chapter run and stores those chunks in the chapter cache. A later run of the same chapter continues with the chunks that are not finished yet. Use --sample-seconds when you want a listen that must not become part of that cache.
Where files land
artifacts/ingest/manifest.json
artifacts/ingest/chapters/001.json
artifacts/chunks/001.jsonl
artifacts/prep/requests/<chunk-id>.json
artifacts/prep/outputs/chunks/<chunk-id>.json
artifacts/prep/outputs/001.jsonl
artifacts/prep/review/chapter-001.md
artifacts/tts/plans/<chunk-id>.json
artifacts/audio/clips/001/*.wav
artifacts/audio/chapter-001.wav
artifacts/<book>/samples/<chapter>-<timestamp>.wav
artifacts/<book>/samples/<chapter>-<timestamp>.md
Run one stage, or resume
Each stage reads the previous stage's files. A chunk whose prep file already validates is not sent to the text model again. A clip that already plays as 24 kHz mono 16-bit WAV is not synthesized again. Delete that chunk's prep file and its clips when you want them redone.
audiobook prep --chapter 1 --style profiles/examples/neutral-narration.yaml
audiobook tts --chapter 1 --style profiles/examples/neutral-narration.yaml
audiobook assemble --chapter 1
Text prep and speech retry HTTP 429 and 5xx with exponential backoff (prep.max_http_retries, tts.max_http_retries, backoff_initial_seconds, backoff_multiplier in the TOML config). Several chunks run at once, capped by prep.concurrency and tts.concurrency. If a prep response fails the JSON schema or the validator, the errors go back to the model up to prep.max_validation_retries times. The chunk then stops with its id and the errors in the message.
Scanned pages
A page with no text layer is kept as a spoken placeholder and listed on the chapter. To OCR it:
pip install -e ".[ocr]"
Install the tesseract binary from your OS packages, set ingest.ocr_enabled = true, and run audiobook extract again. Dense math and tiny notes will still need a look in the review.
Troubleshooting
Chapter breaks look wrong. Run audiobook chapters --project mybook and compare titles with the pages in the PDF. The extractor uses the PDF outline when there is one, otherwise headings. Front matter is its own chapter when pages come before the first chapter heading. Details are in docs/NOTES.md.
Scanned or blank pages. You will see a warning and a placeholder line. Turn on OCR as above, or skip that chapter.
Prep failed for chunk … Read the errors. The usual causes are an inline tag that is not on the allowlist, a style string that quotes the transcript, raw math left in the spoken text, or a figure or note the model dropped. Tighten the style profile, delete that chunk's file under projects/<name>/prep/outputs/chunks/, and run prepare again. Finished chunks stay finished.
Rate limits. The client already backs off on 429 and 5xx. If it still stops, lower prep.concurrency and tts.concurrency (in overrides in project.json, or default.toml) and run the same command again. Finished chunks and clips are skipped.
The voice sounds robotic. Try another prebuilt voice (Charon, Sulafat, Schedar) with a 60-second sample. Add a short line to style.md such as warm, unhurried, a little smile in the voice. Do not pile tags onto every sentence.
The voice sounds over-acted. Put Prefer an empty style on ordinary sentences in style.md, or lower expressiveness. Keep the style notes short. Empty style is what the speech model expects for a plain sentence. Design a calmer voice_... id if the prebuilt voice itself is too bright.
Dialogue is all one voice. Set character_voices in project.json for the speaker labels audiobook chunks --project mybook --chapter N prints. Only two prebuilt voices can share one request. A library id or a voice_... id is spoken one turn at a time.
Tests
pytest
pytest -m live
pytest does not call Gemini. pytest -m live runs one short text-prep call and one short speech call with the default prebuilt voice, and checks that the audio is 24 kHz mono 16-bit WAV-compatible PCM. It is skipped unless GEMINI_API_KEY is set, and it spends a small amount of quota.
License
Copyright 2026 Valentyn Shybanov. Licensed under the Apache License, Version 2.0.
Further reading
- docs/STYLE_PROFILE.md — profile fields
- docs/NOTES.md — extraction failure modes and the cost formula
- docs/GEMINI_API.md — model ids, speech fields, and voice design
src/expressive_audiobook/default.toml— models, voices, retries, and prices (to change it for every run, copy it toconfig/default.tomlin your working folder, or pass--config)
Metadata
Release files for expressive-audiobook 0.1.1
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 |
|---|---|---|---|---|
| expressive_audiobook-0.1.1.tar.gz | 164.6 kB | With provenance | Details for expressive_audiobook-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| expressive_audiobook-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for expressive_audiobook-0.1.1-py3-none-any.whl |
Total release size: 306.4 kB
Release files / expressive_audiobook-0.1.1.tar.gz
| Download URL | expressive_audiobook-0.1.1.tar.gz |
|---|---|
| Size | 164.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d5e0d6f27eac9138b607475653e7f103d7f022e8c576bae892f2408aa240346e
|
|
BLAKE2b-256 checksum How to use checksums |
2d7daad9e6fbbd05babd1de7754f769dd84a53e3215c4e6f31e7d190eed4f750
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.
Transparency logRelease files / expressive_audiobook-0.1.1-py3-none-any.whl
| Download URL | expressive_audiobook-0.1.1-py3-none-any.whl |
|---|---|
| Size | 141.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a44072106ae9f2d0bc08057cca6047541b7ab77e5d838ba4cde9e182a0a14fa8
|
|
BLAKE2b-256 checksum How to use checksums |
2db1f8c9d4600595281f3961f43e1ae808abd308340d0faf6334ed7072d45a91
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.
Transparency logSecurity for expressive-audiobook 0.1.1
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.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for expressive_audiobook-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.
Transparency logA 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.