86 lines
3.4 KiB
Python
86 lines
3.4 KiB
Python
"""Limbă & voce în voice mode pe voci pocket-tts (English-only).
|
|
|
|
Regula (preferința lui Marius, 2026-07-11): vocea configurată NU se schimbă
|
|
niciodată din cauza limbii. Diacriticele RO scăpate într-un răspuns (nume
|
|
proprii ca „Constanța", cuvinte românești) se transliterează la ASCII ca
|
|
pocket-tts să le accepte — fără fallback pe Supertonic, fără clauze mute.
|
|
"""
|
|
import queue
|
|
from unittest.mock import patch
|
|
|
|
from src.voice.tts_stream import TTSQueue
|
|
from tools.tts import _RO_DIACRITICS, fold_ro_diacritics
|
|
|
|
|
|
def _drain_items(q: queue.Queue) -> list:
|
|
items = []
|
|
while True:
|
|
try:
|
|
items.append(q.get_nowait())
|
|
except queue.Empty:
|
|
return items
|
|
|
|
|
|
class TestFoldRoDiacritics:
|
|
def test_folds_all_romanian_diacritics(self):
|
|
assert fold_ro_diacritics("ăâîșț ĂÂÎȘȚ şţ ŞŢ") == "aaist AAIST st ST"
|
|
|
|
def test_proper_noun(self):
|
|
assert fold_ro_diacritics("Constanța") == "Constanta"
|
|
|
|
def test_ascii_untouched(self):
|
|
text = "Tomorrow in Constanta: sunny, 28 degrees."
|
|
assert fold_ro_diacritics(text) == text
|
|
|
|
|
|
class TestPushTextLangRouting:
|
|
@patch("src.voice.tts_stream.engine_for_voice", return_value="pockettts")
|
|
def test_diacritics_folded_voice_kept_on_pockettts(self, _eng):
|
|
ttsq = TTSQueue(voice_id="Marius 4")
|
|
ttsq.push_text("Tomorrow in Constanța it will be sunny, around 28 degrees.")
|
|
items = _drain_items(ttsq._text_queue)
|
|
assert items, "clauzele trebuie să ajungă în coadă"
|
|
for clause, voice, lang in items:
|
|
assert voice == "Marius 4"
|
|
assert lang == "en"
|
|
assert not any(ch in _RO_DIACRITICS for ch in clause)
|
|
|
|
@patch("src.voice.tts_stream.engine_for_voice", return_value="pockettts")
|
|
def test_full_romanian_text_still_keeps_configured_voice(self, _eng):
|
|
ttsq = TTSQueue(voice_id="Marius 4")
|
|
ttsq.push_text("Ora acum e nouă și cinci minute dimineața.")
|
|
items = _drain_items(ttsq._text_queue)
|
|
assert items
|
|
for clause, voice, lang in items:
|
|
assert voice == "Marius 4"
|
|
assert lang == "en"
|
|
assert not any(ch in _RO_DIACRITICS for ch in clause)
|
|
|
|
@patch("src.voice.tts_stream.engine_for_voice", return_value="supertonic")
|
|
def test_supertonic_voice_keeps_romanian(self, _eng):
|
|
ttsq = TTSQueue(voice_id="M2")
|
|
ttsq.push_text("Ora acum e nouă și cinci minute dimineața.")
|
|
items = _drain_items(ttsq._text_queue)
|
|
assert items
|
|
joined = " ".join(clause for clause, _, _ in items)
|
|
assert any(ch in _RO_DIACRITICS for ch in joined), "diacriticele rămân pe Supertonic"
|
|
for clause, voice, lang in items:
|
|
assert voice == "M2"
|
|
assert lang == "ro"
|
|
|
|
|
|
class TestWorkerConsumesTuples:
|
|
@patch("src.voice.tts_stream.synthesize", return_value={"ok": False, "error": "x"})
|
|
def test_worker_unpacks_clause_voice_lang(self, mock_synth):
|
|
ttsq = TTSQueue(voice_id="Marius 4")
|
|
ttsq._text_queue.put(("Hello there.", "Marius 4", "en"))
|
|
ttsq.start()
|
|
try:
|
|
import time
|
|
deadline = time.monotonic() + 2.0
|
|
while not mock_synth.called and time.monotonic() < deadline:
|
|
time.sleep(0.02)
|
|
finally:
|
|
ttsq.stop()
|
|
mock_synth.assert_called_once_with("Hello there.", voice="Marius 4", lang="en")
|