# chatterbox-tts-cli Ein lokaler Text-to-Speech-Assistent auf Basis von [Chatterbox TTS](https://github.com/resemble-ai/chatterbox) (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-cli` für Geschwindigkeitsanpassung: ```bash sudo apt install rubberband-cli ``` --- ## Installation ```bash # 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 ```bash 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 ```bash # 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) ```bash # 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" | | 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. ```bash # 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 ```json { "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 } ``` ```bash # 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. ```bash # 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: ```bash 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`) ```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: ```python 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: ```bash # 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: ```python 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 ```yaml # 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: ```yaml service: rest_command.tts_speak data: text: "Die Waschmaschine ist fertig." ``` ### Node-RED / n8n HTTP-Request-Node direkt auf `POST http://:9999/speak` mit JSON-Body. Kein weiterer Setup nötig. --- ## Aussprache-Wörterbuch Für Namen oder Begriffe, die das Modell falsch ausspricht: ```json { "Xi Jinping": "Schi Dschinping", "Putin": "Pjutin", "Seoul": "Söul", "Kubernetes": "Kubernetis" } ``` ```bash python chatterbox_cli_v4.py --lang de \ --pronunciation-dict aussprache.json \ --input nachricht.txt ``` Häufige Begriffe sind bereits eingebaut (GitHub, YouTube, iPhone, Xi Jinping u. a.). Das eigene Dict wird immer **nach** dem eingebauten angewendet — Überschreibungen sind möglich. --- ## 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.