Englische Fachbegriffe und fremde Eigennamen wurden auf Deutsch falsch vorgelesen
("Repository", "Donald Trump"). Chatterbox hat keinen Lautschrift-Eingang, also bleibt
nur die Umschrift in deutscher Rechtschreibung — die gab es zwar schon, aber nur als
kleines Dict im CLI-Skript, das keine andere Anwendung sinnvoll erweitern konnte.
Empirisch geprüft, warum es keinen besseren Weg gibt:
- Der multilinguale Tokenizer heißt "grapheme_mtl_merged_expanded_v1" und enthält zwar
IPA-Zeichen und ein [ipa]-Sprachtoken. generate() lehnt language_id="ipa" ab; umgeht
man nur die Whitelist, kommt Unsinn heraus: "ɹɪˈpɑzɪˌtɔɹi" klingt wie "Ripacitari",
"dɪˈplɔɪmənt" wie "Deep Romant" (per Whisper zurücktranskribiert). IPA ist tot.
- Der vorhandene Sprachwechsel im Satz ([en]...[/en]) synthetisiert die Teile korrekt,
klebt sie aber ohne Überblendung zusammen: gemessen 4 Nahtpausen von 0,5-0,8 s, der
Satz wächst von 3,9 s auf 7,1 s. Für Fließtext unbrauchbar.
Neu:
- pronunciation.py — eigenständiges Modul, reine Standardbibliothek (kein torch), damit
CLI, HTTP-Service, MCP-Adapter und fremde Projekte (Open Notebook) dasselbe Wörterbuch
nutzen können, ohne das Modell zu laden. Namen werden aus chatterbox_cli_v4
re-exportiert, bestehende Importe bleiben gültig.
- Ladereihenfolge mit Vorrang: eingebaut -> pronunciation/<lang>.json (mitgeliefert) ->
~/.config/chatterbox/pronunciation/<lang>.json -> $CHATTERBOX_PRONUNCIATION_DICT ->
explizit übergebenes Dict. Kaputte JSON-Dateien werden gemeldet, nicht fatal.
- pronunciation/de.json — kuratierte Einträge (Tech, KI, Firmen, Personen, Medien).
- pronunciation_candidates.py — findet in einem Text alles, was hunspell (de) nicht kennt
und noch nicht im Wörterbuch steht. An einem echten Podcast-Transkript getestet: 61
Kandidaten, darunter Voicebox, Scrapling, Scraping.
Fehlerbehebung: Ersetzt wird jetzt an Wortgrenzen und in einem einzigen Durchgang. Das
alte str.replace() machte aus "UNESCO" ein "Uh EnnESCO" und aus "UNIX" ein "Uh EnnIX",
weil der Eintrag "UN" auch mitten im Wort traf.
Verifiziert: 199 Einträge laden in 44 ms ohne torch; UNESCO/UNIX/Xiaomi bleiben intakt;
Open Notebooks TTS-Server spricht die Umschrift ohne eine einzige Codeänderung dort; der
MCP-Service auf :9999 läuft unverändert weiter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
17 KiB
chatterbox-tts-cli
Ein lokaler Text-to-Speech-Assistent auf Basis von Chatterbox TTS (Resemble AI). Optimiert für deutsche Sprache; nutzbar als Kommandozeilen-Tool, als lokaler HTTP-Service und als MCP-Server für KI-Assistenten.
Features
- Satz-für-Satz-Ausgabe — gibt den ersten Satz aus, während die nächsten bereits generiert werden; minimale Latenz
- Lückenlose Audiowiedergabe — Callback-basierter OutputStream; keine Unterbrechungen zwischen Sätzen
- Pause/Resume — Ausgabe pausieren und fortsetzen ohne Datenverlust (
POST /pause,POST /resume) - Geschwindigkeitsanpassung — pitch-erhaltende Zeitstreckung via pyrubberband (R3-Engine);
--speed 0.5–2.0 - Voice Cloning — optionale WAV-Referenz für Akzent und Klang
- Mehrsprachig — Deutsch, Englisch und 20+ weitere Sprachen via
ChatterboxMultilingualTTS - Gemischtsprachige Texte —
[en]...[/en]-Markierungen für englische Passagen in deutschen Texten - Deutsche Textnormalisierung — Datumsangaben (03.06.2026 → „Dritter Sechster Zwanzigsechsundzwanzig"), Uhrzeiten (14:58 → „vierzehn Uhr achtundfünfzig"), Jahreszahlen bis Milliarden, Einheiten (°C, °F, kWh, m², …), Abkürzungen, Aussprache-Wörterbuch
- Markdown-Bereinigung — entfernt
**fett**,# Überschrift, Links, Code-Blöcke automatisch vor der Synthese - HTTP-Service — FastAPI-Service mit Job-Queue, Stop/Pause/Interrupt, Status-Endpunkt
- MCP-Adapter — direkte Integration in Claude Code, Claude Desktop und andere MCP-Hosts
- Systemd-Autostart — Service startet automatisch beim Login
Systemvoraussetzungen
- Python 3.11+
- CUDA-GPU empfohlen (RTX 3070 oder besser; CPU möglich, aber langsam)
- Linux mit PipeWire oder PulseAudio
rubberband-clifür Geschwindigkeitsanpassung:sudo apt install rubberband-cli
Installation
# 1. Conda-Umgebung
conda create -n chatterbox python=3.11
conda activate chatterbox
# 2. PyTorch mit CUDA (Beispiel CUDA 12.4)
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu124
# 3. Alle Abhängigkeiten
pip install -r requirements.txt
Beim ersten Start mit --lang de werden Modelle automatisch heruntergeladen (~2–3 GB, ~/.cache/huggingface/).
Kommandozeilen-CLI
conda activate chatterbox
# Deutschen Text vorlesen
python chatterbox_cli_v4.py --lang de --input text.txt
# Mit Voice Cloning
python chatterbox_cli_v4.py --lang de --voice stimme.wav --input text.txt
# Text direkt übergeben
python chatterbox_cli_v4.py --lang en --text "Hello, how are you?"
# Langsamer sprechen (pitch bleibt gleich)
python chatterbox_cli_v4.py --lang de --speed 0.85 --input text.txt
# Nur speichern, nicht abspielen
python chatterbox_cli_v4.py --lang de --no-play --output ausgabe.wav --input text.txt
# Aussprache-Wörterbuch
python chatterbox_cli_v4.py --lang de --pronunciation-dict aussprache.json --input text.txt
# Gemischtsprachiger Text
python chatterbox_cli_v4.py --lang de --text \
"Das [en]Machine Learning[/en] Modell kostet ca. 50 €."
# Markdown-Bereinigung deaktivieren
python chatterbox_cli_v4.py --lang de --no-strip-markdown --input text.txt
CLI-Optionen
| Option | Standard | Beschreibung |
|---|---|---|
--text TEXT |
— | Text direkt als Argument |
--input DATEI |
— | UTF-8-Textdatei |
--lang CODE |
de |
Sprachcode (de, en, fr, es, …) |
--voice DATEI.wav |
— | Referenz-WAV für Voice Cloning (10–30 s) |
--speed N |
1.0 |
Wiedergabegeschwindigkeit (0.5–2.0) |
--audio-device |
pulse |
Ausgabegerät (z. B. pulse, default) |
--t3-model |
v3 |
Multilingual-Modell: v3 oder v2 |
--acronym-mode |
german |
Akronym-Modus: german, space, period_space |
--pronunciation-dict |
— | JSON-Datei mit Aussprache-Substitutionen |
--no-strip-markdown |
— | Markdown-Formatierung nicht entfernen |
--no-normalize-dates |
— | Datumsangaben nicht in Ordinalform umwandeln |
--no-normalize-times |
— | Uhrzeiten nicht umwandeln |
--no-normalize-years |
— | Jahreszahlen nicht umwandeln |
--no-normalize-units |
— | Einheiten nicht umwandeln |
--save |
nein | WAV-Datei speichern |
--output DATEI.wav |
— | Ausgabepfad (impliziert --save) |
--no-play |
— | Nicht live abspielen |
--no-sentence-mode |
— | Größere Chunks statt satzweise |
--stream |
— | Streaming-Modus (experimentell) |
--no-progress |
— | Weniger Konsolenausgabe |
--debug-delay N |
0 |
Pause vor jedem Satz (zum Testen) |
--stop |
— | Laufende Ausgabe abbrechen |
Voice Cloning — Stimme aufnehmen
Für Voice Cloning braucht Chatterbox eine WAV-Aufnahme von 10–60 Sekunden in ruhiger Umgebung.
Aufnahme-Workflow
# 1. Verfügbare Mikrofon-Eingänge anzeigen
pactl list sources short | grep -v monitor
# 2. Trainingstext in der Datei anzeigen / lesen
cat ~/chatterbox-tts-cli/Trainings_Text.txt
# 3. Aufnahme starten — 60 Sekunden, stoppt automatisch
# (nutzt das Standard-Eingabegerät von PipeWire/PulseAudio)
arecord -D pulse -f S16_LE -r 48000 -c 1 --duration=60 \
~/chatterbox-tts-cli/my_voice_deutsch_60s.wav
# 4. Aufnahme abhören und prüfen
aplay ~/chatterbox-tts-cli/my_voice_deutsch_60s.wav
# 5. Als Voice-Profil nutzen
conda activate chatterbox
python chatterbox_cli_v4.py --lang de \
--voice my_voice_deutsch_60s.wav \
--text "Testtext mit meiner Stimme."
Bestimmtes Mikrofon auswählen (z. B. MOTU M2)
# Quellname des M2 herausfinden
pactl list sources short | grep -v monitor
# Aufnahme explizit vom MOTU M2:
arecord -D pulse \
--default-device=alsa_input.usb-MOTU_M2_M20000046918-00.analog-stereo \
-f S16_LE -r 48000 -c 1 --duration=60 \
~/chatterbox-tts-cli/my_voice_deutsch_60s.wav
Tipps für gute Aufnahmen
- Ruhige Umgebung, kein Hintergrundlärm
- Normaler Gesprächsabstand zum Mikrofon (20–40 cm)
- Gleichmäßiges, natürliches Sprechtempo
- 30–60 Sekunden sind ideal; 10 Sekunden sind das Minimum
Gemischtsprachige Texte
Deutsche Texte enthalten oft englische Fachbegriffe, Markennamen oder Zitate.
Mit [xx]...[/xx]-Markierungen werden diese Passagen mit der richtigen language_id
an das Multilingual-Modell übergeben:
Das [en]Machine Learning[/en] Framework kostet ca. 50 €.
Der [en]CEO[/en] sagte: [en]"We are committed to innovation."[/en]
Ohne Markierungen verhält sich das System identisch wie bisher.
Häufige englische Tech-Begriffe werden bereits automatisch korrekt ausgesprochen
(eingebaut in DEFAULT_PRONUNCIATION_DE):
| Begriff | Aussprache |
|---|---|
| GitHub | „Git Hab" |
| YouTube | „Jutjub" |
| „Linked In" | |
| Wi-Fi | „Wai Fai" |
| iPhone | „Aiphone" |
| ChatGPT | „Tschet Dschie Pie Tie" |
HTTP-Service (tts_service.py)
FastAPI-Service mit Job-Queue und Worker-Thread. Startet automatisch via systemd.
# Manueller Start
uvicorn tts_service:app --host 0.0.0.0 --port 9999
# Mit Modell-Warmup (kein Cold-Start beim ersten Request)
TTS_PRELOAD_LANG=de uvicorn tts_service:app --host 0.0.0.0 --port 9999
# Systemd (Autostart, läuft bereits)
systemctl --user status chatterbox-tts
systemctl --user restart chatterbox-tts
journalctl --user -u chatterbox-tts -f
Endpunkte
| Methode | Pfad | Funktion |
|---|---|---|
POST |
/speak |
Text in Queue einreihen |
POST |
/stop |
Ausgabe abbrechen, Queue leeren |
POST |
/pause |
Ausgabe pausieren (ohne Datenverlust) |
POST |
/resume |
Pausierte Ausgabe fortsetzen |
GET |
/audio/{job_id} |
Fertige WAV herunterladen (nur mit keep_audio=true) |
GET |
/health |
Service-Status und Gerät |
GET |
/status |
Aktueller Job, Queue-Länge, letzte Jobs |
GET |
/voices |
Unterstützte Sprachen |
/speak Request-Body
{
"text": "Hallo Welt",
"lang": "de",
"voice": null,
"interrupt": false,
"speed": 1.0,
"t3_model": "v3",
"audio_device": null,
"max_len": 400,
"save_wav": false,
"output_path": null,
"pronunciation_dict": null,
"session_id": null,
"keep_audio": false
}
# Beispiel
curl -X POST http://localhost:9999/speak \
-H "Content-Type: application/json" \
-d '{"text": "Hallo Welt", "lang": "de"}'
# Aus dem LAN
curl -X POST http://192.168.x.x:9999/speak \
-H "Content-Type: application/json" \
-d '{"text": "Text aus dem Netzwerk", "lang": "de"}'
# Laufende Ausgabe unterbrechen
curl -X POST http://localhost:9999/speak \
-H "Content-Type: application/json" \
-d '{"text": "Wichtiger Text", "lang": "de", "interrupt": true}'
# WAV-Datei erzeugen und herunterladen
JOB=$(curl -s -X POST http://localhost:9999/speak \
-H "Content-Type: application/json" \
-d '{"text": "Hallo Welt", "lang": "de", "keep_audio": true}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])")
sleep 40 # warten bis Synthese fertig
curl -o ausgabe.wav http://localhost:9999/audio/$JOB
# → WAV-Datei; wird nach dem Download automatisch gelöscht
# Pausieren und fortsetzen
curl -X POST http://localhost:9999/pause
curl -X POST http://localhost:9999/resume
# Stoppen
curl -X POST http://localhost:9999/stop
MCP-Adapter (mcp_adapter.py)
Dünner Wrapper über die REST-API für MCP-fähige Hosts.
# stdio-Modus (Claude Code / Claude Desktop)
python mcp_adapter.py --stdio
# HTTP-Modus (andere MCP-Clients, Port 8001)
python mcp_adapter.py
# Anderen TTS-Service ansprechen
TTS_URL=http://192.168.1.10:9999 python mcp_adapter.py --stdio
MCP-Tools
| Tool | Parameter | Funktion |
|---|---|---|
speak |
text, lang, voice, interrupt, speed, session_id, keep_audio | Text ausgeben |
stop |
— | Ausgabe stoppen und Queue leeren |
pause |
— | Ausgabe pausieren (ohne Datenverlust) |
resume |
— | Pausierte Ausgabe fortsetzen |
get_status |
— | Aktuellen Job und Queue abfragen |
list_voices |
— | Unterstützte Sprachen auflisten |
Claude Code Konfiguration
Bereits eingerichtet via claude mcp add --scope user. Zur manuellen Einrichtung:
claude mcp add --scope user chatterbox-tts \
/home/dschlueter/miniforge3/envs/chatterbox/bin/python \
/home/dschlueter/chatterbox-tts-cli/mcp_adapter.py --stdio
Claude Desktop (~/.config/claude/claude_desktop_config.json)
{
"mcpServers": {
"chatterbox-tts": {
"command": "/home/dschlueter/miniforge3/envs/chatterbox/bin/python",
"args": ["/home/dschlueter/chatterbox-tts-cli/mcp_adapter.py", "--stdio"]
}
}
}
Integration mit KI-Tools
Claude Code / Claude Desktop — MCP (fertig eingerichtet)
Claude kann direkt die Tools speak, stop, pause, resume, get_status und list_voices aufrufen.
Kein weiterer Setup nötig.
Ollama (llama3.2, qwen2.5, mistral-nemo u. a.)
Modelle mit Tool-Support können den REST-Service über Function Calling ansprechen:
import ollama, httpx
tools = [{
"type": "function",
"function": {
"name": "speak",
"description": "Text als Sprache ausgeben",
"parameters": {
"type": "object",
"properties": {
"text": {"type": "string"},
"lang": {"type": "string", "default": "de"},
"speed": {"type": "number", "default": 1.0},
},
"required": ["text"],
},
},
}]
resp = ollama.chat(model="qwen2.5", messages=[{"role": "user", "content": "..."}], tools=tools)
for call in resp.message.tool_calls or []:
if call.function.name == "speak":
httpx.post("http://127.0.0.1:9999/speak", json=call.function.arguments)
TTS Agent (tts_agent.py)
Eigenständiger Konversationsagent mit eingebautem Function Calling:
# Mit Ollama
python tts_agent.py --model qwen2.5
# Mit LM Studio
python tts_agent.py --base-url http://localhost:1234/v1 --model local-model
# Mit OpenAI
OPENAI_API_KEY=sk-... python tts_agent.py --model gpt-4o
# Mit Voice Cloning
python tts_agent.py --model qwen2.5 --voice my_voice.wav
Open WebUI
Im Open-WebUI-Menü unter Tools eine neue Python-Klasse anlegen:
import requests
class Tools:
def speak(self, text: str, lang: str = "de") -> str:
"""Text als Sprache ausgeben."""
r = requests.post("http://127.0.0.1:9999/speak",
json={"text": text, "lang": lang}, timeout=10)
return r.json().get("job_id", "error")
def stop(self) -> str:
"""Laufende Sprachausgabe stoppen."""
requests.post("http://127.0.0.1:9999/stop", timeout=5)
return "stopped"
Home Assistant
# configuration.yaml
rest_command:
tts_speak:
url: "http://192.168.x.x:9999/speak"
method: POST
content_type: "application/json"
payload: '{"text": "{{ text }}", "lang": "de"}'
tts_stop:
url: "http://192.168.x.x:9999/stop"
method: POST
tts_pause:
url: "http://192.168.x.x:9999/pause"
method: POST
tts_resume:
url: "http://192.168.x.x:9999/resume"
method: POST
Aufruf in einer Automation:
service: rest_command.tts_speak
data:
text: "Die Waschmaschine ist fertig."
Node-RED / n8n
HTTP-Request-Node direkt auf POST http://<host>:9999/speak mit JSON-Body.
Kein weiterer Setup nötig.
Aussprache-Wörterbuch
Für Namen oder Begriffe, die das Modell falsch ausspricht:
{
"Xi Jinping": "Schi Dschinping",
"Putin": "Pjutin",
"Seoul": "Söul",
"Kubernetes": "Kubernetis"
}
python chatterbox_cli_v4.py --lang de \
--pronunciation-dict aussprache.json \
--input nachricht.txt
Ladereihenfolge
Das Wörterbuch liegt in pronunciation.py — einem eigenständigen Modul ohne
torch-Abhängigkeit, damit jede Chatterbox-Anwendung (CLI, HTTP-Service, MCP-Adapter,
fremde Projekte) es nutzen kann, ohne ein 4-GB-Modell zu laden:
from pronunciation import load_pronunciation_dict, apply_pronunciation_dict
text = apply_pronunciation_dict(text, load_pronunciation_dict("de"))
Quellen werden zusammengeführt, spätere überschreiben frühere:
| # | Quelle | Zweck |
|---|---|---|
| 1 | DEFAULT_PRONUNCIATION_DE (in pronunciation.py) |
Notnagel, falls keine Datei da ist |
| 2 | pronunciation/<lang>.json |
die gepflegte Liste (~200 Einträge), versioniert |
| 3 | ~/.config/chatterbox/pronunciation/<lang>.json |
persönliche Ergänzungen |
| 4 | $CHATTERBOX_PRONUNCIATION_DICT (Pfad, :-getrennt) |
pro Projekt/Deployment |
| 5 | --pronunciation-dict bzw. API-Parameter |
pro Aufruf |
Inhalt anzeigen: python pronunciation.py de
Kandidaten finden
pronunciation_candidates.py sucht in einem Text alles, was hunspell (deutsches
Wörterbuch) nicht kennt und noch nicht im Wörterbuch steht — praktisch immer genau die
englischen Fachbegriffe und fremden Eigennamen:
python pronunciation_candidates.py transkript.txt # nach Häufigkeit sortiert
python pronunciation_candidates.py *.txt --min-count 2 --json # Gerüst zum Ausfüllen
Braucht hunspell + hunspell-de-de. Die Umschrift schreibt ein Mensch.
Ersetzt wird an Wortgrenzen
Früher ein nacktes str.replace(): aus „UNESCO" wurde „Uh EnnESCO", aus „UNIX" ein
„Uh EnnIX". Jetzt wird nur an Wortgrenzen ersetzt, alles in einem Durchgang (längste
Einträge zuerst), sodass Ersetzungen sich nicht gegenseitig treffen.
Umschrift nie komplett großschreiben: die Akronym-Expansion läuft danach und würde „EIGEN" zu „E I Ge E En" buchstabieren.
Warum keine Lautschrift (IPA)? Chatterbox ist graphem-basiert; der multilinguale Tokenizer heißt wörtlich
grapheme_mtl_merged_expanded_v1. Im Vokabular stehen zwar IPA-Zeichen und ein[ipa]-Sprachtoken, aber empirisch spricht das Modell IPA nicht als Lautschrift:ɹɪˈpɑzɪˌtɔɹiklingt wie „Ripacitari",dɪˈplɔɪməntwie „Deep Romant". Auch der Weg über Sprachwechsel im Satz ([en]…[/en]) hilft hier nicht: die Segmente werden ohne Überblendung aneinandergehängt, jede Naht kostet 0,5–0,8 s Pause. Deshalb Umschrift.
Textnormalisierung im Überblick
Die Normalisierungspipeline läuft automatisch vor der TTS-Synthese (nur --lang de):
| Schritt | Beispiel Eingabe | Beispiel Ausgabe |
|---|---|---|
| Aussprache-Dict | „Xi Jinping" | „Schi Jinping" |
| Einheiten | „25 °C", „100 kWh", „10 m²" | „25 Grad Celsius", „100 Kilowattstunde", „10 Quadratmeter" |
| Datum | „03.06.2026" | „Dritter Sechster Zwanzigsechsundzwanzig" |
| Uhrzeit | „14:58 Uhr" | „vierzehn Uhr achtundfünfzig" |
| Jahreszahl | „2026" | „zweitausendsechsundzwanzig" |
| Akronyme | „ARD", „CPU" | „Ah Er De", „C P U" |
Jeder Schritt kann einzeln deaktiviert werden:
--no-normalize-dates, --no-normalize-times, --no-normalize-years, --no-normalize-units, --no-spell-acronyms
Bekannte Einschränkungen
- Wortbetonung lässt sich nicht steuern — kein SSML. Abhilfe: Voice-Referenz mit gewünschter Betonung.
- Laufendes
model.generate()kann nicht mid-call abgebrochen werden (Python-Thread-Grenzen); Stop/Pause greift am nächsten Chunk-Beginn. - Sprachmarkierungen
[en]...[/en]funktionieren nur mitChatterboxMultilingualTTS; bei--lang en(mono) werden sie ignoriert. - Streaming-Modus unterstützt keine Sprachmarkierungen.
Lizenz
MIT — dieses Skript. Das Chatterbox-Modell: MIT-Lizenz (Resemble AI). Modellgewichte: CC BY-NC 4.0.