Edit code for success run
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -27,3 +27,9 @@ Thumbs.db
|
|||||||
|
|
||||||
# Claude Code
|
# Claude Code
|
||||||
.claude/
|
.claude/
|
||||||
|
|
||||||
|
# Data
|
||||||
|
data/
|
||||||
|
|
||||||
|
# Training
|
||||||
|
training/
|
||||||
|
|||||||
48
record_wav.py
Normal file
48
record_wav.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import sounddevice as sd
|
||||||
|
import scipy.io.wavfile as wav
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# 1. Проверка аргументов командной строки
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print("Использование: python record.py <имя_модели> <positive/negative>")
|
||||||
|
print("Пример: python record.py cosmo positive")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
MODEL_NAME = sys.argv[1]
|
||||||
|
MODE = sys.argv[2]
|
||||||
|
BASE_DIR = os.path.join("data", "wakewords", MODEL_NAME, MODE)
|
||||||
|
|
||||||
|
# Создаем папку, если ее нет
|
||||||
|
if not os.path.exists(BASE_DIR):
|
||||||
|
os.makedirs(BASE_DIR)
|
||||||
|
|
||||||
|
def get_next_filename(directory):
|
||||||
|
files = [f for f in os.listdir(directory) if f.endswith('.wav')]
|
||||||
|
return f"{len(files) + 1:03d}.wav"
|
||||||
|
|
||||||
|
def record_sample():
|
||||||
|
filename = get_next_filename(BASE_DIR)
|
||||||
|
filepath = os.path.join(BASE_DIR, filename)
|
||||||
|
|
||||||
|
sample_rate = 16000
|
||||||
|
duration = 2
|
||||||
|
|
||||||
|
print(f"\n[!] Файл {filename} готов к записи.")
|
||||||
|
input("Нажмите Enter, чтобы начать запись (2 секунды)...")
|
||||||
|
|
||||||
|
print("Запись...")
|
||||||
|
recording = sd.rec(int(duration * sample_rate), samplerate=sample_rate, channels=1)
|
||||||
|
sd.wait()
|
||||||
|
wav.write(filepath, sample_rate, recording)
|
||||||
|
print(f"Сохранено в: {filepath}")
|
||||||
|
|
||||||
|
# 2. Основной цикл записи
|
||||||
|
print(f"--- Режим записи: {MODEL_NAME} / {MODE} ---")
|
||||||
|
print("Для выхода нажмите Ctrl+C")
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
record_sample()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nЗапись завершена.")
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
|
import os
|
||||||
import pyaudio
|
import pyaudio
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from .config import SILENCE_THRESHOLD, SILENCE_DURATION, MAX_DURATION, log
|
from .config import SILENCE_THRESHOLD, SILENCE_DURATION, MAX_DURATION, log
|
||||||
from .stt import transcribe
|
from .stt import transcribe
|
||||||
|
|
||||||
|
ECHO_WARMUP = float(os.getenv("ECHO_WARMUP", "0.5")) # сек пропуска в начале — гасит эхо от TTS
|
||||||
|
|
||||||
|
|
||||||
def record() -> str:
|
def record() -> str:
|
||||||
"""Запись до тишины (VAD) + STT"""
|
"""Запись до тишины (VAD) + STT. Игнорирует ECHO_WARMUP в начале."""
|
||||||
try:
|
try:
|
||||||
audio = pyaudio.PyAudio()
|
audio = pyaudio.PyAudio()
|
||||||
stream = audio.open(
|
stream = audio.open(
|
||||||
@@ -27,13 +30,13 @@ def record() -> str:
|
|||||||
speaking_started = False
|
speaking_started = False
|
||||||
max_chunks = int(16000 / 1024 * MAX_DURATION)
|
max_chunks = int(16000 / 1024 * MAX_DURATION)
|
||||||
silence_chunks_needed = int(16000 / 1024 * SILENCE_DURATION)
|
silence_chunks_needed = int(16000 / 1024 * SILENCE_DURATION)
|
||||||
warmup_chunks = int(16000 / 1024 * 0.3) # 0.3 сек — эхо звука активации
|
warmup_chunks = int(16000 / 1024 * ECHO_WARMUP)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for i in range(max_chunks):
|
for i in range(max_chunks):
|
||||||
data = stream.read(1024, exception_on_overflow=False)
|
data = stream.read(1024, exception_on_overflow=False)
|
||||||
if i < warmup_chunks:
|
if i < warmup_chunks:
|
||||||
continue # пропускаем эхо от звука активации
|
continue # гасим эхо от TTS / звука активации
|
||||||
frames.append(data)
|
frames.append(data)
|
||||||
|
|
||||||
amplitude = np.abs(np.frombuffer(data, dtype=np.int16)).mean()
|
amplitude = np.abs(np.frombuffer(data, dtype=np.int16)).mean()
|
||||||
@@ -56,52 +59,8 @@ def record() -> str:
|
|||||||
if not speaking_started:
|
if not speaking_started:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
return transcribe(frames)
|
text = transcribe(frames)
|
||||||
|
# отсекаем мусор от эха (одиночные знаки препинания, пробелы)
|
||||||
|
if not text or not text.strip() or len(text.strip()) < 2:
|
||||||
def record_with_timeout(timeout: float = 8.0) -> str:
|
|
||||||
"""Слушает timeout секунд, возвращает пусто если речи не было"""
|
|
||||||
try:
|
|
||||||
audio = pyaudio.PyAudio()
|
|
||||||
stream = audio.open(
|
|
||||||
format=pyaudio.paInt16,
|
|
||||||
channels=1,
|
|
||||||
rate=16000,
|
|
||||||
input=True,
|
|
||||||
frames_per_buffer=1024,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
log.exception("Не удалось открыть микрофон (followup)")
|
|
||||||
print(f"⚠️ Ошибка микрофона: {e}")
|
|
||||||
return ""
|
return ""
|
||||||
|
return text
|
||||||
frames = []
|
|
||||||
silent_chunks = 0
|
|
||||||
speaking_started = False
|
|
||||||
max_chunks = int(16000 / 1024 * timeout)
|
|
||||||
silence_chunks_needed = int(16000 / 1024 * SILENCE_DURATION)
|
|
||||||
|
|
||||||
try:
|
|
||||||
for _ in range(max_chunks):
|
|
||||||
data = stream.read(1024, exception_on_overflow=False)
|
|
||||||
frames.append(data)
|
|
||||||
amplitude = np.abs(np.frombuffer(data, dtype=np.int16)).mean()
|
|
||||||
|
|
||||||
if amplitude > SILENCE_THRESHOLD:
|
|
||||||
speaking_started = True
|
|
||||||
silent_chunks = 0
|
|
||||||
elif speaking_started:
|
|
||||||
silent_chunks += 1
|
|
||||||
if silent_chunks >= silence_chunks_needed:
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
log.exception("Ошибка при записи аудио (followup)")
|
|
||||||
print(f"⚠️ Ошибка записи: {e}")
|
|
||||||
finally:
|
|
||||||
stream.stop_stream()
|
|
||||||
audio.terminate()
|
|
||||||
|
|
||||||
if not speaking_started:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
return transcribe(frames)
|
|
||||||
|
|||||||
@@ -79,21 +79,21 @@ def ask_agent_stream(text: str, conv: "Conversation | None" = None, agent_id: st
|
|||||||
log.exception("Gateway недоступен")
|
log.exception("Gateway недоступен")
|
||||||
msg = "Не могу связаться с сервером, попробуй ещё раз."
|
msg = "Не могу связаться с сервером, попробуй ещё раз."
|
||||||
print(f"⚠️ {msg}")
|
print(f"⚠️ {msg}")
|
||||||
#play_error_sound()
|
play_error_sound()
|
||||||
speak(msg, agent_id)
|
speak(msg, agent_id)
|
||||||
return msg
|
return msg
|
||||||
except requests.Timeout:
|
except requests.Timeout:
|
||||||
log.exception("Gateway таймаут")
|
log.exception("Gateway таймаут")
|
||||||
msg = "Сервер не ответил вовремя, попробуй ещё раз."
|
msg = "Сервер не ответил вовремя, попробуй ещё раз."
|
||||||
print(f"⚠️ {msg}")
|
print(f"⚠️ {msg}")
|
||||||
#play_error_sound()
|
play_error_sound()
|
||||||
speak(msg, agent_id)
|
speak(msg, agent_id)
|
||||||
return msg
|
return msg
|
||||||
except requests.HTTPError:
|
except requests.HTTPError:
|
||||||
log.exception(f"Gateway HTTP ошибка {resp.status_code}")
|
log.exception(f"Gateway HTTP ошибка {resp.status_code}")
|
||||||
msg = "Ошибка сервера, попробуй ещё раз."
|
msg = "Ошибка сервера, попробуй ещё раз."
|
||||||
print(f"⚠️ Gateway {resp.status_code}: {resp.text}")
|
print(f"⚠️ Gateway {resp.status_code}: {resp.text}")
|
||||||
#play_error_sound()
|
play_error_sound()
|
||||||
speak(msg, agent_id)
|
speak(msg, agent_id)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .config import GATEWAY_URL, AGENT, FOLLOWUP_TIMEOUT, log
|
from .config import GATEWAY_URL, AGENT, log
|
||||||
from .audio import record, record_with_timeout
|
from .audio import record
|
||||||
from .tts import play_activation_sound, speak, stop_speaking
|
from .tts import speak, stop_speaking
|
||||||
from .llm import ask_agent_stream, Conversation, is_reset_command
|
from .llm import ask_agent_stream, Conversation, is_reset_command
|
||||||
|
|
||||||
# Персистентные сессии — одна на день для каждого агента
|
# Персистентные сессии — одна на день для каждого агента
|
||||||
@@ -31,6 +31,29 @@ def _handle_reset(text: str, agent_id: str) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _conversation_loop(agent_id: str, agent_name: str = "Cosmo"):
|
||||||
|
"""Основной цикл диалога — слушает и отвечает пока пользователь говорит.
|
||||||
|
Выходит когда в течение MAX_DURATION не было речи."""
|
||||||
|
conv = _get_session(agent_id)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
text = record()
|
||||||
|
if not text:
|
||||||
|
print(f"😴 Тишина, жду активации...\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"📝 Ты → {agent_name}: {text}")
|
||||||
|
|
||||||
|
if _handle_reset(text, agent_id):
|
||||||
|
conv = _get_session(agent_id)
|
||||||
|
continue
|
||||||
|
|
||||||
|
response = ask_agent_stream(text, conv=conv, agent_id=agent_id)
|
||||||
|
print(f"🤖 {agent_name}: {response}\n")
|
||||||
|
# после ответа — следующая итерация с новым record()
|
||||||
|
# record() сам гасит эхо через ECHO_WARMUP
|
||||||
|
|
||||||
|
|
||||||
def run_with_enter():
|
def run_with_enter():
|
||||||
print("\n🦞 Cosmo Satellite запущен (режим: Enter для активации)")
|
print("\n🦞 Cosmo Satellite запущен (режим: Enter для активации)")
|
||||||
print(f" Gateway : {GATEWAY_URL}")
|
print(f" Gateway : {GATEWAY_URL}")
|
||||||
@@ -40,34 +63,8 @@ def run_with_enter():
|
|||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
input("⏎ Нажми Enter и говори...")
|
input("⏎ Нажми Enter и говори...")
|
||||||
stop_speaking() # barge-in: прервать если ещё говорит
|
stop_speaking() # barge-in
|
||||||
play_activation_sound()
|
_conversation_loop("cosmo", "Cosmo")
|
||||||
|
|
||||||
conv = _get_session("cosmo")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
text = record()
|
|
||||||
if not text:
|
|
||||||
print("⚠️ Ничего не распознано")
|
|
||||||
break
|
|
||||||
|
|
||||||
print(f"📝 Ты: {text}")
|
|
||||||
|
|
||||||
if _handle_reset(text, "cosmo"):
|
|
||||||
conv = _get_session("cosmo")
|
|
||||||
break
|
|
||||||
|
|
||||||
response = ask_agent_stream(text, conv=conv)
|
|
||||||
print(f"🤖 Cosmo: {response}\n")
|
|
||||||
|
|
||||||
print(f"👂 Слушаю продолжение ({int(FOLLOWUP_TIMEOUT)} сек)...")
|
|
||||||
followup = record_with_timeout(timeout=FOLLOWUP_TIMEOUT)
|
|
||||||
|
|
||||||
if not followup:
|
|
||||||
print("😴 Нет продолжения, жду активации...\n")
|
|
||||||
break
|
|
||||||
|
|
||||||
text = followup
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n👋 Выход")
|
print("\n👋 Выход")
|
||||||
@@ -138,23 +135,13 @@ def run_with_porcupine():
|
|||||||
if keyword_index >= 0:
|
if keyword_index >= 0:
|
||||||
agent_id = wake_word_map[keyword_index]
|
agent_id = wake_word_map[keyword_index]
|
||||||
agent_name = AGENTS[agent_id]["name"]
|
agent_name = AGENTS[agent_id]["name"]
|
||||||
stop_speaking() # barge-in: прервать если ещё говорит
|
stop_speaking() # barge-in
|
||||||
print(f"✅ Услышал '{agent_name}'!")
|
print(f"✅ Услышал '{agent_name}'!")
|
||||||
play_activation_sound()
|
|
||||||
|
|
||||||
conv = _get_session(agent_id)
|
# отпускаем микрофон на время диалога
|
||||||
|
stream.stop_stream()
|
||||||
text = record()
|
_conversation_loop(agent_id, agent_name)
|
||||||
if not text:
|
stream.start_stream()
|
||||||
continue
|
|
||||||
|
|
||||||
print(f"📝 Ты → {agent_name}: {text}")
|
|
||||||
|
|
||||||
if _handle_reset(text, agent_id):
|
|
||||||
continue
|
|
||||||
|
|
||||||
response = ask_agent_stream(text, conv=conv, agent_id=agent_id)
|
|
||||||
print(f"🤖 {agent_name}: {response}\n")
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise
|
raise
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
from elevenlabs import VoiceSettings
|
||||||
|
|
||||||
from .config import AUDIO_SINK, AGENTS, log
|
from .config import AUDIO_SINK, AGENTS, log
|
||||||
|
|
||||||
@@ -68,11 +69,20 @@ def _speak_elevenlabs(text: str, agent_id: str):
|
|||||||
print(f"⚠️ tts_voice не задан для {agent_id}")
|
print(f"⚠️ tts_voice не задан для {agent_id}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
voice_settings = VoiceSettings(
|
||||||
|
stability=0.5,
|
||||||
|
similarity_boost=0.75,
|
||||||
|
style=0.0,
|
||||||
|
use_speaker_boost=True,
|
||||||
|
speed=1.1 # Значение от 0.7 до 1.2. 1.1 — это ускорение на 10%
|
||||||
|
)
|
||||||
|
|
||||||
audio_stream = client.text_to_speech.convert(
|
audio_stream = client.text_to_speech.convert(
|
||||||
text=text,
|
text=text,
|
||||||
voice_id=voice_id,
|
voice_id=voice_id,
|
||||||
model_id=ELEVENLABS_MODEL,
|
model_id=ELEVENLABS_MODEL,
|
||||||
output_format="mp3_44100_128",
|
output_format="mp3_44100_128",
|
||||||
|
voice_settings=voice_settings
|
||||||
)
|
)
|
||||||
|
|
||||||
with _process_lock:
|
with _process_lock:
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user