From 184cf44fdf7e7d782a698b5f7a215a48920eccd0 Mon Sep 17 00:00:00 2001 From: Admin Date: Fri, 4 Sep 2026 15:28:54 +0400 Subject: [PATCH] feat(voice): integrate Janis Joplin XTTS-v2 zero-shot voice cloning on MacMini M4 --- docker-compose.yml | 2 ++ handlers/voice.py | 45 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3992012..cd8761e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,7 @@ services: - ./exports:/app/exports - ./data:/app/data - ./handlers:/app/handlers + - ./services:/app/services - ../libs/swarmlib:/app/swarmlib:ro - /home/matrixhasyou/domovoy_google_creds.json:/home/matrixhasyou/domovoy_google_creds.json:ro - /home/matrixhasyou/mutt:/home/matrixhasyou/mutt:rw @@ -45,6 +46,7 @@ services: - ./exports:/app/exports - ./data:/app/data - ./handlers:/app/handlers + - ./services:/app/services - ../libs/swarmlib:/app/swarmlib:ro - /home/matrixhasyou/domovoy_google_creds.json:/home/matrixhasyou/domovoy_google_creds.json:ro - /home/matrixhasyou/mutt:/home/matrixhasyou/mutt:rw diff --git a/handlers/voice.py b/handlers/voice.py index 43033e0..50f8890 100644 --- a/handlers/voice.py +++ b/handlers/voice.py @@ -12,6 +12,7 @@ from aiogram.types import Message, BufferedInputFile from config import ADMIN_USER_ID, DATABASE_PATH import google.generativeai as genai import edge_tts +import aiohttp logger = logging.getLogger(__name__) router = Router() @@ -113,7 +114,7 @@ def log_voice_to_db(user_id, username, msg_type, duration, text): logger.error(f"❌ DB Log error: {e}") async def text_to_speech_bytes(text: str) -> bytes: - """Синтез качественного женского голосового ответа на русском через Edge TTS с глубоким тембром""" + """Синтез голоса Дженис Джоплин через локальный XTTS-v2 на MacMini M4 (с фоллбэком на Edge TTS)""" match = re.search(r"(.*?)", text, re.DOTALL | re.IGNORECASE) if match: speech_text = match.group(1).strip() @@ -128,15 +129,39 @@ async def text_to_speech_bytes(text: str) -> bytes: if not speech_text: speech_text = "Приняла, Александр. Всё проанализировано и готово." - communicate = edge_tts.Communicate(speech_text, "ru-RU-SvetlanaNeural", pitch="-8Hz", rate="-3%") - out_path = f"/tmp/tts_{os.getpid()}_{int(datetime.now().timestamp())}.mp3" - await communicate.save(out_path) - audio_bytes = b"" - if os.path.exists(out_path): - with open(out_path, "rb") as f: - audio_bytes = f.read() - os.remove(out_path) - return audio_bytes + # 1. Приоритет: локальный синтез голосом Дженис Джоплин на MacMini M4 (XTTS-v2 Apple Metal) + try: + timeout = aiohttp.ClientTimeout(total=20) + async with aiohttp.ClientSession(timeout=timeout) as session: + payload = { + "text": speech_text, + "language": "ru", + "speaker_wav": "/Users/matrixhasyou/janis_ref_15s.wav" + } + async with session.post("http://192.168.10.167:8050/tts", json=payload) as resp: + if resp.status == 200: + data = await resp.read() + if len(data) > 1000: + logger.info(f"🎤 Голос Дженис Джоплин (XTTS-v2 MacMini) успешно синтезирован ({len(data)} байт)") + return data + logger.warning(f"XTTS вернул HTTP {resp.status}, переход на Edge TTS") + except Exception as e: + logger.warning(f"XTTS на MacMini недоступен ({e}), переход на Edge TTS") + + # 2. Фоллбэк: Edge TTS (ru-RU-SvetlanaNeural) + try: + communicate = edge_tts.Communicate(speech_text, "ru-RU-SvetlanaNeural", pitch="-8Hz", rate="-3%") + out_path = f"/tmp/tts_{os.getpid()}_{int(datetime.now().timestamp())}.mp3" + await communicate.save(out_path) + audio_bytes = b"" + if os.path.exists(out_path): + with open(out_path, "rb") as f: + audio_bytes = f.read() + os.remove(out_path) + return audio_bytes + except Exception as e: + logger.error(f"Ошибка Edge TTS: {e}") + return b"" def process_audio_with_gemini(audio_path: str) -> str: """Прямое транскрибирование аудио через Gemini Multimodal Audio API"""