FEAT: Voice Recognition (Whisper Small) for owner - v4.1

This commit is contained in:
Admin 2026-04-22 12:42:37 +04:00
parent f7c06fd471
commit 1d5752dfd4
5 changed files with 145 additions and 29 deletions

View file

@ -5,7 +5,7 @@ FROM python:3.12-slim
WORKDIR /app WORKDIR /app
# Установка системных зависимостей (если понадобятся для каких-то либ) # Установка системных зависимостей (если понадобятся для каких-то либ)
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \ ffmpeg
sqlite3 \ sqlite3 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*

58
handlers/voice.py Normal file
View 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
View file

@ -2,22 +2,18 @@
""" """
🏠 Домовой Бот Telegram-бот для чата многоквартирного дома 🏠 Домовой Бот Telegram-бот для чата многоквартирного дома
ВЕРСИЯ: v4.0 REBORN (LKM37 CORE) ВЕРСИЯ: v4.1 VOICE-REBORN
ДАТА ФИКСАЦИИ: 17 апреля 2026 г. ДАТА ФИКСАЦИИ: 21 апреля 2026 г.
СТАТУС: АКТИВИРОВАН СТАТУС: ОБНОВЛЕН
НОВОЕ В v4.0: НОВОЕ В v4.1:
- Единый стиль LKM37 (Slate & Cyan) - Распознавание голосовых сообщений и видеокружков (Whisper Small)
- Стабильное меню (Grid 2xN) - Логирование распознанного текста в БД
- Маркет 2.0 (Фото + Категории)
- SpyDetector 2.0 (Анализ токсичности)
- Взаимовыручка соседей (Neighbor Aid)
- Авто-Дайджест с подтверждением
- Матричная веб-панель
""" """
import logging import logging
import asyncio import asyncio
import sys import sys
import os
from pathlib import Path from pathlib import Path
# Добавляем путь к проекту # Добавляем путь к проекту
@ -45,10 +41,10 @@ from handlers import (
toxicity_router, profile_history_router, achievements_router, toxicity_router, profile_history_router, achievements_router,
thanks_router, events_router, initiative_group_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 handlers.mutual_aid import router as mutual_aid_router
from services.scheduler import Scheduler from services.scheduler import Scheduler
from services.web_server import WebServerService from services.web_server import WebServerService
from database.models import ScheduledPost
# Настройка логирования # Настройка логирования
logging.basicConfig( logging.basicConfig(
@ -60,30 +56,18 @@ logging.basicConfig(
] ]
) )
import os
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# МАРКЕР ЗАПУСКА БОТА
logger.info(f"!!! [STARTUP] main.py execution started. PID: {os.getpid()}")
async def on_startup(bot: Bot): 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() await init_db()
scheduler = Scheduler(bot) scheduler = Scheduler(bot)
scheduler.start() scheduler.start()
# Веб-сервер теперь запускается отдельным сервисом (domovoy-web.service)
# web_server = WebServerService(port=8000)
# web_server.start()
try: try:
await bot.send_message( await bot.send_message(
ADMIN_USER_ID, ADMIN_USER_ID,
'✅ <b>LKM37 CORE v4.0 АКТИВИРОВАН</b>\n\n' '✅ <b>LKM37 CORE v4.1 VOICE-REBORN АКТИВИРОВАН</b>\n\n'
'Система переведена в режим REBORN.\n' 'Модуль распознавания речи (Whisper Small) запущен.\n'
'Все модули стабильны.', 'Шли мне голосовухи — я их теперь "читаю"!',
parse_mode=ParseMode.HTML parse_mode=ParseMode.HTML
) )
except Exception as e: except Exception as e:
@ -102,7 +86,7 @@ async def main():
ads_router, payments_router, multicount_router, topics_router, ads_router, payments_router, multicount_router, topics_router,
toxicity_router, profile_history_router, achievements_router, toxicity_router, profile_history_router, achievements_router,
thanks_router, events_router, initiative_group_router, thanks_router, events_router, initiative_group_router,
mutual_aid_router mutual_aid_router, voice_router # Добавляем голосовой роутер
] ]
for r in routers: for r in routers:

View file

@ -10,3 +10,5 @@ fastapi>=0.109.0
uvicorn>=0.27.0 uvicorn>=0.27.0
jinja2>=3.1.3 jinja2>=3.1.3
python-multipart>=0.0.6 python-multipart>=0.0.6
faster-whisper
pydub

72
services/voice_service.py Normal file
View 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