- Rust 100%
- axum + ptts, ein Binary statt Shim/venv/numpy - HTTP-Vertrag 1:1 aus test_server.py, 6 Tests ohne Modell-Download - PHONON_QUANT neu, Body-Limit 1 MiB statt unbegrenzt |
||
|---|---|---|
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
tts-phonon-rs
Rust-Port von tts-phonon: OpenAI-kompatibler TTS-Dienst über Phonon —
POST /v1/audio/speech liefert 24 kHz mono 16-bit WAV.
Warum überhaupt portiert
Das Original war ein Python-Shim mit dem Argument „ptts gibt es nur als Python-Binding".
Das stimmt nicht: ptts ist ein Rust-Crate (cargo add ptts --features hf,hub). Damit
fallen Shim, venv und numpy weg — ein Binary, ein Prozess, dieselben Bytes.
Bauen
cargo build --release # target/release/phonon-tts
cargo test # HTTP-Vertrag, ohne Modell-Download
cargo test -- --ignored # echter Modelllauf (lädt ~320 MB nach ~/.cache/huggingface)
Laufen
./target/release/phonon-tts # 0.0.0.0:8882
./target/release/phonon-tts --selftest # zwei deutsche Sätze, WAV-Header prüfen
Umgebungsvariablen wie im Original: 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).
Neu: PHONON_QUANT (q8 = Default wie im Original, f32 für volle Gewichte).
curl -s localhost:8882/health
curl -s -X POST localhost:8882/v1/audio/speech \
-H 'content-type: application/json' \
-d '{"input":"Der Deckenventilator ist jetzt an."}' -o out.wav
Vertrag (identisch zum Original)
GET /health→{"ok":true,"voice","lang","sr","device","quant","load_ms"}POST /v1/audio/speech→audio/wav, 16-bit PCM, mono,sample_ratedes Checkpoints- alles andere → 404
{"error":"not found"} - kaputtes JSON → 400
{"error":"bad json"} - Body kein Objekt → 400
{"error":"body must be a JSON object"} inputfehlt/leer/kein String → 400{"error":"empty input"}- unbekannter Stimmname → still auf die Default-Stimme zurückfallen (Clients schicken feste Namen)
- Synthese-Fehler → 500
{"error": "..."}
Abweichungen
pttsals Crate statt Python-Binding — derselbe Kern, dieselben Gewichte, kein Shim.PHONON_THREADSwirkt nur indirekt:pttsdimensioniert seinen eigenen Pool überxn; der Wert wird gelesen, aber nicht mehr gesetzt (die Python-Bindung hatte dafür einen eigenen Setter). Stattdessen begrenzt ein Mutex die Synthese auf eine Anfrage gleichzeitig — genau das Verhalten, das das Original dokumentiert hat („requests queue, one at a time").- Body-Limit 1 MiB — das Original las unbegrenzt viel in den Speicher. Ein Satz passt immer, ein Versehen kostet jetzt eine 413 statt RAM.
rewrites="default"entfällt: die Python-Bindung aktivierte darüber die Text-Rewrites des Modells. Der Rust-Pfad nutzt die Normalisierung der Checkpoint-Config. Hörbar erst bei Sonderzeichen-Ketten; nicht Teil des HTTP-Vertrags.--selftestprüft zusätzlichbits/channelsexplizit, nicht nurnframes.
Tests
cargo test — 6 Tests, kein Modell, kein Netz:
health_reports_the_model,speech_returns_24k_mono_16bit_wavmissing_or_blank_input_is_400_json,non_object_body_is_400_jsonunknown_voice_falls_back_to_default,other_paths_and_methods_are_404_json
Sie sind die 1:1-Übersetzung der test_server.py-Fälle des Originals; das Fake-Engine-Muster
ersetzt dort das Laden von 320 MB Gewichten für eine 400-Antwort.
cargo test -- --ignored lädt das echte Modell und prüft WAV-Header und Dauer.