Skip to main content

Local HTTP/WebSocket Text-to-Speech gateway with pluggable providers (Piper first).

Project description

tts-daemon

A local HTTP/WebSocket text-to-speech gateway with interchangeable engines.

CI PyPI PyPI downloads Python 3.10+ License: MIT GitHub Discussions

🔊 Hear it · Install · API · Integrations · Docs

tts-daemon demo: serve, speak, interrupt, status

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: true cancels everything and speaks now. POST /v1/stop silences 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.1 by 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-daemon CLI (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.

Claude Code hook: Claude's replies spoken aloud through the gateway
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

Star history chart

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tts_daemon-0.1.0.tar.gz (43.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tts_daemon-0.1.0-py3-none-any.whl (49.2 kB view details)

Uploaded Python 3

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

Hashes for tts_daemon-0.1.0.tar.gz
Algorithm Hash digest
SHA256 8c909be01bdf40de7c9eefc3c39350cf5ff5d4160d4906b0ed4df61a2f982b73
MD5 f6e87cdeeb6ab802559c31eb97502288
BLAKE2b-256 e9f561422620e30d497513acec323497a76c4baced7180f0a9227cc12b0acded

See more details on using hashes here.

Provenance

The following attestation bundles were made for tts_daemon-0.1.0.tar.gz:

Publisher: release.yml on DMGiulioRomano/TTS-Daemon

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for tts_daemon-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 83c95fce768065b355833a0640025ee3dbdbe8102c801ac594dc4a938c3caac8
MD5 dc89b5c16c1031fa3cb1865415fb29d2
BLAKE2b-256 23a39b8849eb507711880078217657582e8e0772fabd80841071ea8c56d8bd35

See more details on using hashes here.

Provenance

The following attestation bundles were made for tts_daemon-0.1.0-py3-none-any.whl:

Publisher: release.yml on DMGiulioRomano/TTS-Daemon

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page