Initial commit: полный проект my_speaker
This commit is contained in:
@@ -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.
@@ -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": "Нет активной сессии"}
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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))
|
||||
Reference in New Issue
Block a user