"""Тест 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 # лёгкая модель для слабого ПК python modules/stt/test_stt.py --repeat 3 # замерить скорость несколькими прогонами Критерий приёмки: фраза распознана верно, обработка <= 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())