- Python 100%
Any valid JSON body that was not an object with a string input crashed the handler: an integer input hit str.strip, a list body hit .get, an integer voice hit .lower. The client got RemoteDisconnected, not an error code. do_POST now rejects a non-object body and a non-string or blank input with a 400 JSON response, and falls back to the default voice for a non-string one. Adds the first tests for the service (5, stdlib unittest): health, a real 24 kHz mono 16-bit WAV from a synthesis request, both 4xx paths and the documented unknown-voice fallback. |
||
|---|---|---|
| .gitignore | ||
| README.md | ||
| requirements.txt | ||
| server.py | ||
| test_server.py | ||
phonon-tts
OpenAI-compatible TTS shim over Phonon (ptts): POST /v1/audio/speech returns 24 kHz
mono 16-bit WAV. One Python file — stdlib http.server, numpy, ptts; no PyTorch.
Why
ptts ships as a Rust core with Python bindings only, so OpenAI-shaped clients
(pandi-call, Open WebUI) need an HTTP wrapper instead of a binary — one URL change.
Run
cd ~/coding/projects/tts-phonon
python3 -m venv venv
venv/bin/pip install -r requirements.txt
venv/bin/python server.py # serves on 0.0.0.0:8882
venv/bin/python server.py --selftest # synth two German sentences, assert valid WAV
As a user service (this is how it actually runs):
systemctl --user enable --now phonon-tts.service
journalctl --user -u phonon-tts -f
Tests
venv/bin/python -m unittest -v test_server # HTTP-Vertrag: /health, WAV-Header, 4xx-Pfade, Voice-Fallback
Spaet das Modell (~1 s Ladezeit, ~100–300 ms pro Satz), ein eigener Server auf
127.0.0.1:0 — der laufende Dienst auf :8882 bleibt unberuehrt. Keine Audio-Qualitaetspruefung.
Usage
curl -s http://127.0.0.1:8882/health
# {"ok":true,"voice":"Henrik","lang":"de","sr":24000,"device":"cpu","quant":"q8_0","load_ms":867}
curl -s -X POST http://127.0.0.1:8882/v1/audio/speech \
-H 'Content-Type: application/json' \
-d '{"input":"Der Deckenventilator ist jetzt an, auf einundzwanzig Prozent.","voice":"Henrik"}' \
-o out.wav
Environment: PHONON_MODEL (default hf://empyrealworks/Phonon-40M-multilingual),
PHONON_VOICE (Henrik), PHONON_LANG (de), PHONON_PORT (8882), PHONON_BIND
(0.0.0.0), PHONON_THREADS (4).
Auth
None. No token, no key — it accepts every request it can reach. PHONON_BIND
defaults to 0.0.0.0, i.e. all interfaces, so this belongs on localhost or a trusted
LAN only. Set PHONON_BIND=127.0.0.1 if it should not leave the host.
Known limits
- Synthesis is behind one global lock: requests queue, one at a time.
THREADSis ptts' compute width, not concurrency. response_formatis ignored; the response is always WAV, whatever the client asked.- Unknown voice names are silently replaced by the default voice instead of erroring
(clients hardcode names like
martin). - No
/v1/models, no streaming, nospeed, no input length cap. - Weights download from Hugging Face on first start (
~/.cache/huggingface), the repo carries none. Load 0.8–0.9 s, RSS ~320 MB, 80–220 ms per request for 1.4–3.8 s audio.