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
+137
View File
@@ -0,0 +1,137 @@
"""Тест 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())