first commit

This commit is contained in:
workD12
2026-09-11 19:47:15 +03:00
commit 32754a1fe2
56 changed files with 3836 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
"""Тест TTS-модуля (Модуль 3).
Запуск:
python modules/tts/test_tts.py # синтез фразы по умолчанию, сохранить wav
python modules/tts/test_tts.py --play # то же + воспроизвести
python modules/tts/test_tts.py --text "Своё предложение"
python modules/tts/test_tts.py --voice ru-RU-DmitryNeural --play
python modules/tts/test_tts.py --rate -10 # другая скорость
python modules/tts/test_tts.py --provider piper --model models/piper/ru_RU-dmitri-medium.onnx
python modules/tts/test_tts.py --listen # интерактивно: вводишь текст — слышишь
Критерии приёмки Модуля 3:
1. Русская фраза звучит естественно, скорость на ~15% медленнее обычной.
2. Несколько предложений — с заметными паузами между ними.
3. Синтез короткой фразы (до 100 символов) — <= 3 c.
"""
from __future__ import annotations
import argparse
import sys
import time
from pathlib import Path
try:
from modules.audio_io import Player
from modules.audio_io.codec import decode_audio_bytes # noqa: F401 (использование ниже)
from modules.tts import get_provider
except ImportError:
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from modules.audio_io import Player
from modules.audio_io.codec import decode_audio_bytes # noqa: F401
from modules.tts import get_provider
SAMPLES_DIR = Path(__file__).resolve().parent / "samples"
DEFAULT_TEXT = (
"Здравствуйте! Это проверка голоса ассистента. "
"Сейчас проверим, насколько понятна речь. Погода сегодня хорошая."
)
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="Тест TTS-модуля")
p.add_argument("--text", default=DEFAULT_TEXT, help="текст для синтеза")
p.add_argument("--provider", default="edge", choices=["edge", "piper"])
p.add_argument("--voice", default="ru-RU-SvetlanaNeural",
help="голос edge-tts (ru-RU-DmitryNeural — мужской)")
p.add_argument("--rate", default=-15, help="скорость речи, %% (−15 по ТЗ)")
p.add_argument("--model", default="models/piper/ru_RU-dmitri-medium.onnx",
help="путь к .onnx модели Piper")
p.add_argument("--pause", type=float, default=0.35, help="пауза между предложениями, с")
p.add_argument("--per-sentence", action="store_true",
help="режим «предложение = запрос» (сравнение латентности)")
p.add_argument("--play", action="store_true", help="воспроизвести результат")
p.add_argument("--listen", action="store_true",
help="интерактивно: вводишь текст — слышишь звук")
p.add_argument("--save", default=str(SAMPLES_DIR / "tts_out.wav"))
return p.parse_args()
def speak_once(tts, text: str, play: bool, save: Path) -> None:
result = tts.synthesize(text)
print(result)
save = Path(save)
save.parent.mkdir(parents=True, exist_ok=True)
import soundfile as sf
sf.write(str(save), result.audio, result.samplerate)
print(f"Сохранено: {save} ({len(result.audio) / result.samplerate:.1f} c)")
if play:
player = Player()
print("Воспроизвожу...")
player.play(result.audio, result.samplerate, blocking=True)
def listen_loop(tts) -> None:
player = Player()
print("Режим «слушать»: вводи текст, Enter — озвучить, пустая строка — выход.")
while True:
text = input("текст> ").strip()
if not text:
return
result = tts.synthesize(text)
print(f" синтез {result.generation_sec:.2f} c, аудио {result.duration_sec:.1f} c")
player.play(result.audio, result.samplerate, blocking=True)
def main() -> int:
args = parse_args()
kwargs = {"sentence_pause_sec": args.pause}
if args.provider == "edge":
kwargs.update(voice=args.voice, rate=int(args.rate), per_sentence=args.per_sentence)
else:
kwargs.update(model_path=args.model)
tts = get_provider(args.provider, **kwargs)
if args.listen:
listen_loop(tts)
else:
speak_once(tts, args.text, args.play, args.save)
return 0
if __name__ == "__main__":
sys.exit(main())