Files
2026-09-11 19:47:15 +03:00

137 lines
5.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Тест STT-модуля (Модуль 1).
Запуск:
python modules/stt/test_stt.py --make-sample # сгенерировать sample.mp3 (edge-tts) и распознать
python modules/stt/test_stt.py modules/stt/samples/sample.mp3
python modules/stt/test_stt.py --record 5 # 5 секунд с микрофона → распознать
python modules/stt/test_stt.py --model base <file> # лёгкая модель для слабого ПК
python modules/stt/test_stt.py --repeat 3 <file> # замерить скорость несколькими прогонами
Критерий приёмки: фраза распознана верно, обработка <= 3 c на фразе ~5 c.
"""
from __future__ import annotations
import argparse
import asyncio
import sys
import time
from pathlib import Path
from typing import Optional
import numpy as np
try:
from modules.stt import get_provider
except ImportError: # запуск как обычного скрипта: добавляем корень проекта в путь
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from modules.stt import get_provider
SAMPLES_DIR = Path(__file__).resolve().parent / "samples"
SAMPLE_PHRASE = (
"Привет! Это тестовый голосовой ассистент. "
"Сегодня хорошая погода, и я хочу выпить чаю."
)
DEFAULT_VOICE = "ru-RU-SvetlanaNeural"
def make_sample(path: Path, voice: str) -> None:
"""Синтез русской фразы через edge-tts — удобный источник тестового аудио."""
import edge_tts
path.parent.mkdir(parents=True, exist_ok=True)
async def _save() -> None:
await edge_tts.Communicate(SAMPLE_PHRASE, voice).save(str(path))
print(f"Генерирую {path.name} (голос {voice})...")
asyncio.run(_save())
def record(seconds: int, sample_rate: int = 16000) -> np.ndarray:
"""Запись с микрофона по умолчанию → float32 mono, 16 кГц."""
import sounddevice as sd
import soundfile as sf
print(f"\nЗапись {seconds} с. Приготовьтесь:")
for i in (3, 2, 1):
print(f" {i}...")
time.sleep(0.6)
print(" >>> ГОВОРИТЕ <<<")
raw = sd.rec(int(seconds * sample_rate), samplerate=sample_rate, channels=1, dtype="int16")
sd.wait()
audio = raw.astype(np.float32) / 32768.0
audio = audio.mean(axis=1) # (frames, 1) → mono
wav = SAMPLES_DIR / "recording.wav"
sf.write(str(wav), audio, sample_rate)
print(f"Сохранено: {wav}")
return audio
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="Тест STT-модуля (faster-whisper)")
p.add_argument("audio", nargs="?", help="аудио-файл (wav/mp3/flac)")
p.add_argument("--make-sample", action="store_true",
help="сгенерировать sample.mp3 (edge-tts) и распознать его")
p.add_argument("--voice", default=DEFAULT_VOICE, help="голос edge-tts для --make-sample")
p.add_argument("--record", type=int, default=0, metavar="SEC",
help="записать SEC секунд с микрофона и распознать")
p.add_argument("--model", default="small", help="tiny | base | small | medium")
p.add_argument("--device", default="cpu", help="cpu | cuda")
p.add_argument("--compute", default="int8", help="int8 | float16 | float32")
p.add_argument("--beam", type=int, default=1, help="beam size (1 = быстрее)")
p.add_argument("--no-vad", action="store_true", help="не отрезать тишину")
p.add_argument("--repeat", type=int, default=1, help="число прогонов для замера")
return p.parse_args()
def main() -> int:
args = parse_args()
audio_array: Optional[np.ndarray] = None
target: Optional[Path] = None
if args.record:
audio_array = record(args.record)
elif args.make_sample:
target = SAMPLES_DIR / "sample.mp3"
if not target.exists():
make_sample(target, args.voice)
else:
print(f"Использую существующий {target}")
elif args.audio:
target = Path(args.audio)
else:
print(__doc__)
return 1
print(f"\nЗагружаю модель {args.model!r} ({args.compute}, {args.device})...")
print("Первый запуск скачивает модель с HuggingFace (small ≈ 460 МБ), это нормально.")
stt = get_provider(
"faster-whisper",
model_size=args.model,
device=args.device,
compute_type=args.compute,
beam_size=args.beam,
vad_filter=not args.no_vad,
)
print(f"Модель готова за {stt.load_sec:.1f} с.\n")
results = []
for i in range(1, args.repeat + 1):
source = audio_array if audio_array is not None else target
res = stt.transcribe(source)
results.append(res)
print(f"[прогон {i}/{args.repeat}] {res}")
best = min(results, key=lambda r: r.processing_sec)
print("\n=== ИТОГ ===")
print(f"Текст: {best.text or '(пусто — нечего распознавать)'}")
print(f"Аудио {best.duration_sec:.1f} c → обработка {best.processing_sec:.2f} c "
f"(RTF {best.realtime_factor:.2f})")
print(f"Язык: {best.language} (уверенность {best.language_probability:.2f})")
passed = bool(best.text.strip())
print("ПРИЁМКА:", "ПРОЙДЕНА ✅" if passed else "ПРОВАЛЕНА ❌ (пустой текст)")
return 0 if passed else 2
if __name__ == "__main__":
sys.exit(main())