Initial commit: полный проект my_speaker

This commit is contained in:
2025-12-25 12:16:19 +03:00
commit 8279fbb35f
85 changed files with 6027 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
# API package initialization
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+92
View File
@@ -0,0 +1,92 @@
# app/api/auth.py
from fastapi import APIRouter, Header, Query, HTTPException, Depends
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from pydantic import BaseModel
from app.core.config import get_settings
from app.core.security import create_session_token, validate_session_token, remove_session_token, ACTIVE_SESSIONS
from typing import Optional
router = APIRouter(prefix="/api/auth", tags=["auth"])
security = HTTPBasic()
class LoginResponse(BaseModel):
access_token: str
token_type: str = "bearer"
class UserResponse(BaseModel):
username: str
is_authenticated: bool
class LoginRequest(BaseModel):
username: str
password: str
@router.post("/login", response_model=LoginResponse)
async def login(login_data: LoginRequest):
"""Базовая авторизация по логину и паролю"""
settings = get_settings()
if login_data.username != settings.auth.username or login_data.password != settings.auth.password:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Неверные учетные данные",
headers={"WWW-Authenticate": "Basic"},
)
# Создаем сессионный токен (без JWT, просто уникальный идентификатор)
token = create_session_token(login_data.username)
return LoginResponse(access_token=token)
@router.get("/me", response_model=UserResponse)
async def get_current_user(
authorization: Optional[str] = Header(None), # ← Указываем что это Header
token_param: Optional[str] = Query(None, alias="token") # ← Указываем что это Query
):
"""Получение информации о текущем пользователе"""
token_value = None
# Проверяем заголовок Authorization
if authorization and authorization.startswith("Bearer "):
token_value = authorization.replace("Bearer ", "")
print(f"✓ Токен из заголовка: {token_value}")
# Если токен не найден в заголовке, проверяем параметр запроса
elif token_param:
token_value = token_param
print(f"✓ Токен из query параметра: {token_value}")
# Для обратной совместимости проверяем, если authorization передан как параметр запроса
elif authorization and not authorization.startswith("Bearer "):
token_value = authorization
print(f"✓ Токен из authorization (без Bearer): {token_value}")
print(f"Проверка токена: {token_value}")
print(f"Активные сессии: {list(ACTIVE_SESSIONS.keys())}")
# Проверяем токен в активных сессиях
if token_value and token_value in ACTIVE_SESSIONS:
username = ACTIVE_SESSIONS[token_value]
print(f"✓ Токен найден! Пользователь: {username}")
return UserResponse(username=username, is_authenticated=True)
print(f"✗ Токен не найден в активных сессиях")
return UserResponse(username="", is_authenticated=False)
@router.post("/logout")
async def logout(authorization: Optional[str] = None, token_param: Optional[str] = None):
"""Выход из системы"""
token_value = None
# Проверяем заголовок Authorization
if authorization and authorization.startswith("Bearer "):
token_value = authorization.replace("Bearer ", "")
# Если токен не найден в заголовке, проверяем параметр запроса
elif token_param:
token_value = token_param
# Для обратной совместимости проверяем, если authorization передан как параметр запроса
elif authorization and not authorization.startswith("Bearer "):
token_value = authorization
if token_value and token_value in ACTIVE_SESSIONS:
remove_session_token(token_value)
return {"status": "success", "message": "Выход выполнен успешно"}
return {"status": "success", "message": "Нет активной сессии"}
+110
View File
@@ -0,0 +1,110 @@
# app/api/dashboard.py
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel
from typing import List, Dict, Any, Optional
from app.core.security import require_auth
from app.core.vad import get_vad_instance, get_stt_instance
from app.core.status import status_tracker
from app.core.system_monitor import get_system_load
router = APIRouter(prefix="/api/dashboard", tags=["dashboard"])
class SpeechData(BaseModel):
state: str
speech_prob: float
speech_threshold: float # Добавлено: порог обнаружения речи
energy: float
energy_threshold: float
speech_detected: bool # Добавлено: признак обнаружения речи
continuous_frames: int # Добавлено: количество непрерывных кадров речи
gain: float # Добавлено: текущий коэффициент усиления
noise_floor: float # Добавлено: уровень шума
class RecognizedPhrase(BaseModel):
text: str
timestamp: str
action: str = None
class SystemLoad(BaseModel): # Добавлено: модель для системной нагрузки
cpu_percent: float
memory_percent: float
temperature: float
@router.get("/speech-data", response_model=SpeechData)
async def get_speech_data(request: Request, token: str = Depends(require_auth)):
"""Получение текущих данных о речи и энергии, включая расширенные метрики VAD и STT"""
try:
vad = get_vad_instance(request)
if not vad:
return SpeechData(
state=status_tracker.get_status(),
speech_prob=0.0,
speech_threshold=0.0,
energy=0,
energy_threshold=0,
speech_detected=False,
continuous_frames=0,
gain=1.0,
noise_floor=0.0,
)
# Получаем данные из last_result VAD
last_result = vad.last_result
# Дополнительные атрибуты VAD (на основе класса VADPolling)
gain = vad.current_gain
noise_floor = vad.noise_floor
continuous_frames = last_result.get('continuous_frames', vad.continuous_speech_frames) # Если есть в last_result или атрибуте
return SpeechData(
state=status_tracker.get_status(),
speech_prob=float(last_result.get('speech_ratio', 0.0)),
speech_threshold=float(last_result.get('speech_threshold', 0.0)),
energy=float(last_result.get('energy', 0)),
energy_threshold=float(last_result.get('energy_threshold', 0)),
speech_detected=bool(last_result.get('speech_detected', False)),
continuous_frames=int(continuous_frames),
gain=float(gain),
noise_floor=float(noise_floor),
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/sys-monitor", response_model=SystemLoad) # Добавлено: новый эндпоинт
async def get_sys_monitor(token: str = Depends(require_auth)):
"""Получение данных о нагрузке системы (CPU, память, температура)"""
try:
load_data = get_system_load()
return SystemLoad(**load_data)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/recent-phrases", response_model=List[RecognizedPhrase])
async def get_recent_phrases(request: Request, limit: int = 10, token: str = Depends(require_auth)):
"""Получение последних распознанных фраз"""
try:
stt = get_stt_instance(request)
if not stt or not hasattr(stt, 'get_history'):
from app.core.history import get_recognition_history
phrases = get_recognition_history(limit)
else:
phrases = stt.get_history(limit)
return phrases
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/stats", response_model=Dict[str, Any])
async def get_stats(request: Request, token: str = Depends(require_auth)):
"""Получение статистики работы системы"""
try:
# Здесь будет логика получения статистики работы системы
# Например, количество распознанных команд, время работы и т.д.
return {
"uptime": "0:00:00", # Будет заменено на реальное время работы
"recognized_phrases_count": 0,
"commands_executed_count": 0,
"system_load": 0.0
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
+11
View File
@@ -0,0 +1,11 @@
# app/api/router.py
from fastapi import APIRouter
from app.api import auth, dashboard, settings, session
api_router = APIRouter()
# Подключаем все маршруты API
api_router.include_router(auth.router)
api_router.include_router(dashboard.router)
api_router.include_router(settings.router)
api_router.include_router(session.router)
+35
View File
@@ -0,0 +1,35 @@
# app/api/session.py
from fastapi import APIRouter, Depends, Request
from pydantic import BaseModel
from typing import Optional
from app.core.security import require_auth
router = APIRouter(prefix="/session", tags=["session"])
# Глобальная переменная для хранения экземпляра SessionManager
_session_manager = None
def set_session_manager(session_manager):
"""Устанавливает глобальный экземпляр SessionManager"""
global _session_manager
_session_manager = session_manager
def get_session_manager():
"""Возвращает глобальный экземпляр SessionManager"""
return _session_manager
class SessionStatus(BaseModel):
"""Модель для статуса сессии"""
active: bool
time_left: float
start_time: Optional[str] = None
end_time: Optional[str] = None
@router.get("/status", response_model=SessionStatus)
async def get_session_status(request: Request, token: str = Depends(require_auth)):
"""Получить текущий статус сессии"""
session_manager = get_session_manager()
if not session_manager:
return SessionStatus(active=False, time_left=0)
return session_manager.get_session_status()
+204
View File
@@ -0,0 +1,204 @@
# app/api/settings.py
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel
from typing import Dict, Any, Optional
from app.core.security import require_auth
from app.core.config import get_settings, save_settings
from app.core.vad import restart_vad_and_stt
router = APIRouter(prefix="/api/settings", tags=["settings"])
class VADSettings(BaseModel):
mode: int = 1
sample_rate: int = 16000
frame_duration_ms: int = 30
ring_buffer_size: int = 20
speech_threshold: float = 0.5
energy_threshold: int = 1000
min_speech_duration_ms: int = 300
record_stop_delay_sec: float = 1.0
prebuffer_duration_sec: float = 1.0
max_record_duration_sec: float = 5.0
target_rms: float = 2000.0
max_gain: float = 5.0
noise_reduction_factor: float = 1.5
noise_update_alpha: float = 0.05
min_rms_threshold_factor: float = 2.0
gain_smoothing_alpha: float = 0.1
adaptive_energy_std_factor: float = 2.5
adaptive_energy: bool = True
noise_reduction_enabled: bool = True
auto_gain_enabled: bool = True
record_on_speech: bool = True
class SystemSettings(BaseModel):
history_maxlen: int = 100
polling_interval_ms: int = 500
logging_level: str = "INFO"
log_file: str = "logs/app.log"
log_save_file: bool = True
class STTSettings(BaseModel):
model_path: str = ""
class CommandsSettings(BaseModel):
commands_file: str = ""
class AuthSettings(BaseModel):
username: str = "admin"
password: str = "admin"
class TTSSettings(BaseModel):
host: str = "192.168.200.81"
port: int = 10200
timeout: int = 30
pause_duration: float = 0.5
post_stop_wait: float = 1.0
voice: str = "ru_RU-ruslan-medium"
save_TTS_files: bool = False
class AudioSettings(BaseModel):
playback_device: int = 2
capture_device: int = 2
playback_volume: int = 0
capture_volume: int = 90
class AllSettings(BaseModel):
vad: VADSettings
system: SystemSettings
stt: STTSettings
commands: CommandsSettings
auth: AuthSettings
tts: TTSSettings
audio_settings: AudioSettings
@router.get("", response_model=AllSettings)
async def get_all_settings(token: str = Depends(require_auth)):
"""Получение всех настроек"""
try:
config = get_settings()
return AllSettings(
vad=VADSettings(**config.vad.dict()),
system=SystemSettings(**config.system.dict()),
stt=STTSettings(**config.stt.dict()),
commands=CommandsSettings(**config.commands.dict()),
auth=AuthSettings(**config.auth.dict()),
tts=TTSSettings(**config.tts.dict()),
audio_settings=AudioSettings(**config.audio_settings.dict())
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/vad", response_model=VADSettings)
async def update_vad_settings(settings: VADSettings, token: str = Depends(require_auth)):
"""Обновление настроек VAD"""
try:
config = get_settings()
settings_dict = settings.dict()
for key, value in settings_dict.items():
setattr(config.vad, key, value)
save_settings(config)
return VADSettings(**config.vad.dict())
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/system", response_model=SystemSettings)
async def update_system_settings(settings: SystemSettings, token: str = Depends(require_auth)):
"""Обновление системных настроек"""
try:
config = get_settings()
settings_dict = settings.dict()
for key, value in settings_dict.items():
setattr(config.system, key, value)
save_settings(config)
return SystemSettings(**config.system.dict())
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/stt", response_model=STTSettings)
async def update_stt_settings(settings: STTSettings, token: str = Depends(require_auth)):
"""Обновление настроек STT"""
try:
config = get_settings()
settings_dict = settings.dict()
for key, value in settings_dict.items():
setattr(config.stt, key, value)
save_settings(config)
return STTSettings(**config.stt.dict())
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/commands", response_model=CommandsSettings)
async def update_commands_settings(settings: CommandsSettings, token: str = Depends(require_auth)):
"""Обновление настроек команд"""
try:
config = get_settings()
settings_dict = settings.dict()
for key, value in settings_dict.items():
setattr(config.commands, key, value)
save_settings(config)
return CommandsSettings(**config.commands.dict())
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/auth", response_model=AuthSettings)
async def update_auth_settings(settings: AuthSettings, token: str = Depends(require_auth)):
"""Обновление настроек авторизации"""
try:
config = get_settings()
settings_dict = settings.dict()
for key, value in settings_dict.items():
setattr(config.auth, key, value)
save_settings(config)
return AuthSettings(**config.auth.dict())
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/tts", response_model=TTSSettings)
async def update_tts_settings(settings: TTSSettings, token: str = Depends(require_auth)):
"""Обновление настроек TTS"""
try:
config = get_settings()
settings_dict = settings.dict()
for key, value in settings_dict.items():
setattr(config.tts, key, value)
save_settings(config)
return TTSSettings(**config.tts.dict())
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/audio_settings", response_model=AudioSettings)
async def update_audio_settings(settings: AudioSettings, token: str = Depends(require_auth)):
"""Обновление настроек аудио"""
try:
from volume import set_volume
config = get_settings()
old_settings = config.audio_settings.dict()
settings_dict = settings.dict()
# Обновляем настройки в конфигурации
for key, value in settings_dict.items():
setattr(config.audio_settings, key, value)
# Применяем настройки громкости, если они изменились
if old_settings['playback_volume'] != settings_dict['playback_volume']:
set_volume(settings_dict['playback_device'], settings_dict['playback_volume'], mixer_type='Playback')
if old_settings['capture_volume'] != settings_dict['capture_volume']:
set_volume(settings_dict['capture_device'], settings_dict['capture_volume'], mixer_type='Capture')
save_settings(config)
return AudioSettings(**config.audio_settings.dict())
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/restart")
async def restart_vad(request: Request, token: str = Depends(require_auth)):
"""Перезапуск VAD и STT с новыми настройками"""
try:
restart_vad_and_stt(request)
return {"status": "success", "message": "VAD и STT перезапущены"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))