Audiobook
Converts a long-form article, PDF, or web page into a narrated audiobook.
Audiobook
Use this skill when Kip asks to "make an audiobook of...", "narrate this article",
"read this aloud", or supplies a URL/PDF/EPUB and wants a long-form audio file
back. Default output is a single MP3 in ~/audiobooks/ with embedded ID3 tags
and (when supported by the container) chapter markers per section. Preserve that archival
copy, then stage a delivery copy under the shared workspace before attaching it to
Telegram or another channel. OpenClaw may reject media paths outside its allowed
attachment directories.
Command
{{HOME}}/bin/article-audiobook-render.sh <url> [flags]
Direct invocation (equivalent):
python3 {{HOME}}/.openclaw/workspace/skills/audiobook/audiobook.py <url> [flags]
Flags
| Flag | Default | Description |
|---|---|---|
--provider {openai,elevenlabs,speaches} |
from config.yaml |
TTS backend |
--voice <name> |
alloy |
Provider-specific voice id |
--speed <0.5-2.0> |
1.0 |
Speaking rate |
--format {mp3,m4a} |
mp3 |
Container; m4a enables real chapter markers |
--summary |
off | Summarize the article before narrating (uses default LLM) |
--podcast |
off | Add intro/outro narration |
--no-cache |
off | Skip per-chunk cache |
--dry-run |
off | Extract + chunk + print plan; do not call TTS |
--out <path> |
auto | Override the output path |
Test example
{{HOME}}/bin/article-audiobook-render.sh \
https://www.vatican.va/content/leo-xiv/en/encyclicals/documents/20260515-magnifica-humanitas.html
Expected stdout (one line per phase, ending with a JSON summary):
fetch ok bytes=312841
extract ok title="Magnifica Humanitas" author="Pope Leo XIV" chars=158203
chunk ok sections=18 chunks=42 max_chars=3800
tts provider=openai voice=alloy chunks_total=42 cached=0 rendered=42
concat ok duration=05:12:47
tag ok format=mp3 path={{HOME}}/audiobooks/2026-05-30_magnifica-humanitas.mp3
{"title":"Magnifica Humanitas","author":"Pope Leo XIV","duration_seconds":18767,"runtime":"5h12m","path":"{{HOME}}/audiobooks/2026-05-30_magnifica-humanitas.mp3","chapters":18}
Reply to Kip with the human summary: Generated audiobook for "<title>" by <author>. Runtime: <h>h<m>m. File saved to <path>. Also attach the staged
file per "Channel delivery" below — reporting the path in text alone does
not make it appear as a playable/downloadable attachment in any channel,
Control UI included.
Caching
Per-chunk MP3 fragments are written to ~/.cache/openclaw-audiobook/<sha>.mp3
where <sha> = sha256(provider + voice + speed + chunk_text). Repeat runs of
the same URL with the same voice/speed only re-render chunks whose source text
actually changed. The cache is safe to delete at any time.
Providers
Default and fallback order live in config.yaml. Provider credentials come
from ~/.openclaw/.env:
OPENAI_API_KEY— openaiELEVENLABS_API_KEY— elevenlabsSPEACHES_BASE_URL(defaulthttp://latitude:8200),SPEACHES_TOKEN_FILE(default~/.config/openclaw-speaches/latitude-token) — speaches (offline, self-hosted)
If the requested provider is unavailable, the script falls back to the next
provider in provider.fallbacks. Speaches is the offline fallback — it fills
the role Piper used to (no per-call API cost, works without internet), but
runs on Ben's Latitude laptop over Tailscale instead of locally on the Pi.
Speaches (offline, self-hosted on Latitude)
- Server:
systemd --userservicespeaches.serviceon Latitude, bound to100.125.75.72:8200(tailnet only, bearer-token auth,/healthis the only public endpoint). - Model:
speaches-ai/Kokoro-82M-v1.0-ONNX— all 54 bundled voices are downloaded and available; default isaf_sky(providers.speaches.voiceinconfig.yaml). - List voices:
curl -s http://latitude:8200/v1/models | jq '.data[].voices'(requires the bearer token for anything except/health). - Swap the default voice for a single run:
~/bin/article-audiobook-render.sh --provider speaches --voice af_heart <url> - Cache keying includes the model + configured default voice, so changing
providers.speaches.voiceinconfig.yamltriggers a fresh render rather than returning a stale fragment; a--voiceoverride on the CLI does too, since it's part of the chunk cache key.
Source types
| Input | Extractor |
|---|---|
| HTML page | trafilatura |
PDF URL or file://*.pdf |
pypdf |
EPUB URL or file://*.epub |
ebooklib + BeautifulSoup |
| Plain text URL | passthrough |
Local Markdown/text file or file:// URL |
built-in plain-text extractor |
Local vault notes can be narrated directly:
{{HOME}}/bin/article-audiobook-render.sh "{{HOME}}/obsidian-vault/path/to/note.md" --format mp3
Logs
Per-run JSONL log at ~/audiobooks/_log.jsonl. Failed runs include the phase
that failed (fetch|extract|chunk|tts|concat|tag) plus the exception summary.
Channel delivery
After a successful render, keep the tagged archival file in ~/audiobooks/
and copy it to {{HOME}}/.openclaw/workspace/tmp/audiobook-delivery/ before
attaching it to any channel — Telegram, Control UI, or otherwise. Two
independent reasons this staging step matters:
- OpenClaw may reject local media paths outside its allowed directories, so staging under the workspace avoids that outright.
- OpenClaw only renders a file as a chat attachment when the tool result
carries the path in a structured field (
media/mediaUrl/path/filePath) — never from the path merely being mentioned in reply text. Control UI specifically resolves relative media paths against the session's working directory (the agent workspace), so referencing the staged copy with a workspace-relative path is what lets it render, where an absolute~/audiobooks/...path may not.
Report the archival path (~/audiobooks/...) in the completion summary for
the human-readable record, but attach the staged workspace copy for the
actual delivery. Verify the staged copy exists before attempting to send.
Known upstream caveat (as of OpenClaw 2026.9.4, 2026-09-17): Control UI
had a real bug where relative media paths in tool replies weren't rendered
as attachments at all (openclaw/openclaw#46240), fixed upstream in
openclaw/openclaw#147646 (merged 2026-09-14) — but not yet in any released
version as of 2026.9.4 (published 2026-09-11, predates the fix). If a
correctly-staged, correctly-referenced attachment still doesn't show up in
Control UI, check whether OpenClaw has shipped a release past this fix
before assuming the skill is broken.
Errors and retries
- Network fetch: 3 retries with exponential backoff (handled by
httpx) - TTS request: 2 retries per chunk, then fall through to the next provider in the fallback chain for that chunk only (mixed-provider runs are allowed and logged)
- ffmpeg concat: fails fast — broken cache fragments are deleted and the run retries once
RSS feed (nice-to-have)
If rss.enabled: true in config.yaml, every completed audiobook is appended
to ~/audiobooks/feed.xml (Atom/Podcast-compatible). The feed is served by the
nginx tailnet vhost if ~/audiobooks/ is symlinked under the files site.
When NOT to use this skill
- Short text (< 500 chars) — just narrate inline if needed.
- Paywalled or login-required URLs — extraction will fail; tell Kip.
- Copyrighted commercial books — out of scope; refuse.
