Local HTTP/WebSocket Text-to-Speech gateway with pluggable providers (Piper first).
Project description
A local HTTP/WebSocket text-to-speech gateway with interchangeable engines.
🔊 Hear it · Install · API · Integrations · Docs
Send text from anywhere — a browser, Claude Code, a shell script, an editor —
and hear it spoken on your machine. TTS engines are pluggable providers behind
one stable API: Piper ships first,
and new engines (Kokoro, XTTS, cloud APIs...) drop in without touching the
server. No TTS engine installed yet? It still speaks: a built-in
dependency-free tone provider beeps the text, so you can verify the whole
pipeline before setting up Piper.
Install
pip / pipx (Python 3.10+):
pip install tts-daemon # or: pipx install tts-daemon (isolated, recommended)
One-line script (uses pipx when present, else pip --user):
curl -fsSL https://raw.githubusercontent.com/DMGiulioRomano/TTS-Daemon/main/scripts/install.sh | sh
Docker (API + synthesis; playback needs host audio — see docs/installation.md):
docker build -t tts-daemon https://github.com/DMGiulioRomano/TTS-Daemon.git
docker run --rm -p 5111:5111 tts-daemon
The 60-second tour
Start the server:
$ tts-daemon serve
INFO: Uvicorn running on http://127.0.0.1:5111 (Press CTRL+C to quit)
Speak from a second terminal (beeps until Piper is set up):
$ tts-daemon speak "It works"
[9e01742e98c1] synthesizing via tone
Or over HTTP — interrupt: true cuts off whatever is playing and speaks now:
$ curl -X POST localhost:5111/v1/speak -H 'content-type: application/json' \
-d '{"text": "Coming through", "interrupt": true}'
{"utterance":{"id":"c156561e4f1b","text":"Coming through","provider":"tone","state":"synthesizing",...}}
Check what the gateway is doing:
$ tts-daemon status
default provider : tone
playback : available
speaking : (idle)
queued : 0/64
Give it a real voice (Piper)
pip install piper-tts
mkdir -p ~/.local/share/tts-daemon/piper
python3 -m piper.download_voices en_US-lessac-medium \
--data-dir ~/.local/share/tts-daemon/piper
tts-daemon speak "Now with an actual voice"
The gateway's default provider is auto: it picks Piper as soon as the
binary and a voice model are found, and falls back to tone otherwise.
tts-daemon providers shows what is available and why. Full details (voice
downloads, macOS/Linux audio notes, running as a service):
docs/installation.md.
Why
- One API, many engines. Clients never care which engine is speaking. Switch from Piper to anything else by editing one config line.
- A real speech queue. Utterances play in order;
interrupt: truecancels everything and speaks now.POST /v1/stopsilences the room. - Anything can talk. REST + WebSocket + CLI + a browser userscript + a Claude Code hook, all included and all tiny.
- Local-first. Binds to
127.0.0.1by default; with Piper, audio never leaves your machine. - Small. Four runtime dependencies: FastAPI, uvicorn, pydantic, PyYAML.
How it compares
| tts-daemon | raw piper CLI |
speech-dispatcher / say |
cloud TTS APIs | |
|---|---|---|---|---|
| Speech queue + interrupt | ✅ | ❌ | partial¹ | ❌ (DIY) |
| Swappable engines | ✅ | ❌ (Piper only) | ✅ (its own modules) | ❌ (one vendor) |
| HTTP + WebSocket API | ✅ | ❌ | ❌ (DBus/local socket) | ✅ |
| Browser / editor integrations | ✅ included | ❌ | ❌ | DIY |
| Works offline | ✅ | ✅ | ✅ | ❌ |
| Cost | free | free | free | per character |
¹ speech-dispatcher queues and cancels, but speaks only for local
processes on Linux — there is no network API for browsers, editors, or
other machines' tools.
The API in 30 seconds
| Endpoint | What it does |
|---|---|
POST /v1/speak |
Queue text; options: provider, voice, speed, interrupt, wait |
POST /v1/stop |
Stop the current utterance and clear the queue |
POST /v1/synthesize |
Return audio bytes instead of playing them |
GET /v1/status |
Current utterance, queue, recent history |
GET /v1/voices |
Voices across providers (?provider= to filter) |
GET /v1/providers |
Registered providers + availability + which is default |
GET /v1/utterances/{id} |
State of one utterance |
WS /v1/ws |
Commands + live utterance lifecycle events |
GET /health |
Liveness + version |
Interactive docs are served at /docs; the
full reference with the WebSocket protocol is in docs/api.md.
# speed up, pick a voice, cut off whatever is playing:
curl -X POST localhost:5111/v1/speak -H 'content-type: application/json' \
-d '{"text": "Faster now", "voice": "en_US-lessac-medium", "speed": 1.4, "interrupt": true}'
Included integrations
- Browser — a userscript that speaks any selected text on any website (Alt+S / Alt+X), and can auto-read new chat replies (Alt+A) on ChatGPT/Claude/Gemini or any configurable site.
- Claude Code — a hook script that reads Claude's replies and notifications aloud.
- Terminal — the
tts-daemonCLI (speak,stop,status,voices,providers,synthesize), plus shell aliases. - Your code — a zero-dependency Python client
(
tts_daemon.client.GatewayClient) and examples for REST and WebSocket.
The Claude Code hook speaking a reply (and a notification) through the gateway.
Configuration
Optional — the defaults just work. Generate an annotated config with
tts-daemon init-config (written to ~/.config/tts-daemon/config.yaml):
speech:
default_provider: auto # or: piper, tone, ...
providers:
piper:
models_dir: ~/.local/share/tts-daemon/piper
default_voice: en_US-lessac-medium
Every key can also be set by environment variable
(TTS_DAEMON__SERVER__PORT=6000). See
docs/configuration.md.
Architecture
flowchart LR
subgraph clients [Clients]
A[browser / CLI / hooks / apps]
end
A -->|REST + WebSocket| B[FastAPI layer]
B --> C[SpeechService]
C --> D[PlaybackQueue<br/>worker thread]
C --> R[ProviderRegistry]
R --> P1[Piper]
R --> P2[Tone]
R -. entry points .-> P3[your provider]
D --> E[AudioPlayer<br/>paplay / ffplay / afplay / ...]
Clean layers: a framework-free core (models, queue, service), providers and players as swappable adapters, FastAPI kept at the edge. The reasoning and the locking/lifecycle rules live in docs/architecture.md.
Adding an engine means implementing one small interface
(synthesize, voices, availability) and registering it — either in-tree
or from your own package via the tts_daemon.providers entry point group,
with no gateway changes. Walkthrough: docs/providers.md.
Documentation
| docs/installation.md | Install, Piper voices, audio output, service setup |
| docs/configuration.md | Every option, file + env layering |
| docs/api.md | REST + WebSocket reference |
| docs/architecture.md | Design, layers, threading model |
| docs/providers.md | Writing a new TTS provider |
| docs/development.md | Dev setup, tests, linting, release |
| CONTRIBUTING.md | How to contribute |
| CHANGELOG.md | Release history |
Star history
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tts_daemon-0.1.0.tar.gz.
File metadata
- Download URL: tts_daemon-0.1.0.tar.gz
- Upload date:
- Size: 43.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c909be01bdf40de7c9eefc3c39350cf5ff5d4160d4906b0ed4df61a2f982b73
|
|
| MD5 |
f6e87cdeeb6ab802559c31eb97502288
|
|
| BLAKE2b-256 |
e9f561422620e30d497513acec323497a76c4baced7180f0a9227cc12b0acded
|
Provenance
The following attestation bundles were made for tts_daemon-0.1.0.tar.gz:
Publisher:
release.yml on DMGiulioRomano/TTS-Daemon
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tts_daemon-0.1.0.tar.gz -
Subject digest:
8c909be01bdf40de7c9eefc3c39350cf5ff5d4160d4906b0ed4df61a2f982b73 - Sigstore transparency entry: 2211609270
- Sigstore integration time:
-
Permalink:
DMGiulioRomano/TTS-Daemon@47a09c4f751ff3dac33e315571582e1ee9d6e724 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/DMGiulioRomano
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@47a09c4f751ff3dac33e315571582e1ee9d6e724 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tts_daemon-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tts_daemon-0.1.0-py3-none-any.whl
- Upload date:
- Size: 49.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83c95fce768065b355833a0640025ee3dbdbe8102c801ac594dc4a938c3caac8
|
|
| MD5 |
dc89b5c16c1031fa3cb1865415fb29d2
|
|
| BLAKE2b-256 |
23a39b8849eb507711880078217657582e8e0772fabd80841071ea8c56d8bd35
|
Provenance
The following attestation bundles were made for tts_daemon-0.1.0-py3-none-any.whl:
Publisher:
release.yml on DMGiulioRomano/TTS-Daemon
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tts_daemon-0.1.0-py3-none-any.whl -
Subject digest:
83c95fce768065b355833a0640025ee3dbdbe8102c801ac594dc4a938c3caac8 - Sigstore transparency entry: 2211609315
- Sigstore integration time:
-
Permalink:
DMGiulioRomano/TTS-Daemon@47a09c4f751ff3dac33e315571582e1ee9d6e724 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/DMGiulioRomano
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@47a09c4f751ff3dac33e315571582e1ee9d6e724 -
Trigger Event:
push
-
Statement type: