diff --git a/config.json b/config.json index 5e22d9a..4760cd2 100644 --- a/config.json +++ b/config.json @@ -104,7 +104,7 @@ "949388626146517022" ], "user_name": "Marius", - "default_voice": "Marius 3", + "default_voice": "Marius 4", "auto_leave_minutes": 5, "stt_model": "/home/moltbot/echo-core/models/whisper-small-ro-cv11-int8" }, diff --git a/src/voice/normalize.py b/src/voice/normalize.py index bd41c79..2288136 100644 --- a/src/voice/normalize.py +++ b/src/voice/normalize.py @@ -267,17 +267,25 @@ def expand_currency(text: str) -> str: # ---------- Symbols ---------- -def expand_symbols(text: str) -> str: - """Replace common symbols with their Romanian spoken form.""" - text = text.replace('%', ' la sută') - text = text.replace('&', ' și ') - text = text.replace('@', ' la ') - text = text.replace('°', ' grade') +_SYMBOL_WORDS = { + 'ro': {'%': ' la sută', '&': ' și ', '@': ' la ', '°': ' grade'}, + 'en': {'%': ' percent', '&': ' and ', '@': ' at ', '°': ' degrees'}, +} + + +def expand_symbols(text: str, lang: str = 'ro') -> str: + """Replace common symbols with their spoken form for the given language.""" + words = _SYMBOL_WORDS.get(lang, _SYMBOL_WORDS['ro']) + for symbol, word in words.items(): + text = text.replace(symbol, word) text = re.sub(r'\s+', ' ', text).strip() return text -from tools.tts import sanitize_for_supertonic as sanitize_punctuation +# Punctuation-only mapping (fără cap de lungime) — cap-ul de 400 chars e +# limita per-request Supertonic și se aplică în _synthesize_supertonic, +# după segmentarea în clauze, nu aici pe textul întreg. +from tools.tts import map_tts_punctuation as sanitize_punctuation # ---------- Abbreviations ---------- @@ -306,34 +314,41 @@ _MAX_WORDS = 200 _TRUNCATE_SUFFIX = "Restul l-am scris în chat." -def expand_for_tts(text: str) -> str: +def expand_for_tts(text: str, lang: str = 'ro') -> str: """Apply the full normalization pipeline (markdown strip, abbreviations, numbers, currency, units, symbols) WITHOUT the 200-word truncation. Use this for one-shot TTS generation (e.g. /audio command) where the "Restul l-am scris în chat." suffix from normalize_for_tts() would be misleading (no live chat mirror exists for that flow). + + The RO-specific expansions (abbreviations, thousands, time, currency, + units, numbers-to-words) only make sense for Romanian text — for other + languages (e.g. English text routed to pocket-tts, which is + English-only) they'd inject Romanian words/diacritics into text the + target engine can't speak. Skip them when lang != 'ro'. """ text = strip_markdown(text) text = sanitize_punctuation(text) - text = expand_abbreviations(text) - text = normalize_thousands(text) - text = expand_time(text) - text = expand_currency(text) - text = expand_units(text) - text = expand_numbers_ro(text) - text = expand_symbols(text) + if lang == 'ro': + text = expand_abbreviations(text) + text = normalize_thousands(text) + text = expand_time(text) + text = expand_currency(text) + text = expand_units(text) + text = expand_numbers_ro(text) + text = expand_symbols(text, lang=lang) return text.strip() -def normalize_for_tts(text: str) -> str: +def normalize_for_tts(text: str, lang: str = 'ro') -> str: """Apply the full normalization pipeline and truncate to 200 words. If the text exceeds 200 words, the first 200 are kept and the suffix "Restul l-am scris în chat." is appended so the listener knows the response continues in the text channel mirror. """ - text = expand_for_tts(text) + text = expand_for_tts(text, lang=lang) words = text.split() if len(words) > _MAX_WORDS: text = ' '.join(words[:_MAX_WORDS]) + f" {_TRUNCATE_SUFFIX}" diff --git a/src/voice/tts_stream.py b/src/voice/tts_stream.py index b63cdd4..2ffe7c9 100644 --- a/src/voice/tts_stream.py +++ b/src/voice/tts_stream.py @@ -22,7 +22,7 @@ from typing import Iterator, List, Optional import discord from src.voice.normalize import normalize_for_tts -from tools.tts import synthesize +from tools.tts import engine_for_voice, synthesize log = logging.getLogger(__name__) @@ -202,7 +202,14 @@ class TTSQueue: """Normalize, segment into clauses, enqueue each clause for synthesis.""" if not text: return - cleaned = normalize_for_tts(text) + # Resolve lang from the *current* voice_id (not self.lang, which is + # set once at construction and goes stale on in-band voice swap — + # see session.ttsq.voice_id mutation in discord_voice.py / pipeline.py). + # pocket-tts is English-only, so RO number/time/currency expansion + # must be skipped or the normalizer injects Romanian diacritics that + # pocket-tts refuses to speak (silent dropped clause). + lang = "en" if engine_for_voice(self.voice_id) == "pockettts" else "ro" + cleaned = normalize_for_tts(text, lang=lang) n = 0 for clause in clause_segments(cleaned): clause = clause.strip() diff --git a/tools/tts.py b/tools/tts.py index 2efbea5..daf3b8e 100644 --- a/tools/tts.py +++ b/tools/tts.py @@ -62,8 +62,14 @@ def _looks_romanian(text: str) -> bool: return any(ch in _RO_DIACRITICS for ch in text) -def sanitize_for_supertonic(text: str) -> str: - """Replace Unicode punctuation and strip chars that crash Supertonic's ONNX model.""" +def map_tts_punctuation(text: str) -> str: + """Replace Unicode punctuation with ASCII and strip emoji — NO length cap. + + Folosit și de pipeline-ul de normalizare voice (src/voice/normalize.py), + care rulează pe textul întreg ÎNAINTE de segmentarea în clauze — un cap de + lungime aici ar reteza răspunsurile lungi în mijlocul cuvântului și ar + face moartă trunchierea la 200 de cuvinte din normalize_for_tts. + """ for src, dst in _TTS_PUNCT_MAP.items(): text = text.replace(src, dst) # Strip emoji and high-codepoint chars (keep ASCII printable + Latin/Romanian diacritice) @@ -74,7 +80,12 @@ def sanitize_for_supertonic(text: str) -> str: cleaned.append(ch) else: cleaned.append(' ') - text = ' '.join(''.join(cleaned).split()) + return ' '.join(''.join(cleaned).split()) + + +def sanitize_for_supertonic(text: str) -> str: + """map_tts_punctuation + hard cap la limita ONNX Supertonic (per request).""" + text = map_tts_punctuation(text) if len(text) > _MAX_TTS_CHARS: text = text[:_MAX_TTS_CHARS] return text diff --git a/tts_voices.json b/tts_voices.json index 12c259b..cafa1eb 100644 --- a/tts_voices.json +++ b/tts_voices.json @@ -62,5 +62,10 @@ "alba": { "engine": "pockettts", "voice_url": "alba" + }, + "Marius 4": { + "engine": "pockettts", + "state_path": "models/voices/marius-4.safetensors", + "owner": "Marius" } } \ No newline at end of file