Initial commit: Cosmo voice assistant
Полностью локальный голосовой ассистент на Python. Стек: - Wake word: openWakeWord (onnxruntime) - STT: RealtimeSTT + faster-whisper + Silero VAD (CUDA) - LLM-агент: smolagents ToolCallingAgent + Ollama qwen2.5:7b - TTS: Silero V4 (torch.hub) + sounddevice - Shell: Git Bash (Windows) / bash (macOS) Поддерживает Windows и macOS. Агент с памятью и tool calling — находит программы самостоятельно, запоминает пути, выполняет произвольные shell-команды. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
87
cosmo/transcriber.py
Normal file
87
cosmo/transcriber.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
STT модуль на базе RealtimeSTT.
|
||||
Использует faster-whisper + Silero VAD под капотом.
|
||||
Поддерживает стриминг — partial transcriptions во время речи.
|
||||
"""
|
||||
|
||||
import threading
|
||||
from RealtimeSTT import AudioToTextRecorder
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class Transcriber:
|
||||
def __init__(self, config: dict):
|
||||
whisper_cfg = config["whisper"]
|
||||
audio_cfg = config["audio"]
|
||||
|
||||
self._recorder: AudioToTextRecorder | None = None
|
||||
self._config = {
|
||||
"model": whisper_cfg["model_size"],
|
||||
"language": whisper_cfg["language"],
|
||||
"device": whisper_cfg["device"],
|
||||
"compute_type": whisper_cfg["compute_type"],
|
||||
# Silero VAD параметры
|
||||
"silero_sensitivity": 0.4,
|
||||
"webrtc_sensitivity": 3,
|
||||
"post_speech_silence_duration": audio_cfg["silence_duration"],
|
||||
"min_length_of_recording": 0.3,
|
||||
"min_gap_between_recordings": 0.01,
|
||||
# Отключаем wake word в RealtimeSTT — используем свой
|
||||
"wakeword_backend": "none",
|
||||
# Не запускать в режиме непрерывного прослушивания
|
||||
"use_microphone": True,
|
||||
"spinner": False,
|
||||
"level": 0, # минимальный лог уровень внутри RealtimeSTT
|
||||
}
|
||||
|
||||
logger.info(
|
||||
f"Инициализирую RealtimeSTT: модель={whisper_cfg['model_size']}, "
|
||||
f"device={whisper_cfg['device']}, compute={whisper_cfg['compute_type']}"
|
||||
)
|
||||
self._init_recorder()
|
||||
|
||||
def _init_recorder(self):
|
||||
try:
|
||||
self._recorder = AudioToTextRecorder(**self._config)
|
||||
logger.info("RealtimeSTT готов")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка инициализации RealtimeSTT: {e}")
|
||||
raise
|
||||
|
||||
def record_and_transcribe(self, on_partial: callable = None) -> str:
|
||||
"""
|
||||
Записывает команду и транскрибирует.
|
||||
on_partial(text) — опциональный колбэк для частичных результатов.
|
||||
Возвращает финальный текст.
|
||||
"""
|
||||
if self._recorder is None:
|
||||
self._init_recorder()
|
||||
|
||||
result_holder = []
|
||||
done_event = threading.Event()
|
||||
|
||||
def on_text(text: str):
|
||||
result_holder.append(text)
|
||||
done_event.set()
|
||||
|
||||
# Partial results — показываем что слышим в реальном времени
|
||||
if on_partial:
|
||||
self._recorder.on_realtime_transcription_update = on_partial
|
||||
|
||||
logger.info("Слушаю команду...")
|
||||
self._recorder.text(on_text)
|
||||
done_event.wait(timeout=12.0)
|
||||
|
||||
text = result_holder[0].strip() if result_holder else ""
|
||||
if text:
|
||||
logger.info(f"Транскрипция: '{text}'")
|
||||
else:
|
||||
logger.info("Команда не распознана (тишина или таймаут)")
|
||||
return text
|
||||
|
||||
def shutdown(self):
|
||||
if self._recorder:
|
||||
try:
|
||||
self._recorder.shutdown()
|
||||
except Exception:
|
||||
pass
|
||||
Reference in New Issue
Block a user