This commit is contained in:
Shaman
2026-10-01 07:38:01 +03:00
commit 82c306ff69
33 changed files with 2629 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
import numpy as np
import torch
from app.segmentation import Segment
from app.synthesis import SynthesisEngine, ModelEntry
class FakeModel:
def __init__(self, name, calls):
self.name = name
self.calls = calls
self.speakers = ["spk1", "spk2"]
def apply_tts(self, *, text=None, ssml_text=None, speaker, sample_rate,
put_accent, put_yo):
self.calls.append({
"name": self.name, "speaker": speaker, "text": text,
"ssml_text": ssml_text, "put_accent": put_accent,
})
return torch.zeros(10)
class FakeTts:
def __init__(self, name, calls, put_accent=True):
self.sample_rate = 24000
self.put_accent = put_accent
self.put_yo = True
self.tts_model = FakeModel(name, calls)
def make_engine(use_ssml=True, put_accent=True):
calls: list[dict] = []
engine = SynthesisEngine(
models=[
ModelEntry(model_id="ru_model", tts=FakeTts("ru", calls, put_accent), languages=["ru"]),
ModelEntry(model_id="en_model", tts=FakeTts("en", calls, put_accent), languages=["en"]),
],
sample_rate=24000,
use_ssml=use_ssml,
)
return engine, calls
def test_single_speaker_for_all_segments():
engine, calls = make_engine(use_ssml=False)
segments = [Segment(text="Озон", lang="ru"), Segment(text="Пайтон", lang="ru")]
pcm = engine.synthesize_segments(segments, "baya", "ru")
assert pcm
assert len(calls) == 2
assert all(c["name"] == "ru" and c["speaker"] == "baya" for c in calls)
def test_en_lang_selects_en_model():
engine, calls = make_engine(use_ssml=False)
segments = [Segment(text="hello", lang="en")]
engine.synthesize_segments(segments, "en_0", "en")
assert calls[0]["name"] == "en"
assert calls[0]["text"] == "hello"
def test_legacy_path_uses_text_not_ssml():
engine, calls = make_engine(use_ssml=False)
engine.synthesize_segments([Segment(text="привет", lang="ru")], "baya", "ru")
assert calls[0]["text"] == "привет"
assert calls[0]["ssml_text"] is None
def test_no_silence_padding_between_segments():
engine, calls = make_engine(use_ssml=False)
segments = [Segment(text="а", lang="ru"), Segment(text="б", lang="ru")]
pcm = engine.synthesize_segments(segments, "baya", "ru")
expected = (np.zeros(10) * 32767).astype(np.int16)
assert len(pcm) == 2 * len(expected.tobytes())
# --- ssml_text path (task 3) ---
def test_ssml_path_passes_ssml_text():
engine, calls = make_engine()
segments = [Segment(text="привет", lang="ru"), Segment(text="мир", lang="ru")]
pcm = engine.synthesize_segments(segments, "baya", "ru")
assert pcm
assert len(calls) == 1 # один чанк на абзац
c = calls[0]
assert c["text"] is None
assert c["ssml_text"].startswith("<speak>")
assert "<s>привет</s>" in c["ssml_text"]
assert "<s>мир</s>" in c["ssml_text"]
def test_ssml_path_single_call_per_chunk():
engine, calls = make_engine()
# Два разных абзаца -> два <p> -> (в пределах лимита) один документ/один вызов.
segments = [
Segment(text="первый", lang="ru", para=0),
Segment(text="второй", lang="ru", para=1),
]
engine.synthesize_segments(segments, "baya", "ru")
assert len(calls) == 1
assert "<p>" in calls[0]["ssml_text"]
def test_ssml_prosody_rate_and_pitch_passed_to_builder():
engine, calls = make_engine()
engine.synthesize_segments(
[Segment(text="привет", lang="ru")], "baya", "ru",
rate="slow", pitch="high",
)
ssml = calls[0]["ssml_text"]
assert '<prosody' in ssml
assert 'rate="slow"' in ssml
assert 'pitch="high"' in ssml
def test_ssml_medium_prosody_omitted():
engine, calls = make_engine()
engine.synthesize_segments(
[Segment(text="привет", lang="ru")], "baya", "ru",
rate="medium", pitch="medium",
)
assert "<prosody" not in calls[0]["ssml_text"]
def test_ssml_use_ssml_false_overrides_for_call():
engine, calls = make_engine(use_ssml=True)
engine.synthesize_segments(
[Segment(text="привет", lang="ru")], "baya", "ru", use_ssml=False
)
assert calls[0]["ssml_text"] is None
assert calls[0]["text"] == "привет"
def test_put_accent_true_on_ssml_path():
# put_accent=True даже если модель по умолчанию False (ручные + работают).
engine, calls = make_engine(put_accent=False)
engine.synthesize_segments([Segment(text="привет", lang="ru")], "baya", "ru")
assert calls[0]["put_accent"] is True
def test_manual_stress_marker_reaches_model_via_ssml():
engine, calls = make_engine()
engine.synthesize_segments(
[Segment(text="втор+ой сл+ово", lang="ru")], "baya", "ru"
)
assert "втор+ой" in calls[0]["ssml_text"]
assert "сл+ово" in calls[0]["ssml_text"]
def test_manual_stress_non_vowel_passes_without_error():
engine, calls = make_engine()
pcm = engine.synthesize_segments(
[Segment(text="т+ест", lang="ru")], "baya", "ru"
)
assert pcm
assert "т+ест" in calls[0]["ssml_text"]