107 lines
4.7 KiB
Python
107 lines
4.7 KiB
Python
"""Тест 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()) |