FEAT: Voice Recognition (Whisper Small) for owner - v4.1
This commit is contained in:
parent
f7c06fd471
commit
1d5752dfd4
5 changed files with 145 additions and 29 deletions
|
|
@ -5,7 +5,7 @@ FROM python:3.12-slim
|
|||
WORKDIR /app
|
||||
|
||||
# Установка системных зависимостей (если понадобятся для каких-то либ)
|
||||
RUN apt-get update && apt-get install -y \
|
||||
RUN apt-get update && apt-get install -y \ ffmpeg
|
||||
sqlite3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
|
|
|||
58
handlers/voice.py
Normal file
58
handlers/voice.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import os
|
||||
import logging
|
||||
from aiogram import Router, F, Bot
|
||||
from aiogram.types import Message, ContentType
|
||||
from config import ADMIN_USER_ID
|
||||
from services.voice_service import VoiceService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
# Initialize VoiceService
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
db_path = os.path.join(BASE_DIR, "database", "domovoy.db")
|
||||
voice_service = VoiceService(db_path=db_path, model_size="small")
|
||||
|
||||
@router.message(F.content_type.in_({ContentType.VOICE, ContentType.VIDEO_NOTE}))
|
||||
async def handle_voice_message(message: Message, bot: Bot):
|
||||
# Только для владельца
|
||||
if message.from_user.id != ADMIN_USER_ID:
|
||||
return
|
||||
|
||||
msg_type = "VOICE" if message.voice else "VIDEO_NOTE"
|
||||
file_id = message.voice.file_id if message.voice else message.video_note.file_id
|
||||
|
||||
# Отправляем статус "печатает"
|
||||
await bot.send_chat_action(message.chat.id, "typing")
|
||||
|
||||
# Создаем папку для временных файлов если её нет
|
||||
temp_dir = os.path.join(BASE_DIR, "temp_voice")
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
|
||||
try:
|
||||
file = await bot.get_file(file_id)
|
||||
file_path = os.path.join(temp_dir, f"{file_id}.oga" if message.voice else f"{file_id}.mp4")
|
||||
await bot.download_file(file.file_path, file_path)
|
||||
|
||||
# Распознавание
|
||||
text = voice_service.transcribe(
|
||||
file_path=file_path,
|
||||
user_id=message.from_user.id,
|
||||
username=message.from_user.username or "Unknown",
|
||||
msg_type=msg_type
|
||||
)
|
||||
|
||||
# Удаляем временный файл
|
||||
if os.path.exists(file_path): os.remove(file_path)
|
||||
|
||||
if text:
|
||||
await message.reply(f"📝 <b>Распознанный текст:</b>\n\n{text}", parse_mode="HTML")
|
||||
else:
|
||||
await message.reply("🔇 Аудио сообщение пустое или не распознано.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Voice handler error: {e}")
|
||||
await message.reply(f"❌ Ошибка обработки: {e}")
|
||||
|
||||
# Export router
|
||||
voice_router = router
|
||||
40
main.py
40
main.py
|
|
@ -2,22 +2,18 @@
|
|||
"""
|
||||
🏠 Домовой Бот — Telegram-бот для чата многоквартирного дома
|
||||
|
||||
ВЕРСИЯ: v4.0 — REBORN (LKM37 CORE)
|
||||
ДАТА ФИКСАЦИИ: 17 апреля 2026 г.
|
||||
СТАТУС: ✅ АКТИВИРОВАН
|
||||
ВЕРСИЯ: v4.1 — VOICE-REBORN
|
||||
ДАТА ФИКСАЦИИ: 21 апреля 2026 г.
|
||||
СТАТУС: ✅ ОБНОВЛЕН
|
||||
|
||||
НОВОЕ В v4.0:
|
||||
- ✅ Единый стиль LKM37 (Slate & Cyan)
|
||||
- ✅ Стабильное меню (Grid 2xN)
|
||||
- ✅ Маркет 2.0 (Фото + Категории)
|
||||
- ✅ SpyDetector 2.0 (Анализ токсичности)
|
||||
- ✅ Взаимовыручка соседей (Neighbor Aid)
|
||||
- ✅ Авто-Дайджест с подтверждением
|
||||
- ✅ Матричная веб-панель
|
||||
НОВОЕ В v4.1:
|
||||
- ✅ Распознавание голосовых сообщений и видеокружков (Whisper Small)
|
||||
- ✅ Логирование распознанного текста в БД
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Добавляем путь к проекту
|
||||
|
|
@ -45,10 +41,10 @@ from handlers import (
|
|||
toxicity_router, profile_history_router, achievements_router,
|
||||
thanks_router, events_router, initiative_group_router
|
||||
)
|
||||
from handlers.voice import voice_router
|
||||
from handlers.mutual_aid import router as mutual_aid_router
|
||||
from services.scheduler import Scheduler
|
||||
from services.web_server import WebServerService
|
||||
from database.models import ScheduledPost
|
||||
|
||||
# Настройка логирования
|
||||
logging.basicConfig(
|
||||
|
|
@ -60,30 +56,18 @@ logging.basicConfig(
|
|||
]
|
||||
)
|
||||
|
||||
import os
|
||||
logger = logging.getLogger(__name__)
|
||||
# МАРКЕР ЗАПУСКА БОТА
|
||||
logger.info(f"!!! [STARTUP] main.py execution started. PID: {os.getpid()}")
|
||||
|
||||
async def on_startup(bot: Bot):
|
||||
"""Действия при запуске"""
|
||||
logger.info(f"!!! [STARTUP] on_startup triggered. PID: {os.getpid()}")
|
||||
logger.info('🚀 LKM37 CORE v4.0 запускается...')
|
||||
await init_db()
|
||||
|
||||
scheduler = Scheduler(bot)
|
||||
scheduler.start()
|
||||
|
||||
# Веб-сервер теперь запускается отдельным сервисом (domovoy-web.service)
|
||||
# web_server = WebServerService(port=8000)
|
||||
# web_server.start()
|
||||
|
||||
try:
|
||||
await bot.send_message(
|
||||
ADMIN_USER_ID,
|
||||
'✅ <b>LKM37 CORE v4.0 АКТИВИРОВАН</b>\n\n'
|
||||
'Система переведена в режим REBORN.\n'
|
||||
'Все модули стабильны.',
|
||||
'✅ <b>LKM37 CORE v4.1 VOICE-REBORN АКТИВИРОВАН</b>\n\n'
|
||||
'Модуль распознавания речи (Whisper Small) запущен.\n'
|
||||
'Шли мне голосовухи — я их теперь "читаю"!',
|
||||
parse_mode=ParseMode.HTML
|
||||
)
|
||||
except Exception as e:
|
||||
|
|
@ -102,7 +86,7 @@ async def main():
|
|||
ads_router, payments_router, multicount_router, topics_router,
|
||||
toxicity_router, profile_history_router, achievements_router,
|
||||
thanks_router, events_router, initiative_group_router,
|
||||
mutual_aid_router
|
||||
mutual_aid_router, voice_router # Добавляем голосовой роутер
|
||||
]
|
||||
|
||||
for r in routers:
|
||||
|
|
|
|||
|
|
@ -10,3 +10,5 @@ fastapi>=0.109.0
|
|||
uvicorn>=0.27.0
|
||||
jinja2>=3.1.3
|
||||
python-multipart>=0.0.6
|
||||
faster-whisper
|
||||
pydub
|
||||
|
|
|
|||
72
services/voice_service.py
Normal file
72
services/voice_service.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import os
|
||||
import logging
|
||||
from faster_whisper import WhisperModel
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
from pydub import AudioSegment
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class VoiceService:
|
||||
def __init__(self, db_path: str, model_size: str = "small"):
|
||||
self.db_path = db_path
|
||||
self.model_size = model_size
|
||||
# CPU-only optimization: int8 is fast on standard CPUs
|
||||
self.model = WhisperModel(model_size, device="cpu", compute_type="int8")
|
||||
self._init_db()
|
||||
|
||||
def _init_db(self):
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS voice_recognition_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
username TEXT,
|
||||
message_type TEXT,
|
||||
duration INTEGER,
|
||||
text TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def transcribe(self, file_path: str, user_id: int, username: str, msg_type: str) -> str:
|
||||
try:
|
||||
start_time = datetime.now()
|
||||
|
||||
# Convert to wav if needed (Telegram uses .oga/.ogg)
|
||||
wav_path = file_path.rsplit('.', 1)[0] + ".wav"
|
||||
audio = AudioSegment.from_file(file_path)
|
||||
duration = int(len(audio) / 1000)
|
||||
audio.export(wav_path, format="wav")
|
||||
|
||||
segments, info = self.model.transcribe(wav_path, beam_size=5)
|
||||
text = " ".join([segment.text for segment in segments]).strip()
|
||||
|
||||
# Clean up temp files
|
||||
if os.path.exists(wav_path): os.remove(wav_path)
|
||||
|
||||
# Log to DB
|
||||
self._log_to_db(user_id, username, msg_type, duration, text)
|
||||
|
||||
return text
|
||||
except Exception as e:
|
||||
logger.error(f"Transcription error: {e}")
|
||||
return f"[Ошибка распознавания]: {e}"
|
||||
|
||||
def _log_to_db(self, user_id, username, msg_type, duration, text):
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
INSERT INTO voice_recognition_logs (user_id, username, message_type, duration, text)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
''', (user_id, username, msg_type, duration, text))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
logger.error(f"DB Log error: {e}")
|
||||
|
||||
# Global instance will be initialized in main.py or handlers
|
||||
Loading…
Reference in a new issue