2026-05-16 08:56:50 +02:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
|
import importlib.util
|
Aussprache-Wörterbuch: eigenes Modul, Wortgrenzen, gepflegte Liste, Kandidaten-Suche
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>
2026-07-11 16:52:37 +02:00
|
|
|
|
import json
|
2026-05-16 08:56:50 +02:00
|
|
|
|
import queue
|
|
|
|
|
|
import re
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import threading
|
|
|
|
|
|
import time
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
from typing import List, Optional, Tuple
|
|
|
|
|
|
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
# ---------------------------------------------------------------------------
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
# Kooperativer Stop- und Pause-Mechanismus
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
# ---------------------------------------------------------------------------
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
STOP_REQUESTED = threading.Event()
|
|
|
|
|
|
PAUSE_REQUESTED = threading.Event()
|
|
|
|
|
|
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
|
|
|
|
|
|
def request_stop() -> None:
|
|
|
|
|
|
STOP_REQUESTED.set()
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
PAUSE_REQUESTED.clear() # eine laufende Pause beim Stop aufheben
|
|
|
|
|
|
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
|
|
|
|
|
|
def clear_stop() -> None:
|
|
|
|
|
|
STOP_REQUESTED.clear()
|
|
|
|
|
|
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
def stop_requested() -> bool:
|
|
|
|
|
|
return STOP_REQUESTED.is_set()
|
|
|
|
|
|
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
|
|
|
|
|
|
def request_pause() -> None:
|
|
|
|
|
|
PAUSE_REQUESTED.set()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def request_resume() -> None:
|
|
|
|
|
|
PAUSE_REQUESTED.clear()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_paused() -> bool:
|
|
|
|
|
|
return PAUSE_REQUESTED.is_set()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _wait_while_paused(stop_event: Optional[threading.Event] = None) -> bool:
|
|
|
|
|
|
"""Blockiert solange pausiert ist. Gibt True zurück wenn Stop angefordert wurde."""
|
|
|
|
|
|
while PAUSE_REQUESTED.is_set():
|
|
|
|
|
|
if (stop_event and stop_event.is_set()) or STOP_REQUESTED.is_set():
|
|
|
|
|
|
return True
|
|
|
|
|
|
time.sleep(0.05)
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
2026-05-16 08:56:50 +02:00
|
|
|
|
import torch
|
|
|
|
|
|
import torchaudio as ta
|
|
|
|
|
|
|
|
|
|
|
|
# SDPA does not support output_attentions=True (required by AlignmentStreamAnalyzer hook);
|
|
|
|
|
|
# fall back to eager attention so attention weights are returned as tensors, not None.
|
|
|
|
|
|
import chatterbox.models.t3.llama_configs as _llama_cfg
|
|
|
|
|
|
_llama_cfg.LLAMA_520M_CONFIG_DICT["attn_implementation"] = "eager"
|
|
|
|
|
|
|
|
|
|
|
|
from chatterbox.tts import ChatterboxTTS
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
from chatterbox.mtl_tts import ChatterboxMultilingualTTS
|
|
|
|
|
|
HAS_MULTILINGUAL = True
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
except (ImportError, ModuleNotFoundError):
|
2026-05-16 08:56:50 +02:00
|
|
|
|
ChatterboxMultilingualTTS = None
|
|
|
|
|
|
HAS_MULTILINGUAL = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SUPPORTED_LANGS = {
|
|
|
|
|
|
"ar", "da", "de", "el", "en", "es", "fi", "fr", "he", "hi", "it",
|
|
|
|
|
|
"ja", "ko", "ms", "nl", "no", "pl", "pt", "ru", "sv", "sw", "tr", "zh"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SENTENCE_END_RE = re.compile(
|
|
|
|
|
|
r'.+?(?:'
|
|
|
|
|
|
r'\.\.\.|…|'
|
|
|
|
|
|
r'[!?¡¿]+|'
|
|
|
|
|
|
r'[!?。]+|'
|
|
|
|
|
|
r'‽|'
|
|
|
|
|
|
r'(?<!\d)\.' # Punkt, aber NICHT nach einer Ziffer (kein Ordinalzahl-Split)
|
|
|
|
|
|
r')(?=\s+|$)',
|
|
|
|
|
|
re.DOTALL
|
|
|
|
|
|
)
|
|
|
|
|
|
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
# Abkürzungen, deren abschließender Punkt KEIN Satzende ist.
|
|
|
|
|
|
# Punkte in gematchten Mustern werden in split_into_sentences() temporär maskiert.
|
|
|
|
|
|
_ABBREV_MASK_RE = re.compile(
|
|
|
|
|
|
r'\b(?:'
|
|
|
|
|
|
r'z\.B|d\.h|u\.a|z\.T|u\.U|s\.o|s\.u|m\.E|i\.d\.R' # zweiteilige Konnektive
|
|
|
|
|
|
r'|ggf|vgl|etc|ca|usw|bzw|sog|inkl|exkl|bzgl|zzgl' # einsilbige Kürzel
|
|
|
|
|
|
r'|Dr|Prof|Hr|Fr|Hrsg|Dipl|Ing' # Titel
|
|
|
|
|
|
r'|Abs|Nr|Art|Bd|Abb|Kap|Mrd|Mio|Std|Tel|Str' # Fachbegriffe
|
|
|
|
|
|
r')\.'
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-16 08:56:50 +02:00
|
|
|
|
NON_SPELLED_ACRONYMS = {
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
# Internationale Organisationen / Eigennamen (werden als Wort gesprochen)
|
|
|
|
|
|
"NATO", "NASA", "UNESCO", "OPEC", "IAEA", "UNICEF",
|
|
|
|
|
|
# Tech-Akronyme, die buchstabenweise ausgesprochen werden sollen,
|
|
|
|
|
|
# aber mit deutschen Buchstabennamen falsch klingen würden → daher hier ausnehmen,
|
|
|
|
|
|
# damit sie als lateinische Buchstaben buchstabiert werden (via period_space-Modus)
|
|
|
|
|
|
"USB", "SSD", "RAM", "CPU", "GPU", "URL", "API", "PDF", "LAN", "WLAN",
|
|
|
|
|
|
"HTML", "HTTP", "HTTPS", "JSON", "SQL", "VPN", "SSH", "FTP",
|
|
|
|
|
|
"CEO", "CFO", "CTO", "COO",
|
2026-05-16 08:56:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GERMAN_LETTER_NAMES = {
|
|
|
|
|
|
'A': 'Ah', 'B': 'Be', 'C': 'Tse', 'D': 'De', 'E': 'E',
|
|
|
|
|
|
'F': 'Ef', 'G': 'Ge', 'H': 'Ha', 'I': 'I', 'J': 'Jot',
|
|
|
|
|
|
'K': 'Ka', 'L': 'El', 'M': 'Em', 'N': 'En', 'O': 'O',
|
|
|
|
|
|
'P': 'Pe', 'Q': 'Ku', 'R': 'Er', 'S': 'Es', 'T': 'Te',
|
|
|
|
|
|
'U': 'U', 'V': 'Fau', 'W': 'We', 'X': 'Iks', 'Y': 'Ypsilon',
|
|
|
|
|
|
'Z': 'Tset',
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Trennlinien wie "--- Ende ---", "===", "---" filtern
|
|
|
|
|
|
# Matcht: reine Strichlinien ODER "---Wort---"-Muster mit kurzem Inhalt (<= 20 Zeichen)
|
|
|
|
|
|
SEPARATOR_LINE_RE = re.compile(r'^\s*-{2,}\s*[\w\s]{0,20}\s*-{2,}\s*$|^\s*[=_-]{3,}\s*$')
|
|
|
|
|
|
|
|
|
|
|
|
UPPER_ACRONYM_RE = re.compile(r'\b[A-ZÄÖÜ]{2,}(?:[A-ZÄÖÜ0-9]*[A-ZÄÖÜ])?\b')
|
|
|
|
|
|
# Akronym direkt vor Bindestrich + Wort: "US-Präsident", "NATO-Mitglied"
|
|
|
|
|
|
ACRONYM_COMPOUND_RE = re.compile(r'\b([A-ZÄÖÜ]{2,}(?:[A-ZÄÖÜ0-9]*[A-ZÄÖÜ])?)-(?=[A-ZÄÖÜa-zäöü])')
|
|
|
|
|
|
|
|
|
|
|
|
# Unterstützt:
|
|
|
|
|
|
# - 14:58
|
|
|
|
|
|
# - 14.58
|
|
|
|
|
|
# - 14:58 Uhr
|
|
|
|
|
|
# - 14.58 Uhr
|
|
|
|
|
|
TIME_RE = re.compile(r'\b([01]?\d|2[0-3])([:.])([0-5]\d)(?:\s*Uhr)?\b', re.IGNORECASE)
|
|
|
|
|
|
|
2026-06-03 23:45:03 +02:00
|
|
|
|
# Datum DD.MM.YYYY — muss VOR TIME_RE verarbeitet werden (sonst matcht TIME_RE "03.06" als Uhrzeit)
|
|
|
|
|
|
DATE_RE = re.compile(
|
|
|
|
|
|
r'\b(0?[1-9]|[12]\d|3[01])\.(0?[1-9]|1[012])\.((?:19|20|21)\d{2})\b'
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-16 08:56:50 +02:00
|
|
|
|
# Vierstellige Jahreszahlen
|
|
|
|
|
|
YEAR_RE = re.compile(r'\b(19\d{2}|20\d{2}|21\d{2})\b')
|
|
|
|
|
|
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
# Einfache deutsche Einheiten (absteigende Länge wird in normalize_units() sichergestellt)
|
2026-05-16 08:56:50 +02:00
|
|
|
|
UNIT_REPLACEMENTS = {
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
# Geschwindigkeit
|
2026-05-16 08:56:50 +02:00
|
|
|
|
"km/h": "Kilometer pro Stunde",
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
"m/s": "Meter pro Sekunde",
|
|
|
|
|
|
"rpm": "Umdrehungen pro Minute",
|
|
|
|
|
|
# Länge
|
2026-05-16 08:56:50 +02:00
|
|
|
|
"km": "Kilometer",
|
|
|
|
|
|
"cm": "Zentimeter",
|
|
|
|
|
|
"mm": "Millimeter",
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
"m": "Meter",
|
|
|
|
|
|
# Fläche / Volumen
|
|
|
|
|
|
"cm²": "Quadratzentimeter",
|
|
|
|
|
|
"m²": "Quadratmeter",
|
|
|
|
|
|
"m³": "Kubikmeter",
|
|
|
|
|
|
# Masse
|
2026-05-16 08:56:50 +02:00
|
|
|
|
"kg": "Kilogramm",
|
|
|
|
|
|
"mg": "Milligramm",
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
"g": "Gramm",
|
|
|
|
|
|
# Temperatur
|
|
|
|
|
|
"°C": "Grad Celsius",
|
|
|
|
|
|
"°F": "Grad Fahrenheit",
|
|
|
|
|
|
# Elektrik / Energie
|
|
|
|
|
|
"kWh": "Kilowattstunde",
|
|
|
|
|
|
"kW": "Kilowatt",
|
|
|
|
|
|
"W": "Watt",
|
|
|
|
|
|
"V": "Volt",
|
|
|
|
|
|
"A": "Ampere",
|
|
|
|
|
|
"J": "Joule",
|
|
|
|
|
|
# Frequenz
|
2026-05-16 08:56:50 +02:00
|
|
|
|
"GHz": "Gigahertz",
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
"MHz": "Megahertz",
|
|
|
|
|
|
"kHz": "Kilohertz",
|
|
|
|
|
|
"Hz": "Hertz",
|
|
|
|
|
|
# Druck
|
|
|
|
|
|
"kPa": "Kilopascal",
|
|
|
|
|
|
"bar": "bar",
|
|
|
|
|
|
# Datenspeicher
|
|
|
|
|
|
"PB": "Petabyte",
|
|
|
|
|
|
"TB": "Terabyte",
|
|
|
|
|
|
"GB": "Gigabyte",
|
|
|
|
|
|
"Mb": "Megabyte",
|
|
|
|
|
|
"Kb": "Kilobyte",
|
|
|
|
|
|
# Sonstiges
|
2026-05-16 08:56:50 +02:00
|
|
|
|
"€": "Euro",
|
|
|
|
|
|
"$": "Dollar",
|
|
|
|
|
|
"%": "Prozent",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Aussprache-Wörterbuch: eigenes Modul, Wortgrenzen, gepflegte Liste, Kandidaten-Suche
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>
2026-07-11 16:52:37 +02:00
|
|
|
|
# Aussprache-Wörterbuch: liegt in pronunciation.py (ohne torch-Abhängigkeit, damit
|
|
|
|
|
|
# es auch schlanke Werkzeuge und fremde Anwendungen nutzen können). Die Namen werden
|
|
|
|
|
|
# hier re-exportiert, damit bestehende Importe von chatterbox_cli_v4 weiter greifen.
|
|
|
|
|
|
from pronunciation import ( # noqa: E402
|
|
|
|
|
|
DEFAULT_PRONUNCIATION_DE,
|
|
|
|
|
|
PRONUNCIATION_ENV,
|
|
|
|
|
|
apply_pronunciation_dict,
|
|
|
|
|
|
load_pronunciation_dict,
|
|
|
|
|
|
)
|
2026-05-16 08:56:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
import unicodedata as _unicodedata
|
|
|
|
|
|
|
|
|
|
|
|
# Markdown-Muster für strip_markdown()
|
|
|
|
|
|
_MD_CODE_BLOCK = re.compile(r'```[\s\S]*?```')
|
|
|
|
|
|
_MD_INLINE_CODE = re.compile(r'`([^`\n]+)`')
|
|
|
|
|
|
_MD_BOLD_ITALIC = re.compile(r'[*_]{1,3}([^*_\n]+)[*_]{1,3}')
|
|
|
|
|
|
_MD_HEADING = re.compile(r'^#{1,6}\s+', re.MULTILINE)
|
|
|
|
|
|
_MD_LIST_ITEM = re.compile(r'^\s*[-*+]\s+', re.MULTILINE)
|
|
|
|
|
|
_MD_LINK = re.compile(r'\[([^\]]+)\]\([^\)]+\)')
|
|
|
|
|
|
_MD_IMAGE = re.compile(r'!\[([^\]]*)\]\([^\)]+\)')
|
|
|
|
|
|
_MD_BLOCKQUOTE = re.compile(r'^\s*>\s?', re.MULTILINE)
|
|
|
|
|
|
_MD_HR = re.compile(r'^\s*[-*_]{3,}\s*$', re.MULTILINE)
|
|
|
|
|
|
|
|
|
|
|
|
# Unicode-Kategorien, die Emojis und nicht druckbare Symbole abdecken
|
|
|
|
|
|
_EMOJI_CATEGORIES = frozenset({"So", "Cn", "Co"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def strip_markdown(text: str) -> str:
|
|
|
|
|
|
"""Entfernt Markdown-Formatierung und gibt lesbaren Klartext zurück."""
|
|
|
|
|
|
text = _MD_CODE_BLOCK.sub('', text) # ```...``` komplett entfernen
|
|
|
|
|
|
text = _MD_INLINE_CODE.sub(r'\1', text) # `code` → code
|
|
|
|
|
|
text = _MD_IMAGE.sub(r'\1', text) #  → alt
|
|
|
|
|
|
text = _MD_LINK.sub(r'\1', text) # [text](url) → text
|
|
|
|
|
|
text = _MD_BOLD_ITALIC.sub(r'\1', text) # **fett** / *kursiv* → Inhalt
|
|
|
|
|
|
text = _MD_HEADING.sub('', text) # # Überschrift → Überschrift
|
|
|
|
|
|
text = _MD_BLOCKQUOTE.sub('', text) # > Zitat → Zitat
|
|
|
|
|
|
text = _MD_LIST_ITEM.sub('', text) # - Punkt → Punkt
|
|
|
|
|
|
text = _MD_HR.sub('', text) # --- / *** komplett entfernen
|
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-16 08:56:50 +02:00
|
|
|
|
def clean_raw_text(text: str) -> str:
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
"""Unsichtbare Steuerzeichen und Emojis entfernen, die Splitting oder TTS stoeren."""
|
2026-05-16 08:56:50 +02:00
|
|
|
|
for ch in ('', '', '', ''):
|
|
|
|
|
|
text = text.replace(ch, '')
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
# Emojis und nicht-druckbare Sondersymbole entfernen
|
|
|
|
|
|
text = ''.join(
|
|
|
|
|
|
ch for ch in text
|
|
|
|
|
|
if _unicodedata.category(ch) not in _EMOJI_CATEGORIES
|
|
|
|
|
|
)
|
2026-05-16 08:56:50 +02:00
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
# Regex für Sprachmarkierungen [xx]...[/xx] im Text
|
|
|
|
|
|
_LANG_SPAN_RE = re.compile(r'\[([a-z]{2})\](.*?)\[/\1\]', re.DOTALL)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def extract_language_spans(text: str, default_lang: str) -> List[Tuple[str, str]]:
|
|
|
|
|
|
"""Zerlegt Text mit [xx]...[/xx]-Markierungen in (segment, lang)-Tupel.
|
|
|
|
|
|
|
|
|
|
|
|
Beispiel:
|
|
|
|
|
|
"Das [en]Machine Learning[/en] Modell."
|
|
|
|
|
|
→ [("Das", "de"), ("Machine Learning", "en"), ("Modell.", "de")]
|
|
|
|
|
|
|
|
|
|
|
|
Ohne Markierungen wird [(text, default_lang)] zurückgegeben.
|
|
|
|
|
|
"""
|
|
|
|
|
|
segments: List[Tuple[str, str]] = []
|
|
|
|
|
|
last_end = 0
|
|
|
|
|
|
for m in _LANG_SPAN_RE.finditer(text):
|
|
|
|
|
|
before = text[last_end:m.start()]
|
|
|
|
|
|
if before.strip():
|
|
|
|
|
|
segments.append((before.strip(), default_lang))
|
|
|
|
|
|
lang_tag = m.group(1)
|
|
|
|
|
|
content = m.group(2).strip()
|
|
|
|
|
|
if content and lang_tag in SUPPORTED_LANGS:
|
|
|
|
|
|
segments.append((content, lang_tag))
|
|
|
|
|
|
last_end = m.end()
|
|
|
|
|
|
tail = text[last_end:].strip()
|
|
|
|
|
|
if tail:
|
|
|
|
|
|
segments.append((tail, default_lang))
|
|
|
|
|
|
return segments if segments else [(text, default_lang)]
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-16 08:56:50 +02:00
|
|
|
|
def has_module(name: str) -> bool:
|
|
|
|
|
|
return importlib.util.find_spec(name) is not None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_device(explicit_device: Optional[str] = None) -> str:
|
|
|
|
|
|
if explicit_device:
|
|
|
|
|
|
if explicit_device.startswith("cuda") and not torch.cuda.is_available():
|
|
|
|
|
|
raise RuntimeError("CUDA angefordert, aber keine CUDA-GPU verfügbar.")
|
|
|
|
|
|
if explicit_device.startswith("cuda"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
idx = int(explicit_device.split(":")[1]) if ":" in explicit_device else 0
|
|
|
|
|
|
except (IndexError, ValueError):
|
|
|
|
|
|
idx = 0
|
|
|
|
|
|
torch.cuda.set_device(idx)
|
|
|
|
|
|
return explicit_device
|
|
|
|
|
|
|
|
|
|
|
|
if torch.cuda.is_available():
|
|
|
|
|
|
torch.cuda.set_device(0)
|
|
|
|
|
|
return "cuda:0"
|
|
|
|
|
|
|
|
|
|
|
|
return "cpu"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def number_to_words_de(n: int) -> str:
|
|
|
|
|
|
ones = {
|
|
|
|
|
|
0: "null", 1: "eins", 2: "zwei", 3: "drei", 4: "vier", 5: "fünf",
|
|
|
|
|
|
6: "sechs", 7: "sieben", 8: "acht", 9: "neun", 10: "zehn",
|
|
|
|
|
|
11: "elf", 12: "zwölf", 13: "dreizehn", 14: "vierzehn",
|
|
|
|
|
|
15: "fünfzehn", 16: "sechzehn", 17: "siebzehn", 18: "achtzehn",
|
|
|
|
|
|
19: "neunzehn"
|
|
|
|
|
|
}
|
|
|
|
|
|
tens = {
|
|
|
|
|
|
20: "zwanzig", 30: "dreißig", 40: "vierzig", 50: "fünfzig",
|
|
|
|
|
|
60: "sechzig", 70: "siebzig", 80: "achtzig", 90: "neunzig"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if n < 20:
|
|
|
|
|
|
return ones[n]
|
|
|
|
|
|
|
|
|
|
|
|
if n < 100:
|
|
|
|
|
|
t = (n // 10) * 10
|
|
|
|
|
|
o = n % 10
|
|
|
|
|
|
if o == 0:
|
|
|
|
|
|
return tens[t]
|
|
|
|
|
|
one_prefix = "ein" if o == 1 else ones[o]
|
|
|
|
|
|
return f"{one_prefix}und{tens[t]}"
|
|
|
|
|
|
|
|
|
|
|
|
if n < 1000:
|
|
|
|
|
|
h = n // 100
|
|
|
|
|
|
r = n % 100
|
|
|
|
|
|
prefix = "einhundert" if h == 1 else f"{ones[h]}hundert"
|
|
|
|
|
|
return prefix if r == 0 else f"{prefix}{number_to_words_de(r)}"
|
|
|
|
|
|
|
|
|
|
|
|
if n < 1000000:
|
|
|
|
|
|
th = n // 1000
|
|
|
|
|
|
r = n % 1000
|
|
|
|
|
|
prefix = "eintausend" if th == 1 else f"{number_to_words_de(th)}tausend"
|
|
|
|
|
|
return prefix if r == 0 else f"{prefix}{number_to_words_de(r)}"
|
|
|
|
|
|
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
if n < 1_000_000_000:
|
|
|
|
|
|
m = n // 1_000_000
|
|
|
|
|
|
r = n % 1_000_000
|
|
|
|
|
|
prefix = "eine Million" if m == 1 else f"{number_to_words_de(m)} Millionen"
|
|
|
|
|
|
return prefix if r == 0 else f"{prefix} {number_to_words_de(r)}"
|
|
|
|
|
|
|
|
|
|
|
|
if n < 1_000_000_000_000:
|
|
|
|
|
|
b = n // 1_000_000_000
|
|
|
|
|
|
r = n % 1_000_000_000
|
|
|
|
|
|
prefix = "eine Milliarde" if b == 1 else f"{number_to_words_de(b)} Milliarden"
|
|
|
|
|
|
return prefix if r == 0 else f"{prefix} {number_to_words_de(r)}"
|
|
|
|
|
|
|
2026-05-16 08:56:50 +02:00
|
|
|
|
return str(n)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def number_to_words_en(n: int) -> str:
|
|
|
|
|
|
ones = {
|
|
|
|
|
|
0: "zero", 1: "one", 2: "two", 3: "three", 4: "four", 5: "five",
|
|
|
|
|
|
6: "six", 7: "seven", 8: "eight", 9: "nine", 10: "ten",
|
|
|
|
|
|
11: "eleven", 12: "twelve", 13: "thirteen", 14: "fourteen",
|
|
|
|
|
|
15: "fifteen", 16: "sixteen", 17: "seventeen", 18: "eighteen",
|
|
|
|
|
|
19: "nineteen"
|
|
|
|
|
|
}
|
|
|
|
|
|
tens = {
|
|
|
|
|
|
20: "twenty", 30: "thirty", 40: "forty", 50: "fifty",
|
|
|
|
|
|
60: "sixty", 70: "seventy", 80: "eighty", 90: "ninety"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if n < 20:
|
|
|
|
|
|
return ones[n]
|
|
|
|
|
|
|
|
|
|
|
|
if n < 100:
|
|
|
|
|
|
t = (n // 10) * 10
|
|
|
|
|
|
o = n % 10
|
|
|
|
|
|
return tens[t] if o == 0 else f"{tens[t]}-{ones[o]}"
|
|
|
|
|
|
|
|
|
|
|
|
if n < 1000:
|
|
|
|
|
|
h = n // 100
|
|
|
|
|
|
r = n % 100
|
|
|
|
|
|
prefix = f"{ones[h]} hundred"
|
|
|
|
|
|
return prefix if r == 0 else f"{prefix} {number_to_words_en(r)}"
|
|
|
|
|
|
|
|
|
|
|
|
if n < 1000000:
|
|
|
|
|
|
th = n // 1000
|
|
|
|
|
|
r = n % 1000
|
|
|
|
|
|
prefix = f"{number_to_words_en(th)} thousand"
|
|
|
|
|
|
return prefix if r == 0 else f"{prefix} {number_to_words_en(r)}"
|
|
|
|
|
|
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
if n < 1_000_000_000:
|
|
|
|
|
|
m = n // 1_000_000
|
|
|
|
|
|
r = n % 1_000_000
|
|
|
|
|
|
prefix = f"{number_to_words_en(m)} million"
|
|
|
|
|
|
return prefix if r == 0 else f"{prefix} {number_to_words_en(r)}"
|
|
|
|
|
|
|
|
|
|
|
|
if n < 1_000_000_000_000:
|
|
|
|
|
|
b = n // 1_000_000_000
|
|
|
|
|
|
r = n % 1_000_000_000
|
|
|
|
|
|
prefix = f"{number_to_words_en(b)} billion"
|
|
|
|
|
|
return prefix if r == 0 else f"{prefix} {number_to_words_en(r)}"
|
|
|
|
|
|
|
2026-05-16 08:56:50 +02:00
|
|
|
|
return str(n)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def year_to_words_de(year: int) -> str:
|
|
|
|
|
|
if year < 1000 or year > 9999:
|
|
|
|
|
|
return str(year)
|
|
|
|
|
|
|
|
|
|
|
|
if year == 2000:
|
|
|
|
|
|
return "zweitausend"
|
|
|
|
|
|
|
|
|
|
|
|
if 2001 <= year <= 2099:
|
|
|
|
|
|
return f"zweitausend{number_to_words_de(year - 2000)}"
|
|
|
|
|
|
|
|
|
|
|
|
return number_to_words_de(year)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def year_to_words_en(year: int) -> str:
|
|
|
|
|
|
if year < 1000 or year > 9999:
|
|
|
|
|
|
return str(year)
|
|
|
|
|
|
|
|
|
|
|
|
if 2000 <= year <= 2009:
|
|
|
|
|
|
if year == 2000:
|
|
|
|
|
|
return "two thousand"
|
|
|
|
|
|
return f"two thousand {number_to_words_en(year - 2000)}"
|
|
|
|
|
|
|
|
|
|
|
|
if 2010 <= year <= 2099:
|
|
|
|
|
|
last_two = year % 100
|
|
|
|
|
|
return f"twenty {number_to_words_en(last_two)}"
|
|
|
|
|
|
|
|
|
|
|
|
first_two = year // 100
|
|
|
|
|
|
last_two = year % 100
|
|
|
|
|
|
if last_two == 0:
|
|
|
|
|
|
return f"{number_to_words_en(first_two)} hundred"
|
|
|
|
|
|
return f"{number_to_words_en(first_two)} {number_to_words_en(last_two)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-03 23:45:03 +02:00
|
|
|
|
_ORDINAL_IRREGULAR_DE = {
|
|
|
|
|
|
1: "Erster", 2: "Zweiter", 3: "Dritter", 4: "Vierter",
|
|
|
|
|
|
5: "Fünfter", 6: "Sechster", 7: "Siebter", 8: "Achter",
|
|
|
|
|
|
9: "Neunter", 10: "Zehnter", 11: "Elfter", 12: "Zwölfter",
|
|
|
|
|
|
13: "Dreizehnter", 14: "Vierzehnter", 15: "Fünfzehnter",
|
|
|
|
|
|
16: "Sechzehnter", 17: "Siebzehnter", 18: "Achtzehnter",
|
|
|
|
|
|
19: "Neunzehnter",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ordinal_de(n: int) -> str:
|
|
|
|
|
|
"""Nominativer maskuliner Ordinal (1 → 'Erster', 20 → 'Zwanzigster')."""
|
|
|
|
|
|
if n in _ORDINAL_IRREGULAR_DE:
|
|
|
|
|
|
return _ORDINAL_IRREGULAR_DE[n]
|
|
|
|
|
|
return number_to_words_de(n).capitalize() + "ster"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def year_for_date_de(year: int) -> str:
|
|
|
|
|
|
"""Jahreszahl in Datums-Aussprache: 2026 → 'Zwanzigsechsundzwanzig'."""
|
|
|
|
|
|
if 2010 <= year <= 2099:
|
|
|
|
|
|
return "Zwanzig" + number_to_words_de(year % 100)
|
|
|
|
|
|
if year == 2000:
|
|
|
|
|
|
return "Zweitausend"
|
|
|
|
|
|
if 2001 <= year <= 2009:
|
|
|
|
|
|
return "Zweitausend" + number_to_words_de(year - 2000)
|
|
|
|
|
|
return year_to_words_de(year).capitalize()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def normalize_dates(text: str, lang: str) -> str:
|
|
|
|
|
|
if lang != "de":
|
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
def repl(m: re.Match) -> str:
|
|
|
|
|
|
day = int(m.group(1))
|
|
|
|
|
|
month = int(m.group(2))
|
|
|
|
|
|
year = int(m.group(3))
|
|
|
|
|
|
return f"{ordinal_de(day)} {ordinal_de(month)} {year_for_date_de(year)}"
|
|
|
|
|
|
|
|
|
|
|
|
return DATE_RE.sub(repl, text)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-16 08:56:50 +02:00
|
|
|
|
def spell_out_acronym(token: str, mode: str = "period_space") -> str:
|
|
|
|
|
|
chars = list(token)
|
|
|
|
|
|
|
|
|
|
|
|
if mode == "german":
|
|
|
|
|
|
return " ".join(GERMAN_LETTER_NAMES.get(c, c) for c in chars)
|
|
|
|
|
|
|
|
|
|
|
|
if mode == "space":
|
|
|
|
|
|
return " ".join(chars)
|
|
|
|
|
|
|
|
|
|
|
|
if mode == "period":
|
|
|
|
|
|
return ".".join(chars) + "."
|
|
|
|
|
|
|
|
|
|
|
|
if mode == "comma":
|
|
|
|
|
|
return ", ".join(chars)
|
|
|
|
|
|
|
|
|
|
|
|
if mode == "period_space":
|
|
|
|
|
|
return ". ".join(chars) + "."
|
|
|
|
|
|
|
|
|
|
|
|
raise ValueError(f"Unbekannter mode: {mode}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def normalize_units(text: str, lang: str) -> str:
|
|
|
|
|
|
if lang != "de":
|
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
for unit, expanded in sorted(UNIT_REPLACEMENTS.items(), key=lambda x: len(x[0]), reverse=True):
|
|
|
|
|
|
text = re.sub(rf'(?<=\d)\s*{re.escape(unit)}\b', f" {expanded}", text)
|
|
|
|
|
|
|
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def normalize_times(text: str, lang: str) -> str:
|
|
|
|
|
|
def repl(match: re.Match) -> str:
|
|
|
|
|
|
hh = int(match.group(1))
|
|
|
|
|
|
mm = int(match.group(3))
|
|
|
|
|
|
|
|
|
|
|
|
if lang == "de":
|
|
|
|
|
|
if mm == 0:
|
|
|
|
|
|
return f"{number_to_words_de(hh)} Uhr"
|
|
|
|
|
|
return f"{number_to_words_de(hh)} Uhr {number_to_words_de(mm)}"
|
|
|
|
|
|
|
|
|
|
|
|
if lang == "en":
|
|
|
|
|
|
if hh == 0 and mm == 0:
|
|
|
|
|
|
return "twelve midnight"
|
|
|
|
|
|
if hh == 12 and mm == 0:
|
|
|
|
|
|
return "twelve noon"
|
|
|
|
|
|
|
|
|
|
|
|
hour12 = hh % 12
|
|
|
|
|
|
if hour12 == 0:
|
|
|
|
|
|
hour12 = 12
|
|
|
|
|
|
suffix = "a m" if hh < 12 else "p m"
|
|
|
|
|
|
|
|
|
|
|
|
if mm == 0:
|
|
|
|
|
|
return f"{number_to_words_en(hour12)} {suffix}"
|
|
|
|
|
|
if mm < 10:
|
|
|
|
|
|
return f"{number_to_words_en(hour12)} oh {number_to_words_en(mm)} {suffix}"
|
|
|
|
|
|
return f"{number_to_words_en(hour12)} {number_to_words_en(mm)} {suffix}"
|
|
|
|
|
|
|
|
|
|
|
|
return match.group(0)
|
|
|
|
|
|
|
|
|
|
|
|
return TIME_RE.sub(repl, text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def normalize_years(text: str, lang: str) -> str:
|
|
|
|
|
|
def repl(match: re.Match) -> str:
|
|
|
|
|
|
year = int(match.group(1))
|
|
|
|
|
|
|
|
|
|
|
|
if lang == "de":
|
|
|
|
|
|
return year_to_words_de(year)
|
|
|
|
|
|
|
|
|
|
|
|
if lang == "en":
|
|
|
|
|
|
return year_to_words_en(year)
|
|
|
|
|
|
|
|
|
|
|
|
return match.group(0)
|
|
|
|
|
|
|
|
|
|
|
|
return YEAR_RE.sub(repl, text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def preprocess_tts_text(
|
|
|
|
|
|
text: str,
|
|
|
|
|
|
lang: str,
|
|
|
|
|
|
spell_uppercase_acronyms: bool = True,
|
|
|
|
|
|
acronym_mode: Optional[str] = None, # None = auto: 'german' bei de, sonst 'period_space'
|
2026-06-03 23:45:03 +02:00
|
|
|
|
normalize_date_values: bool = True,
|
2026-05-16 08:56:50 +02:00
|
|
|
|
normalize_time_values: bool = True,
|
|
|
|
|
|
normalize_year_values: bool = True,
|
|
|
|
|
|
normalize_units_values: bool = True,
|
|
|
|
|
|
pronunciation_dict: Optional[dict] = None,
|
|
|
|
|
|
) -> str:
|
|
|
|
|
|
if acronym_mode is None:
|
|
|
|
|
|
acronym_mode = "german" if lang == "de" else "period_space"
|
|
|
|
|
|
|
Aussprache-Wörterbuch: eigenes Modul, Wortgrenzen, gepflegte Liste, Kandidaten-Suche
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>
2026-07-11 16:52:37 +02:00
|
|
|
|
# 1. Aussprache-Wörterbuch zuerst (vor Akronym-Expansion, damit Eigennamen greifen).
|
|
|
|
|
|
# load_pronunciation_dict() fuegt eingebaute, mitgelieferte, benutzereigene und
|
|
|
|
|
|
# per Umgebungsvariable gesetzte Eintraege zusammen; `pronunciation_dict` (vom
|
|
|
|
|
|
# Aufrufer) hat Vorrang. In EINEM Durchgang ersetzen, damit eine Ersetzung nicht
|
|
|
|
|
|
# erneut von einem anderen Eintrag getroffen wird.
|
|
|
|
|
|
text = apply_pronunciation_dict(text, load_pronunciation_dict(lang, pronunciation_dict))
|
2026-05-16 08:56:50 +02:00
|
|
|
|
|
|
|
|
|
|
if normalize_units_values:
|
|
|
|
|
|
text = normalize_units(text, lang)
|
|
|
|
|
|
|
2026-06-03 23:45:03 +02:00
|
|
|
|
# Datum VOR Uhrzeit normalisieren — sonst matcht TIME_RE "03.06" fälschlicherweise
|
|
|
|
|
|
if normalize_date_values:
|
|
|
|
|
|
text = normalize_dates(text, lang)
|
|
|
|
|
|
|
2026-05-16 08:56:50 +02:00
|
|
|
|
if normalize_time_values:
|
|
|
|
|
|
text = normalize_times(text, lang)
|
|
|
|
|
|
|
|
|
|
|
|
if normalize_year_values:
|
|
|
|
|
|
text = normalize_years(text, lang)
|
|
|
|
|
|
|
|
|
|
|
|
if spell_uppercase_acronyms:
|
|
|
|
|
|
def repl_compound(match: re.Match) -> str:
|
|
|
|
|
|
acr = match.group(1)
|
|
|
|
|
|
if acr in NON_SPELLED_ACRONYMS:
|
|
|
|
|
|
return acr + " "
|
|
|
|
|
|
return spell_out_acronym(acr, mode=acronym_mode) + " "
|
|
|
|
|
|
|
|
|
|
|
|
def repl(match: re.Match) -> str:
|
|
|
|
|
|
token = match.group(0)
|
|
|
|
|
|
if token in NON_SPELLED_ACRONYMS:
|
|
|
|
|
|
return token
|
|
|
|
|
|
return spell_out_acronym(token, mode=acronym_mode)
|
|
|
|
|
|
|
|
|
|
|
|
# Compound zuerst: "US-Präsident" → "U Es Präsident" (Bindestrich weg)
|
|
|
|
|
|
text = ACRONYM_COMPOUND_RE.sub(repl_compound, text)
|
|
|
|
|
|
# Dann verbleibende Akronyme buchstabieren
|
|
|
|
|
|
text = UPPER_ACRONYM_RE.sub(repl, text)
|
|
|
|
|
|
|
|
|
|
|
|
text = re.sub(r'\s+', ' ', text).strip()
|
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def split_long_text(text: str, max_len: int = 400) -> List[str]:
|
|
|
|
|
|
chunks = []
|
|
|
|
|
|
current = ""
|
|
|
|
|
|
|
|
|
|
|
|
for part in text.split("\n\n"):
|
|
|
|
|
|
part = part.strip()
|
|
|
|
|
|
if not part:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
sentences = SENTENCE_END_RE.findall(part)
|
|
|
|
|
|
|
|
|
|
|
|
consumed = "".join(sentences).strip()
|
|
|
|
|
|
rest = part[len(consumed):].strip()
|
|
|
|
|
|
if rest:
|
|
|
|
|
|
sentences.append(rest)
|
|
|
|
|
|
|
|
|
|
|
|
for sentence in sentences:
|
|
|
|
|
|
sentence = sentence.strip()
|
|
|
|
|
|
if not sentence:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
if len(sentence) > max_len:
|
|
|
|
|
|
if current:
|
|
|
|
|
|
chunks.append(current.strip())
|
|
|
|
|
|
current = ""
|
|
|
|
|
|
|
|
|
|
|
|
chunks.extend(force_split_sentence(sentence, max_len))
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
if current and len(current) + 1 + len(sentence) > max_len:
|
|
|
|
|
|
chunks.append(current.strip())
|
|
|
|
|
|
current = sentence
|
|
|
|
|
|
else:
|
|
|
|
|
|
current = f"{current} {sentence}".strip() if current else sentence
|
|
|
|
|
|
|
|
|
|
|
|
if current:
|
|
|
|
|
|
chunks.append(current.strip())
|
|
|
|
|
|
current = ""
|
|
|
|
|
|
|
|
|
|
|
|
return chunks
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def split_for_conversation(text: str, first_chunk_len: int = 120, max_len: int = 400) -> List[str]:
|
|
|
|
|
|
base_chunks = split_long_text(text, max_len=max_len)
|
|
|
|
|
|
if not base_chunks:
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
first = base_chunks[0]
|
|
|
|
|
|
if len(first) <= first_chunk_len:
|
|
|
|
|
|
return base_chunks
|
|
|
|
|
|
|
|
|
|
|
|
early = force_split_sentence(first, first_chunk_len)
|
|
|
|
|
|
return early + base_chunks[1:]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def force_split_sentence(text: str, max_len: int) -> List[str]:
|
|
|
|
|
|
text = re.sub(r"\s+", " ", text).strip()
|
|
|
|
|
|
if len(text) <= max_len:
|
|
|
|
|
|
return [text]
|
|
|
|
|
|
|
|
|
|
|
|
parts = []
|
|
|
|
|
|
remaining = text
|
|
|
|
|
|
|
|
|
|
|
|
while len(remaining) > max_len:
|
|
|
|
|
|
split_pos = remaining.rfind(" ", 0, max_len + 1)
|
|
|
|
|
|
if split_pos <= 0:
|
Add HTTP service, MCP adapter, systemd autostart; fix bugs and docs
- 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>
2026-05-16 10:19:00 +02:00
|
|
|
|
# Kein Leerzeichen vor max_len — vorwärts zum nächsten Wortende suchen
|
|
|
|
|
|
next_space = remaining.find(" ", max_len)
|
|
|
|
|
|
split_pos = next_space if next_space != -1 else len(remaining)
|
2026-05-16 08:56:50 +02:00
|
|
|
|
parts.append(remaining[:split_pos].strip())
|
|
|
|
|
|
remaining = remaining[split_pos:].strip()
|
|
|
|
|
|
|
|
|
|
|
|
if remaining:
|
|
|
|
|
|
parts.append(remaining)
|
|
|
|
|
|
|
|
|
|
|
|
return parts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def split_into_sentences(text: str, max_len: int = 200) -> List[str]:
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
# Nummerierte Listenpunkte ("1. ...", "2. ...") als eigene Absätze normalisieren,
|
|
|
|
|
|
# damit sie nicht mit benachbarten Sätzen zusammengefasst werden.
|
|
|
|
|
|
text = re.sub(r'(?m)^(\d+\.\s+)', r'\n\n\1', text)
|
|
|
|
|
|
|
2026-05-16 08:56:50 +02:00
|
|
|
|
result = []
|
|
|
|
|
|
for part in text.split("\n\n"):
|
|
|
|
|
|
part = part.strip()
|
|
|
|
|
|
if not part:
|
|
|
|
|
|
continue
|
|
|
|
|
|
if SEPARATOR_LINE_RE.match(part):
|
|
|
|
|
|
continue
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
# Abkürzungspunkte temporär maskieren, damit sie nicht als Satzenden gelten.
|
|
|
|
|
|
masked = _ABBREV_MASK_RE.sub(lambda m: m.group(0)[:-1] + "\x00", part)
|
|
|
|
|
|
sentences = SENTENCE_END_RE.findall(masked)
|
2026-05-16 08:56:50 +02:00
|
|
|
|
consumed = "".join(sentences).strip()
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
rest = masked[len(consumed):].strip()
|
2026-05-16 08:56:50 +02:00
|
|
|
|
if rest:
|
|
|
|
|
|
sentences.append(rest)
|
|
|
|
|
|
for sentence in sentences:
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
sentence = sentence.replace("\x00", ".").strip()
|
2026-05-16 08:56:50 +02:00
|
|
|
|
if not sentence:
|
|
|
|
|
|
continue
|
|
|
|
|
|
if len(sentence) > max_len:
|
|
|
|
|
|
result.extend(force_split_sentence(sentence, max_len))
|
|
|
|
|
|
else:
|
|
|
|
|
|
result.append(sentence)
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def read_input_text(text_arg: Optional[str], input_path: Optional[str]) -> str:
|
|
|
|
|
|
if text_arg and input_path:
|
|
|
|
|
|
raise ValueError("Bitte entweder --text oder --input angeben, nicht beides.")
|
|
|
|
|
|
if not text_arg and not input_path:
|
|
|
|
|
|
raise ValueError("Bitte --text oder --input angeben.")
|
|
|
|
|
|
|
|
|
|
|
|
if text_arg:
|
|
|
|
|
|
return text_arg.strip()
|
|
|
|
|
|
|
|
|
|
|
|
path = Path(input_path)
|
|
|
|
|
|
if not path.exists():
|
|
|
|
|
|
raise FileNotFoundError(f"Input-Datei nicht gefunden: {path}")
|
|
|
|
|
|
|
|
|
|
|
|
return path.read_text(encoding="utf-8").strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def default_output_path(input_path: Optional[str], lang: str) -> Path:
|
|
|
|
|
|
if input_path:
|
|
|
|
|
|
src = Path(input_path)
|
|
|
|
|
|
return src.with_suffix(f".{lang}.wav")
|
|
|
|
|
|
return Path(f"tts_output.{lang}.wav")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_model(lang: str, device: str, t3_model: Optional[str] = None):
|
|
|
|
|
|
if lang == "en":
|
|
|
|
|
|
model = ChatterboxTTS.from_pretrained(device=device)
|
|
|
|
|
|
return model, "mono", model.sr
|
|
|
|
|
|
|
|
|
|
|
|
if not HAS_MULTILINGUAL:
|
|
|
|
|
|
raise RuntimeError(
|
|
|
|
|
|
"Multilingual-Modell nicht verfügbar. Installiere ein Chatterbox-Paket mit chatterbox.mtl_tts."
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
model = ChatterboxMultilingualTTS.from_pretrained(device=device, t3_model=t3_model)
|
|
|
|
|
|
return model, "multi", model.sr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_chunk(model, model_kind: str, text: str, lang: str, voice_path: Optional[str]):
|
|
|
|
|
|
kwargs = {}
|
|
|
|
|
|
if voice_path:
|
|
|
|
|
|
kwargs["audio_prompt_path"] = voice_path
|
|
|
|
|
|
|
|
|
|
|
|
if model_kind == "mono":
|
|
|
|
|
|
return model.generate(text, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
return model.generate(text, language_id=lang, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_stream_chunk(
|
|
|
|
|
|
model,
|
|
|
|
|
|
model_kind: str,
|
|
|
|
|
|
text: str,
|
|
|
|
|
|
lang: str,
|
|
|
|
|
|
voice_path: Optional[str],
|
|
|
|
|
|
stream_chunk_size: int,
|
|
|
|
|
|
):
|
|
|
|
|
|
kwargs = {
|
|
|
|
|
|
"chunk_size": stream_chunk_size,
|
|
|
|
|
|
"print_metrics": False,
|
|
|
|
|
|
}
|
|
|
|
|
|
if voice_path:
|
|
|
|
|
|
kwargs["audio_prompt_path"] = voice_path
|
|
|
|
|
|
|
|
|
|
|
|
if model_kind == "mono":
|
|
|
|
|
|
return model.generate_stream(text, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
return model.generate_stream(text, language_id=lang, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PlaybackWorker:
|
|
|
|
|
|
PLAYBACK_RATE = 48000 # PipeWire/PulseAudio standard
|
|
|
|
|
|
CALLBACK_BLOCK = 2048 # ~43 ms pro Callback-Block bei 48 kHz
|
|
|
|
|
|
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
def __init__(self, sample_rate: int, device: Optional[str] = "pulse", speed: float = 1.0,
|
|
|
|
|
|
stop_event: Optional[threading.Event] = None):
|
2026-05-16 08:56:50 +02:00
|
|
|
|
self.sample_rate = sample_rate
|
|
|
|
|
|
self.device = device
|
|
|
|
|
|
self.speed = speed
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
self.stop_event = stop_event
|
2026-05-16 08:56:50 +02:00
|
|
|
|
# Eingang: Torch-Tensoren vom TTS-Modell
|
|
|
|
|
|
self.audio_queue: "queue.Queue[Optional[torch.Tensor]]" = queue.Queue()
|
|
|
|
|
|
# Intern: fertig vorbereitete numpy-Blöcke für den Callback
|
|
|
|
|
|
self._block_queue: "queue.Queue" = queue.Queue(maxsize=500)
|
|
|
|
|
|
self._blocks_produced = 0
|
|
|
|
|
|
self._blocks_consumed = 0
|
|
|
|
|
|
self.thread = None
|
|
|
|
|
|
self.error = None
|
|
|
|
|
|
|
|
|
|
|
|
def start(self):
|
|
|
|
|
|
if not has_module("sounddevice"):
|
|
|
|
|
|
raise RuntimeError(
|
|
|
|
|
|
"Für Live-Wiedergabe ist das Modul 'sounddevice' nötig. Installiere z. B. 'pip install sounddevice'."
|
|
|
|
|
|
)
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
if self.device is not None:
|
|
|
|
|
|
import sounddevice as sd
|
|
|
|
|
|
try:
|
|
|
|
|
|
sd.query_devices(self.device)
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
available = [d["name"] for d in sd.query_devices()]
|
|
|
|
|
|
raise RuntimeError(
|
|
|
|
|
|
f"Audio-Gerät nicht gefunden: '{self.device}'. Verfügbare Geräte: {available}"
|
|
|
|
|
|
)
|
2026-05-16 08:56:50 +02:00
|
|
|
|
self.thread = threading.Thread(target=self._run, daemon=True)
|
|
|
|
|
|
self.thread.start()
|
|
|
|
|
|
|
|
|
|
|
|
def _callback(self, outdata, frames, time_info, status):
|
|
|
|
|
|
# Läuft im Audio-Thread: so schnell wie möglich, kein Lock nötig.
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
if (self.stop_event and self.stop_event.is_set()) or PAUSE_REQUESTED.is_set():
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
outdata[:] = 0.0
|
|
|
|
|
|
return
|
2026-05-16 08:56:50 +02:00
|
|
|
|
try:
|
|
|
|
|
|
data = self._block_queue.get_nowait()
|
|
|
|
|
|
outdata[:, 0] = data
|
|
|
|
|
|
self._blocks_consumed += 1
|
|
|
|
|
|
except queue.Empty:
|
|
|
|
|
|
outdata[:] = 0.0 # Stille statt Underrun-Klick
|
|
|
|
|
|
|
|
|
|
|
|
def _produce(self):
|
|
|
|
|
|
"""Wandelt Torch-Tensoren in CALLBACK_BLOCK-große numpy-Arrays um."""
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
|
|
|
|
remainder = np.zeros(0, dtype="float32")
|
|
|
|
|
|
|
|
|
|
|
|
while True:
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
if self.stop_event and self.stop_event.is_set():
|
|
|
|
|
|
break
|
2026-05-16 08:56:50 +02:00
|
|
|
|
item = self.audio_queue.get()
|
|
|
|
|
|
if item is None:
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
chunk = item.detach().cpu()
|
|
|
|
|
|
if chunk.ndim == 2:
|
|
|
|
|
|
chunk = chunk.squeeze(0)
|
|
|
|
|
|
|
|
|
|
|
|
if self.speed != 1.0:
|
|
|
|
|
|
import pyrubberband as pyrb
|
|
|
|
|
|
# R3-Engine (--fine): deutlich weniger Phasiness als R2, besser für Sprache.
|
|
|
|
|
|
# rate < 1.0 = langsamer, rate > 1.0 = schneller; Pitch bleibt gleich.
|
|
|
|
|
|
stretched = pyrb.time_stretch(
|
|
|
|
|
|
chunk.numpy().astype("float64"), self.sample_rate, self.speed,
|
|
|
|
|
|
rbargs={"--fine": ""},
|
|
|
|
|
|
)
|
|
|
|
|
|
chunk = torch.from_numpy(stretched.astype("float32"))
|
|
|
|
|
|
|
|
|
|
|
|
chunk = ta.functional.resample(chunk, self.sample_rate, self.PLAYBACK_RATE)
|
|
|
|
|
|
|
|
|
|
|
|
samples = np.concatenate([remainder, chunk.numpy().astype("float32")])
|
|
|
|
|
|
|
|
|
|
|
|
i = 0
|
|
|
|
|
|
while i + self.CALLBACK_BLOCK <= len(samples):
|
|
|
|
|
|
self._block_queue.put(samples[i : i + self.CALLBACK_BLOCK])
|
|
|
|
|
|
self._blocks_produced += 1
|
|
|
|
|
|
i += self.CALLBACK_BLOCK
|
|
|
|
|
|
remainder = samples[i:]
|
|
|
|
|
|
|
|
|
|
|
|
# Restliche Samples (< CALLBACK_BLOCK) mit Stille auffüllen
|
|
|
|
|
|
if len(remainder) > 0:
|
|
|
|
|
|
block = np.zeros(self.CALLBACK_BLOCK, dtype="float32")
|
|
|
|
|
|
block[: len(remainder)] = remainder
|
|
|
|
|
|
self._block_queue.put(block)
|
|
|
|
|
|
self._blocks_produced += 1
|
|
|
|
|
|
|
|
|
|
|
|
def _run(self):
|
|
|
|
|
|
try:
|
|
|
|
|
|
import sounddevice as sd
|
|
|
|
|
|
|
|
|
|
|
|
producer = threading.Thread(target=self._produce, daemon=True)
|
|
|
|
|
|
producer.start()
|
|
|
|
|
|
|
|
|
|
|
|
with sd.OutputStream(
|
|
|
|
|
|
samplerate=self.PLAYBACK_RATE,
|
|
|
|
|
|
channels=1,
|
|
|
|
|
|
dtype="float32",
|
|
|
|
|
|
device=self.device,
|
|
|
|
|
|
blocksize=self.CALLBACK_BLOCK,
|
|
|
|
|
|
callback=self._callback,
|
|
|
|
|
|
):
|
|
|
|
|
|
producer.join() # alle Tensoren sind zu Blöcken konvertiert
|
|
|
|
|
|
|
|
|
|
|
|
# Warten bis der Callback alle Blöcke abgespielt hat
|
|
|
|
|
|
while self._blocks_consumed < self._blocks_produced:
|
|
|
|
|
|
time.sleep(0.02)
|
|
|
|
|
|
|
|
|
|
|
|
# Letzten Block aus Hardware-Buffer ausspielen lassen
|
|
|
|
|
|
time.sleep(self.CALLBACK_BLOCK / self.PLAYBACK_RATE + 0.1)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.error = e
|
|
|
|
|
|
|
|
|
|
|
|
def put(self, chunk: torch.Tensor):
|
|
|
|
|
|
self.audio_queue.put(chunk)
|
|
|
|
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
|
|
self.audio_queue.put(None)
|
|
|
|
|
|
if self.thread:
|
|
|
|
|
|
self.thread.join()
|
|
|
|
|
|
if self.error:
|
|
|
|
|
|
raise RuntimeError(f"Fehler bei Live-Wiedergabe: {self.error}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def synthesize_non_streaming(
|
|
|
|
|
|
text: str,
|
|
|
|
|
|
lang: str,
|
|
|
|
|
|
output_path: Optional[Path],
|
|
|
|
|
|
max_len: int,
|
|
|
|
|
|
first_chunk_len: int,
|
|
|
|
|
|
voice_path: Optional[str],
|
|
|
|
|
|
device: str,
|
|
|
|
|
|
show_progress: bool = True,
|
|
|
|
|
|
spell_uppercase_acronyms: bool = True,
|
|
|
|
|
|
acronym_mode: Optional[str] = None,
|
2026-06-03 23:45:03 +02:00
|
|
|
|
normalize_date_values: bool = True,
|
2026-05-16 08:56:50 +02:00
|
|
|
|
normalize_time_values: bool = True,
|
|
|
|
|
|
normalize_year_values: bool = True,
|
|
|
|
|
|
normalize_units_values: bool = True,
|
|
|
|
|
|
conversation_mode: bool = True,
|
|
|
|
|
|
play_audio: bool = False,
|
|
|
|
|
|
save_wav: bool = True,
|
|
|
|
|
|
audio_device: Optional[str] = "pulse",
|
|
|
|
|
|
sentence_mode: bool = True,
|
|
|
|
|
|
speed: float = 1.0,
|
|
|
|
|
|
debug_delay: float = 0.0,
|
|
|
|
|
|
t3_model: Optional[str] = None,
|
|
|
|
|
|
pronunciation_dict: Optional[dict] = None,
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
stop_event: Optional[threading.Event] = None,
|
2026-05-16 08:56:50 +02:00
|
|
|
|
) -> Optional[Path]:
|
|
|
|
|
|
if lang not in SUPPORTED_LANGS:
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
f"Nicht unterstützte Sprache '{lang}'. Unterstützt: {', '.join(sorted(SUPPORTED_LANGS))}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if voice_path and not Path(voice_path).exists():
|
|
|
|
|
|
raise FileNotFoundError(f"Voice-Referenz nicht gefunden: {voice_path}")
|
|
|
|
|
|
|
|
|
|
|
|
# Erst unsichtbare Zeichen entfernen, dann Sätze splitten (Paragraphen-Struktur erhalten),
|
|
|
|
|
|
# danach erst Akronym-Expansion — sonst erzeugen "A. R. D."-Punkte falsche Satzgrenzen.
|
|
|
|
|
|
text = clean_raw_text(text)
|
|
|
|
|
|
|
|
|
|
|
|
model, model_kind, sr = load_model(lang, device, t3_model=t3_model)
|
|
|
|
|
|
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
# [xx]...[/xx]-Sprachmarkierungen extrahieren; ohne Markierungen → ein Span in default lang.
|
|
|
|
|
|
lang_spans = extract_language_spans(text, lang)
|
2026-05-16 08:56:50 +02:00
|
|
|
|
|
|
|
|
|
|
preprocess_kw = dict(
|
|
|
|
|
|
spell_uppercase_acronyms=spell_uppercase_acronyms,
|
|
|
|
|
|
acronym_mode=acronym_mode,
|
2026-06-03 23:45:03 +02:00
|
|
|
|
normalize_date_values=normalize_date_values,
|
2026-05-16 08:56:50 +02:00
|
|
|
|
normalize_time_values=normalize_time_values,
|
|
|
|
|
|
normalize_year_values=normalize_year_values,
|
|
|
|
|
|
normalize_units_values=normalize_units_values,
|
|
|
|
|
|
pronunciation_dict=pronunciation_dict,
|
|
|
|
|
|
)
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
# chunk_pairs: Liste von (verarbeiteter_Text, chunk_lang)
|
|
|
|
|
|
chunk_pairs: List[Tuple[str, str]] = []
|
|
|
|
|
|
for span_idx, (span_text, span_lang) in enumerate(lang_spans):
|
|
|
|
|
|
if sentence_mode:
|
|
|
|
|
|
raw = split_into_sentences(span_text, max_len=max_len)
|
|
|
|
|
|
elif conversation_mode and span_idx == 0:
|
|
|
|
|
|
raw = split_for_conversation(span_text, first_chunk_len=first_chunk_len, max_len=max_len)
|
|
|
|
|
|
else:
|
|
|
|
|
|
raw = split_long_text(span_text, max_len=max_len)
|
|
|
|
|
|
for c in raw:
|
|
|
|
|
|
processed = preprocess_tts_text(c, lang=span_lang, **preprocess_kw)
|
|
|
|
|
|
if processed.strip():
|
|
|
|
|
|
chunk_pairs.append((processed, span_lang))
|
|
|
|
|
|
|
|
|
|
|
|
if not chunk_pairs:
|
2026-05-16 08:56:50 +02:00
|
|
|
|
raise ValueError("Kein verwertbarer Text nach dem Einlesen gefunden.")
|
|
|
|
|
|
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
chunks = [t for t, _ in chunk_pairs] # für Progress-Anzeige
|
|
|
|
|
|
|
2026-05-16 08:56:50 +02:00
|
|
|
|
if show_progress:
|
|
|
|
|
|
print(f"Sprache: {lang}")
|
|
|
|
|
|
print(f"Gerät: {device}")
|
|
|
|
|
|
print(f"Modell: {'ChatterboxTTS (monolingual)' if model_kind == 'mono' else 'ChatterboxMultilingualTTS'}")
|
|
|
|
|
|
print(f"Sätze: {len(chunks)}")
|
|
|
|
|
|
print(f"Modus: {'Satz-für-Satz' if sentence_mode else 'non-streaming'} + Playback")
|
|
|
|
|
|
print(f"Live-Wiedergabe: {'ja' if play_audio else 'nein'}")
|
|
|
|
|
|
print(f"WAV speichern: {'ja' if save_wav and output_path else 'nein'}")
|
|
|
|
|
|
if output_path and save_wav:
|
|
|
|
|
|
print(f"Ausgabe: {output_path}")
|
|
|
|
|
|
|
|
|
|
|
|
if play_audio:
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
playback = PlaybackWorker(sample_rate=sr, device=audio_device, speed=speed,
|
|
|
|
|
|
stop_event=stop_event)
|
2026-05-16 08:56:50 +02:00
|
|
|
|
playback.start()
|
|
|
|
|
|
else:
|
|
|
|
|
|
playback = None
|
|
|
|
|
|
|
|
|
|
|
|
wavs = []
|
|
|
|
|
|
try:
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
for i, (chunk, chunk_lang) in enumerate(chunk_pairs, start=1):
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
if stop_event and stop_event.is_set():
|
|
|
|
|
|
if show_progress:
|
|
|
|
|
|
print("Abbruch angefordert – Synthese gestoppt.")
|
|
|
|
|
|
break
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
if _wait_while_paused(stop_event):
|
|
|
|
|
|
break
|
2026-05-16 08:56:50 +02:00
|
|
|
|
if debug_delay > 0:
|
|
|
|
|
|
if show_progress:
|
|
|
|
|
|
print(f"[{i}/{len(chunks)}] Warte {debug_delay:.0f}s (debug_delay) ...")
|
|
|
|
|
|
time.sleep(debug_delay)
|
|
|
|
|
|
if show_progress:
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
lang_hint = f" [{chunk_lang}]" if chunk_lang != lang else ""
|
|
|
|
|
|
print(f"[{i}/{len(chunks)}] Generiere ({len(chunk)} Zeichen){lang_hint} ...")
|
|
|
|
|
|
wav = generate_chunk(model, model_kind, chunk, chunk_lang, voice_path)
|
2026-05-16 08:56:50 +02:00
|
|
|
|
wavs.append(wav)
|
|
|
|
|
|
if playback is not None:
|
|
|
|
|
|
playback.put(wav)
|
|
|
|
|
|
finally:
|
|
|
|
|
|
if playback is not None:
|
|
|
|
|
|
playback.stop()
|
|
|
|
|
|
|
|
|
|
|
|
if not wavs:
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
final_wav = wavs[0] if len(wavs) == 1 else torch.cat(wavs, dim=-1)
|
|
|
|
|
|
|
|
|
|
|
|
if save_wav and output_path:
|
|
|
|
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
ta.save(str(output_path), final_wav, sr)
|
|
|
|
|
|
return output_path
|
|
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def synthesize_streaming(
|
|
|
|
|
|
text: str,
|
|
|
|
|
|
lang: str,
|
|
|
|
|
|
output_path: Optional[Path],
|
|
|
|
|
|
max_len: int,
|
|
|
|
|
|
first_chunk_len: int,
|
|
|
|
|
|
voice_path: Optional[str],
|
|
|
|
|
|
device: str,
|
|
|
|
|
|
show_progress: bool = True,
|
|
|
|
|
|
spell_uppercase_acronyms: bool = True,
|
|
|
|
|
|
acronym_mode: str = "period_space",
|
2026-06-03 23:45:03 +02:00
|
|
|
|
normalize_date_values: bool = True,
|
2026-05-16 08:56:50 +02:00
|
|
|
|
normalize_time_values: bool = True,
|
|
|
|
|
|
normalize_year_values: bool = True,
|
|
|
|
|
|
normalize_units_values: bool = True,
|
|
|
|
|
|
conversation_mode: bool = True,
|
|
|
|
|
|
play_audio: bool = True,
|
|
|
|
|
|
save_wav: bool = True,
|
|
|
|
|
|
stream_chunk_size: int = 25,
|
|
|
|
|
|
audio_device: Optional[str] = None,
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
stop_event: Optional[threading.Event] = None,
|
2026-05-16 08:56:50 +02:00
|
|
|
|
) -> Optional[Path]:
|
|
|
|
|
|
if lang not in SUPPORTED_LANGS:
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
f"Nicht unterstützte Sprache '{lang}'. Unterstützt: {', '.join(sorted(SUPPORTED_LANGS))}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if voice_path and not Path(voice_path).exists():
|
|
|
|
|
|
raise FileNotFoundError(f"Voice-Referenz nicht gefunden: {voice_path}")
|
|
|
|
|
|
|
Add HTTP service, MCP adapter, systemd autostart; fix bugs and docs
- 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>
2026-05-16 10:19:00 +02:00
|
|
|
|
# Erst bereinigen und splitten, dann pro Chunk normalisieren —
|
|
|
|
|
|
# sonst erzeugen Akronym-Punkte ("ARD" → "Ah Er De.") falsche Satzgrenzen.
|
|
|
|
|
|
text = clean_raw_text(text)
|
2026-05-16 08:56:50 +02:00
|
|
|
|
|
|
|
|
|
|
model, model_kind, sr = load_model(lang, device)
|
|
|
|
|
|
|
|
|
|
|
|
if not hasattr(model, "generate_stream"):
|
|
|
|
|
|
raise RuntimeError(
|
|
|
|
|
|
"Dieses Chatterbox-Paket bietet kein generate_stream(). "
|
|
|
|
|
|
"Installiere z. B. 'chatterbox-streaming'."
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if conversation_mode:
|
Add HTTP service, MCP adapter, systemd autostart; fix bugs and docs
- 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>
2026-05-16 10:19:00 +02:00
|
|
|
|
raw_chunks = split_for_conversation(text, first_chunk_len=first_chunk_len, max_len=max_len)
|
2026-05-16 08:56:50 +02:00
|
|
|
|
else:
|
Add HTTP service, MCP adapter, systemd autostart; fix bugs and docs
- 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>
2026-05-16 10:19:00 +02:00
|
|
|
|
raw_chunks = split_long_text(text, max_len=max_len)
|
|
|
|
|
|
|
|
|
|
|
|
preprocess_kw = dict(
|
|
|
|
|
|
lang=lang,
|
|
|
|
|
|
spell_uppercase_acronyms=spell_uppercase_acronyms,
|
|
|
|
|
|
acronym_mode=acronym_mode,
|
2026-06-03 23:45:03 +02:00
|
|
|
|
normalize_date_values=normalize_date_values,
|
Add HTTP service, MCP adapter, systemd autostart; fix bugs and docs
- 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>
2026-05-16 10:19:00 +02:00
|
|
|
|
normalize_time_values=normalize_time_values,
|
|
|
|
|
|
normalize_year_values=normalize_year_values,
|
|
|
|
|
|
normalize_units_values=normalize_units_values,
|
|
|
|
|
|
)
|
|
|
|
|
|
text_chunks = [preprocess_tts_text(c, **preprocess_kw) for c in raw_chunks]
|
|
|
|
|
|
text_chunks = [c for c in text_chunks if c.strip()]
|
2026-05-16 08:56:50 +02:00
|
|
|
|
|
|
|
|
|
|
if not text_chunks:
|
|
|
|
|
|
raise ValueError("Kein verwertbarer Text nach dem Einlesen gefunden.")
|
|
|
|
|
|
|
|
|
|
|
|
if play_audio:
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
playback = PlaybackWorker(sample_rate=sr, device=audio_device, stop_event=stop_event)
|
2026-05-16 08:56:50 +02:00
|
|
|
|
playback.start()
|
|
|
|
|
|
else:
|
|
|
|
|
|
playback = None
|
|
|
|
|
|
|
|
|
|
|
|
all_audio_chunks: List[torch.Tensor] = []
|
|
|
|
|
|
t0 = time.perf_counter()
|
|
|
|
|
|
first_audio_started = False
|
|
|
|
|
|
|
|
|
|
|
|
if show_progress:
|
|
|
|
|
|
print(f"Sprache: {lang}")
|
|
|
|
|
|
print(f"Gerät: {device}")
|
|
|
|
|
|
print(f"Modell: {'ChatterboxTTS (monolingual)' if model_kind == 'mono' else 'ChatterboxMultilingualTTS'}")
|
|
|
|
|
|
print(f"Text-Chunks: {len(text_chunks)}")
|
|
|
|
|
|
print(f"Modus: streaming")
|
|
|
|
|
|
print(f"Gesprächsmodus: {'ja' if conversation_mode else 'nein'}")
|
|
|
|
|
|
print(f"Live-Wiedergabe: {'ja' if play_audio else 'nein'}")
|
|
|
|
|
|
print(f"WAV speichern: {'ja' if save_wav and output_path else 'nein'}")
|
|
|
|
|
|
print(f"Streaming chunk_size: {stream_chunk_size}")
|
|
|
|
|
|
if output_path:
|
|
|
|
|
|
print(f"Ausgabe: {output_path}")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
for text_idx, text_chunk in enumerate(text_chunks, start=1):
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
if stop_event and stop_event.is_set():
|
|
|
|
|
|
if show_progress:
|
|
|
|
|
|
print("Abbruch angefordert – Streaming gestoppt.")
|
|
|
|
|
|
break
|
2026-05-16 08:56:50 +02:00
|
|
|
|
if show_progress:
|
|
|
|
|
|
print(f"[Text {text_idx}/{len(text_chunks)}] Starte Streaming für {len(text_chunk)} Zeichen ...")
|
|
|
|
|
|
|
|
|
|
|
|
stream_iter = generate_stream_chunk(
|
|
|
|
|
|
model=model,
|
|
|
|
|
|
model_kind=model_kind,
|
|
|
|
|
|
text=text_chunk,
|
|
|
|
|
|
lang=lang,
|
|
|
|
|
|
voice_path=voice_path,
|
|
|
|
|
|
stream_chunk_size=stream_chunk_size,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
for audio_idx, item in enumerate(stream_iter, start=1):
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
if stop_event and stop_event.is_set():
|
|
|
|
|
|
break
|
2026-05-16 08:56:50 +02:00
|
|
|
|
if isinstance(item, tuple) and len(item) == 2:
|
|
|
|
|
|
audio_chunk, metrics = item
|
|
|
|
|
|
else:
|
|
|
|
|
|
audio_chunk, metrics = item, None
|
|
|
|
|
|
|
|
|
|
|
|
all_audio_chunks.append(audio_chunk)
|
|
|
|
|
|
|
|
|
|
|
|
if playback is not None:
|
|
|
|
|
|
playback.put(audio_chunk)
|
|
|
|
|
|
if not first_audio_started:
|
|
|
|
|
|
first_audio_started = True
|
|
|
|
|
|
if show_progress:
|
|
|
|
|
|
dt = time.perf_counter() - t0
|
|
|
|
|
|
print(f"Audio-Wiedergabe gestartet nach {dt:.3f}s")
|
|
|
|
|
|
|
|
|
|
|
|
if show_progress:
|
|
|
|
|
|
msg = f" -> Audio-Chunk {audio_idx}"
|
|
|
|
|
|
if metrics is not None:
|
|
|
|
|
|
latency = getattr(metrics, "latency_to_first_chunk", None)
|
|
|
|
|
|
rtf = getattr(metrics, "rtf", None)
|
|
|
|
|
|
chunk_count = getattr(metrics, "chunk_count", None)
|
|
|
|
|
|
if chunk_count is not None:
|
|
|
|
|
|
msg += f", model_chunk={chunk_count}"
|
|
|
|
|
|
if latency:
|
|
|
|
|
|
msg += f", first_latency={latency:.3f}s"
|
|
|
|
|
|
if rtf:
|
|
|
|
|
|
msg += f", rtf={rtf:.3f}"
|
|
|
|
|
|
print(msg)
|
|
|
|
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
|
if playback is not None:
|
|
|
|
|
|
playback.stop()
|
|
|
|
|
|
|
|
|
|
|
|
final_output = None
|
|
|
|
|
|
if save_wav and output_path:
|
|
|
|
|
|
final_audio = all_audio_chunks[0] if len(all_audio_chunks) == 1 else torch.cat(all_audio_chunks, dim=-1)
|
|
|
|
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
ta.save(str(output_path), final_audio, sr)
|
|
|
|
|
|
final_output = output_path
|
|
|
|
|
|
|
|
|
|
|
|
return final_output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_argparser() -> argparse.ArgumentParser:
|
|
|
|
|
|
p = argparse.ArgumentParser(
|
|
|
|
|
|
description="Low-Latency Chatterbox TTS CLI mit deutscher Text-Normalisierung und optionalem Streaming."
|
|
|
|
|
|
)
|
|
|
|
|
|
p.add_argument("--text", type=str, help="Direkter Eingabetext.")
|
|
|
|
|
|
p.add_argument("--input", type=str, help="Pfad zu UTF-8-Textdatei.")
|
|
|
|
|
|
p.add_argument("--lang", type=str, default="de", help="Sprachcode, default: de.")
|
|
|
|
|
|
p.add_argument("--len", dest="max_len", type=int, default=400, help="Maximale Chunk-Länge, default: 400.")
|
|
|
|
|
|
p.add_argument("--first-chunk-len", type=int, default=80, help="Kleinere Zielgröße für den ersten Chunk im Gesprächsmodus. Default: 80.")
|
|
|
|
|
|
p.add_argument("--output", type=str, help="Ausgabedatei .wav")
|
|
|
|
|
|
p.add_argument("--voice", type=str, help="Optionale Referenz-WAV für Voice-Cloning.")
|
|
|
|
|
|
p.add_argument("--device", type=str, default=None, help="z. B. cuda:0 oder cpu.")
|
|
|
|
|
|
p.add_argument("--no-progress", action="store_true", help="Weniger Konsolen-Output.")
|
|
|
|
|
|
p.add_argument("--no-spell-acronyms", action="store_true", help="Großgeschriebene Akronyme nicht buchstabieren.")
|
|
|
|
|
|
p.add_argument(
|
|
|
|
|
|
"--acronym-mode",
|
|
|
|
|
|
type=str,
|
|
|
|
|
|
default=None, # None = automatisch: 'german' bei de, 'period_space' sonst
|
|
|
|
|
|
choices=["space", "period", "comma", "period_space", "german"],
|
|
|
|
|
|
help="Ausgabeformat für buchstabierte Akronyme. Default: 'german' bei --lang de, sonst 'period_space'."
|
|
|
|
|
|
)
|
|
|
|
|
|
p.add_argument("--pronunciation-dict", type=str, default=None, help="Pfad zu einer JSON-Datei mit Aussprache-Substitutionen (Eigenname → Lautschrift).")
|
2026-06-03 23:45:03 +02:00
|
|
|
|
p.add_argument("--no-normalize-dates", action="store_true", help="Datumsangaben (03.06.2026) nicht in sprechbaren Text umwandeln.")
|
2026-05-16 08:56:50 +02:00
|
|
|
|
p.add_argument("--no-normalize-times", action="store_true", help="Uhrzeiten nicht in sprechbaren Text umwandeln.")
|
|
|
|
|
|
p.add_argument("--no-normalize-years", action="store_true", help="Jahreszahlen nicht in sprechbaren Text umwandeln.")
|
|
|
|
|
|
p.add_argument("--no-normalize-units", action="store_true", help="Einheiten nicht in sprechbaren Text umwandeln.")
|
|
|
|
|
|
p.add_argument("--stream", action="store_true", help="Streaming-TTS-Modus (experimentell, kann abgehackt klingen).")
|
|
|
|
|
|
p.add_argument("--no-play", action="store_true", help="Nicht live abspielen.")
|
|
|
|
|
|
p.add_argument("--audio-device", type=str, default="pulse", help="Sounddevice-Ausgabegerät, z. B. 'pulse' oder 'M2: USB Audio'. Standard: pulse.")
|
|
|
|
|
|
p.add_argument("--save", action="store_true", help="WAV-Datei speichern (Standard: nein).")
|
|
|
|
|
|
p.add_argument("--stream-chunk-size", type=int, default=12, help="Streaming chunk_size (nur mit --stream). Default: 12.")
|
|
|
|
|
|
p.add_argument("--no-sentence-mode", action="store_true", help="Sätze zu größeren Chunks gruppieren statt einzeln ausgeben.")
|
|
|
|
|
|
p.add_argument("--speed", type=float, default=1.0, help="Wiedergabegeschwindigkeit: 0.8 = 20%% langsamer, 1.2 = 20%% schneller. Default: 1.0.")
|
|
|
|
|
|
p.add_argument("--debug-delay", type=float, default=0.0, help="Sekunden Pause vor jedem Satz (simuliert langsame KI). Nur zum Testen.")
|
|
|
|
|
|
p.add_argument("--t3-model", type=str, default="v3", help="Multilingual T3-Modell: 'v3' (default), 'v2' oder Dateiname.")
|
|
|
|
|
|
p.add_argument("--no-conversation-mode", action="store_true", help="Ersten Chunk nicht künstlich kleiner machen (nur ohne --no-sentence-mode).")
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
p.add_argument("--no-strip-markdown", action="store_true", help="Markdown-Formatierung (**, *, #, etc.) nicht aus dem Text entfernen.")
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
p.add_argument("--stop", action="store_true", help="Globales Stop-Signal setzen (für Tests und Service-Integration).")
|
2026-05-16 08:56:50 +02:00
|
|
|
|
return p
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> int:
|
|
|
|
|
|
parser = build_argparser()
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
if args.stop:
|
|
|
|
|
|
request_stop()
|
|
|
|
|
|
print("Stop-Signal gesetzt.")
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
if args.speed <= 0:
|
|
|
|
|
|
parser.error(f"--speed muss positiv sein, erhalten: {args.speed}")
|
|
|
|
|
|
if args.first_chunk_len > args.max_len:
|
|
|
|
|
|
parser.error(
|
|
|
|
|
|
f"--first-chunk-len ({args.first_chunk_len}) darf nicht größer sein als --len ({args.max_len})"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-16 08:56:50 +02:00
|
|
|
|
try:
|
|
|
|
|
|
text = read_input_text(args.text, args.input)
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
if not args.no_strip_markdown:
|
|
|
|
|
|
text = strip_markdown(text)
|
2026-05-16 08:56:50 +02:00
|
|
|
|
device = get_device(args.device)
|
|
|
|
|
|
output_path = Path(args.output) if args.output else default_output_path(args.input, args.lang)
|
|
|
|
|
|
|
|
|
|
|
|
save_wav = args.save or bool(args.output)
|
|
|
|
|
|
|
|
|
|
|
|
# Acronym-Mode-Default: 'german' bei Deutsch, 'period_space' sonst
|
|
|
|
|
|
acronym_mode = args.acronym_mode or ("german" if args.lang == "de" else "period_space")
|
|
|
|
|
|
|
|
|
|
|
|
# Optionales Aussprache-Wörterbuch laden
|
|
|
|
|
|
pronunciation_dict: Optional[dict] = None
|
|
|
|
|
|
if args.pronunciation_dict:
|
|
|
|
|
|
import json
|
|
|
|
|
|
pron_path = Path(args.pronunciation_dict)
|
|
|
|
|
|
if not pron_path.exists():
|
|
|
|
|
|
raise FileNotFoundError(f"Aussprache-Dict nicht gefunden: {pron_path}")
|
Bugfixes, Verbesserungen und Mixed-Language-Support
Bugfixes:
- Abkürzungen (z.B., d.h., Dr., Prof.) werden nicht mehr als Satzenden erkannt (_ABBREV_MASK_RE)
- Multilingual-Import: except Exception → except (ImportError, ModuleNotFoundError)
- tts_agent: ReAct-Schleife auf max. 10 Iterationen begrenzt, model_dump → explizites Dict
- tts_service: audio_device=None fällt auf 'pulse' zurück
- JSON-Fehlerbehandlung für --pronunciation-dict mit aussagekräftiger Meldung
- PlaybackWorker: Audio-Device wird vor Stream-Start via sd.query_devices() geprüft
- mcp_adapter: Fehlerbehandlung für HTTP-Fehler, Timeout erhöht, session_id ergänzt
- tts_agent: Health-Check beim Start, --speed/--first-chunk-len Validierung
Neue Features:
- Gemischtsprachige Texte: [en]...[/en]-Markierungen für per-Segment language_id
- strip_markdown(): entfernt Markdown-Formatierung vor der Synthese (--no-strip-markdown)
- Emoji-Entfernung in clean_raw_text() via unicodedata
- Pause/Resume: request_pause()/request_resume(), POST /pause, POST /resume, MCP-Tools
- Neue Einheiten: °C, °F, kWh, kW, W, V, A, J, kPa, bar, m², m³, m/s, rpm
- number_to_words_de/en bis Milliarden
- DEFAULT_PRONUNCIATION_DE erweitert (GitHub, YouTube, LinkedIn, Wi-Fi, iPhone, ChatGPT, …)
- NON_SPELLED_ACRONYMS erweitert (USB, CPU, GPU, API, CEO, HTML, …)
- Nummerierte Listen als separate Chunks behandelt
- Modell-Warmup via TTS_PRELOAD_LANG Env-Variable
- requirements.txt: Upper-Bounds für fastapi und uvicorn
Dokumentation: CLAUDE.md, README.md, BEDIENUNGSANLEITUNG.md vollständig aktualisiert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 11:36:54 +02:00
|
|
|
|
try:
|
|
|
|
|
|
pronunciation_dict = json.loads(pron_path.read_text(encoding="utf-8"))
|
|
|
|
|
|
except json.JSONDecodeError as e:
|
|
|
|
|
|
raise ValueError(f"Ungültiges JSON in {pron_path}: {e}") from e
|
2026-05-16 08:56:50 +02:00
|
|
|
|
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
clear_stop()
|
|
|
|
|
|
|
2026-05-16 08:56:50 +02:00
|
|
|
|
if args.stream:
|
|
|
|
|
|
out = synthesize_streaming(
|
|
|
|
|
|
text=text,
|
|
|
|
|
|
lang=args.lang,
|
|
|
|
|
|
output_path=output_path if save_wav else None,
|
|
|
|
|
|
max_len=args.max_len,
|
|
|
|
|
|
first_chunk_len=args.first_chunk_len,
|
|
|
|
|
|
voice_path=args.voice,
|
|
|
|
|
|
device=device,
|
|
|
|
|
|
show_progress=not args.no_progress,
|
|
|
|
|
|
spell_uppercase_acronyms=not args.no_spell_acronyms,
|
|
|
|
|
|
acronym_mode=acronym_mode,
|
2026-06-03 23:45:03 +02:00
|
|
|
|
normalize_date_values=not args.no_normalize_dates,
|
2026-05-16 08:56:50 +02:00
|
|
|
|
normalize_time_values=not args.no_normalize_times,
|
|
|
|
|
|
normalize_year_values=not args.no_normalize_years,
|
|
|
|
|
|
normalize_units_values=not args.no_normalize_units,
|
|
|
|
|
|
conversation_mode=not args.no_conversation_mode,
|
|
|
|
|
|
play_audio=not args.no_play,
|
|
|
|
|
|
save_wav=save_wav,
|
|
|
|
|
|
stream_chunk_size=args.stream_chunk_size,
|
|
|
|
|
|
audio_device=args.audio_device,
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
stop_event=STOP_REQUESTED,
|
2026-05-16 08:56:50 +02:00
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
out = synthesize_non_streaming(
|
|
|
|
|
|
text=text,
|
|
|
|
|
|
lang=args.lang,
|
|
|
|
|
|
output_path=output_path if save_wav else None,
|
|
|
|
|
|
max_len=args.max_len,
|
|
|
|
|
|
first_chunk_len=args.first_chunk_len,
|
|
|
|
|
|
voice_path=args.voice,
|
|
|
|
|
|
device=device,
|
|
|
|
|
|
show_progress=not args.no_progress,
|
|
|
|
|
|
spell_uppercase_acronyms=not args.no_spell_acronyms,
|
|
|
|
|
|
acronym_mode=acronym_mode,
|
2026-06-03 23:45:03 +02:00
|
|
|
|
normalize_date_values=not args.no_normalize_dates,
|
2026-05-16 08:56:50 +02:00
|
|
|
|
normalize_time_values=not args.no_normalize_times,
|
|
|
|
|
|
normalize_year_values=not args.no_normalize_years,
|
|
|
|
|
|
normalize_units_values=not args.no_normalize_units,
|
|
|
|
|
|
conversation_mode=not args.no_conversation_mode,
|
|
|
|
|
|
play_audio=not args.no_play,
|
|
|
|
|
|
save_wav=save_wav,
|
|
|
|
|
|
audio_device=args.audio_device,
|
|
|
|
|
|
sentence_mode=not args.no_sentence_mode,
|
|
|
|
|
|
speed=args.speed,
|
|
|
|
|
|
debug_delay=args.debug_delay,
|
|
|
|
|
|
t3_model=args.t3_model,
|
|
|
|
|
|
pronunciation_dict=pronunciation_dict,
|
Erweiterung: Stop-Mechanismus, REST-Service und MCP-Adapter
- chatterbox_cli_v4.py: kooperativer Stop-Mechanismus via threading.Event
(STOP_REQUESTED, request_stop, clear_stop); PlaybackWorker, synthesize_non_streaming
und synthesize_streaming prüfen das Event vor jedem Chunk; --stop CLI-Flag
- tts_service.py: FastAPI-Service mit Modell-Caching, Job-Queue und Worker-Thread;
Endpunkte: POST /speak, POST /stop, GET /health, GET /status, GET /voices
- mcp_adapter.py: MCP-Adapter (stdio/streamable-http) über tts_service; Tools:
speak, stop, get_status, list_voices
- requirements.txt: fastapi, uvicorn, httpx, mcp ergänzt
- CLAUDE.md: Architektur und Startbefehle dokumentiert
- .gitignore: Ideen/-Verzeichnis ausgeschlossen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 09:46:43 +02:00
|
|
|
|
stop_event=STOP_REQUESTED,
|
2026-05-16 08:56:50 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if out is not None:
|
|
|
|
|
|
print(f"Fertig: {out}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print("Fertig.")
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"Fehler: {e}", file=sys.stderr)
|
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
raise SystemExit(main())
|
|
|
|
|
|
|