chatterbox-tts-cli/README.md
dschlueter 2bea7fb597 Wörterbuch: Abkürzungen korrigiert, Neustart-Hinweis in der Doku
Acht Umschriften nachgebessert (vom Nutzer): Abkürzungen wie CLI, IDE, SDK, MCP, TTS,
STT und VPN werden jetzt mit englischen Buchstabennamen gesprochen ("VPN" -> "Wieh Pieh
En" statt "Fau Peh En"), dazu "Elon Musk" -> "Ihlon Mask".

Anmerkung für später: damit sind die Abkürzungen uneinheitlich — CPU, GPU, URL und HTTP
stehen weiterhin auf deutscher Buchstabierung ("Geh Peh Uh"). Das kann so gewollt sein,
weil sich diese im Deutschen tatsächlich eingebürgert haben; wer es angleichen will,
findet sie am Ende von pronunciation/de.json.

Doku: der Neustart-Hinweis fehlte. Die CLI liest das Wörterbuch bei jedem Aufruf neu, die
Dienste halten es aber im Speicher (_cache in pronunciation.py) — ohne
`systemctl --user restart chatterbox-tts` bzw. `open-notebook-tts` sprechen sie
stillschweigend weiter die alte Aussprache.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 17:21:24 +02:00

18 KiB
Raw Blame History

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.52.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-cli fü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 (~23 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 (1030 s)
--speed N 1.0 Wiedergabegeschwindigkeit (0.52.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 1060 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 (2040 cm)
  • Gleichmäßiges, natürliches Sprechtempo
  • 3060 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"
LinkedIn „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

Nach einer Änderung am Wörterbuch müssen die Dienste neu gestartet werden — sie halten es pro Sprache im Speicher (_cache in pronunciation.py). Die CLI liest bei jedem Aufruf neu.

systemctl --user restart chatterbox-tts       # HTTP-Service / MCP-Adapter
systemctl --user restart open-notebook-tts    # Open-Notebook-Wrapper, falls installiert

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ɔɹi klingt wie „Ripacitari", dɪˈplɔɪmənt wie „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,50,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 mit ChatterboxMultilingualTTS; 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.