- chatterbox_cli_v4.py: cooperative stop/interrupt via threading.Event; fix force_split_sentence (word boundary instead of mid-word cut); fix synthesize_streaming normalization order (split before preprocess) - tts_service.py: FastAPI service with job queue, model cache, worker thread; LAN-accessible on 0.0.0.0:9999; audio_device default None (auto) - mcp_adapter.py: MCP adapter (stdio + streamable-http) wrapping REST API; update docstring and default TTS_URL to port 9999 - requirements.txt: add fastapi, uvicorn, httpx, mcp - README.md, BEDIENUNGSANLEITUNG.md: document service, MCP, AI integrations (Claude, Ollama, Open WebUI, llama.cpp, Home Assistant), systemd autostart - CLAUDE.md: reflect current architecture (service + adapter now implemented) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.5 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Running the CLI
conda activate chatterbox
# Deutschen Text aus Datei vorlesen
python chatterbox_cli_v4.py --lang de --input text.txt
# Mit Voice Cloning
python chatterbox_cli_v4.py --lang de --voice my_voice.wav --input text.txt
# Text direkt übergeben (Englisch)
python chatterbox_cli_v4.py --lang en --text "Hello world"
# Nur speichern, kein Playback
python chatterbox_cli_v4.py --lang de --no-play --output ausgabe.wav --input text.txt
# Geschwindigkeit anpassen (pitch-erhaltend, erfordert rubberband-cli)
python chatterbox_cli_v4.py --lang de --speed 0.85 --input text.txt
# Streaming-Modus (experimentell, niedrigere Latenz, kann abgehackt klingen)
python chatterbox_cli_v4.py --lang de --stream --input text.txt
# Aussprache-Wörterbuch (JSON: {"Eigenname": "Lautschrift"})
python chatterbox_cli_v4.py --lang de --pronunciation-dict aussprache.json --input text.txt
No build step, no test suite, no linter configuration — this is a single-file script.
Running the HTTP Service
# Läuft als systemd-User-Service (Autostart beim Login):
systemctl --user status chatterbox-tts
systemctl --user restart chatterbox-tts
journalctl --user -u chatterbox-tts -f
# Manuell starten (Port 9999, LAN-weit erreichbar):
uvicorn tts_service:app --host 0.0.0.0 --port 9999
# Health-Check:
curl http://127.0.0.1:9999/health
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
Running the MCP Adapter
# stdio (Claude Code / Claude Desktop) — bereits in ~/.claude.json konfiguriert:
python mcp_adapter.py --stdio
# HTTP-Transport (Port 8001):
python mcp_adapter.py
# Anderen TTS-Service ansprechen:
TTS_URL=http://192.168.1.10:9999 python mcp_adapter.py --stdio
Architecture
Files
| Datei | Funktion |
|---|---|
chatterbox_cli_v4.py |
Kern-CLI und alle Hilfsfunktionen; wird von tts_service.py importiert |
tts_service.py |
FastAPI-Service mit Job-Queue und Worker-Thread |
mcp_adapter.py |
MCP-Wrapper über die REST-API |
CLI pipeline (chatterbox_cli_v4.py)
Text input → clean_raw_text → chunking → preprocess_tts_text per chunk → TTS generation → audio output
Reihenfolge ist kritisch: erst splitten (Satzgrenzen auf Rohtext erkennen), dann normalisieren (Akronym-Punkte würden sonst falsche Satzgrenzen erzeugen).
Stop/Interrupt
Modul-globales threading.Event:
STOP_REQUESTED = threading.Event()
request_stop() # setzt das Event
clear_stop() # löscht es vor jedem neuen Job
stop_requested() # abfragen
PlaybackWorker und beide Synthesize-Funktionen prüfen das Event an Chunk-Grenzen. Ein laufendes model.generate() kann nicht mid-call abgebrochen werden (Python-Thread-Grenzen) — der Abbruch greift am nächsten Chunk.
Text normalization (preprocess_tts_text)
- Pronunciation dict (vor Akronym-Expansion, damit Eigennamen zuerst greifen)
- Unit normalization (120 km/h → "120 Kilometer pro Stunde")
- Time normalization (14:58 → "vierzehn Uhr achtundfünfzig")
- Year normalization (2026 → "zweitausendsechsundzwanzig")
- Acronym spelling (ARD → "Ah Er De";
NON_SPELLED_ACRONYMSausgenommen)
DEFAULT_PRONUNCIATION_DE enthält eingebaute deutsche Lautschrift-Näherungen (z. B. Xi → "Schi").
Text chunking
Drei Modi (CLI-Flags):
- sentence_mode (default):
split_into_sentences()— ein Satz pro TTS-Call, geringste Latenz - conversation_mode:
split_for_conversation()— erster Chunk klein (--first-chunk-len, default 80), Rest bis--len(400) - plain:
split_long_text()— absatzbasiertes Chunking bis--len
force_split_sentence sucht bei Überlänge erst vorwärts zum nächsten Wortende — kein Schneiden mitten im Wort.
Model loading (load_model)
--lang en→ChatterboxTTS(mono, immer verfügbar)- Andere Sprachen →
ChatterboxMultilingualTTS(HAS_MULTILINGUAL-Flag bewacht Import) --t3-model v3(default) oderv2wählt den multilingualen T3-Checkpoint- Modelle werden in
~/.cache/huggingface/gecacht (~2–3 GB) - Kritisch:
attn_implementation = "eager"wird beim Import erzwungen — SDPA gibtNone-Attention-Weights zurück und bricht denAlignmentStreamAnalyzer-Hook
Audio output (PlaybackWorker)
sounddevice.OutputStreammit Callback bei 48 kHz (PipeWire/PulseAudio-Standard)- Interner Producer-Thread: Torch-Tensoren →
CALLBACK_BLOCK-große (2048 Samples) numpy-Arrays --speed != 1.0: pyrubberband R3-Engine (--fine) streckt Zeit ohne Pitch-Änderung, dann Resampling viatorchaudio.functional.resample(chunk, model_sr, 48000)PlaybackWorker.stop()schicktNone-Sentinel in die Queue und jointed den Thread
Two synthesis paths
synthesize_non_streaming: generiert jeden Chunk vollständig, füttert fertige Tensoren inPlaybackWorker, concateniert alle WAVs für--savesynthesize_streaming: ruftmodel.generate_stream()mitchunk_sizeauf; jeder Audio-Sub-Chunk geht direkt inPlaybackWorker; experimentell
HTTP Service (tts_service.py)
- Modell-Cache:
_model_cache: dict[(lang, t3_model), (model, kind, sr)]— einmal laden, halten; Thread-sicher via_model_lock - Job-Queue:
queue.Queue[SpeakJob]mit einzelnem Worker-Thread; verhindert parallelen GPU/Audio-Zugriff SpeakRequest.interrupt: ruftrequest_stop()+_drain_queue()vor dem Einreihen auf- Status:
_current_job,_recent_jobs(max. 20) via_state_lockthread-safe lesbar