# app/core/config.py import os import logging from typing import Dict, Any, Optional from pydantic import BaseModel, Field from ruamel.yaml import YAML from functools import lru_cache # Инициализация логгера logger = logging.getLogger('VADApp') # Путь к файлу конфигурации CONFIG_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'config.yaml') class AuthConfig(BaseModel): username: str = "admin" password: str = "admin" class VADConfig(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 SystemConfig(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 STTConfig(BaseModel): model_path: str = "" class CommandsConfig(BaseModel): commands_file: str = "" class SessionConfig(BaseModel): session_timeout_seconds: int = 30 class TTSConfig(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 AudioSettingsConfig(BaseModel): playback_device: int = 0 capture_device: int = 0 playback_volume: int = 80 capture_volume: int = 90 class Settings(BaseModel): auth: AuthConfig = Field(default_factory=AuthConfig) vad: VADConfig = Field(default_factory=VADConfig) system: SystemConfig = Field(default_factory=SystemConfig) stt: STTConfig = Field(default_factory=STTConfig) commands: CommandsConfig = Field(default_factory=CommandsConfig) session: SessionConfig = Field(default_factory=SessionConfig) tts: TTSConfig = Field(default_factory=TTSConfig) audio_settings: AudioSettingsConfig = Field(default_factory=AudioSettingsConfig) def setup_logging(logging_level='INFO'): """Настройка уровня логирования""" logger.debug(f"Установка уровня логирования: {logging_level}") try: logger.setLevel(getattr(logging, logging_level.upper(), logging.INFO)) logger.info(f"Уровень логирования установлен: {logging_level}") except Exception as e: logger.error(f"Ошибка настройки логирования: {e}", exc_info=True) raise def load_yaml_config() -> Dict[str, Any]: """Загрузка конфигурации из YAML файла""" logger.debug("Загрузка конфигурации из YAML") try: yaml = YAML() yaml.preserve_quotes = True if not os.path.exists(CONFIG_PATH): logger.error(f"Файл конфигурации не найден: {CONFIG_PATH}") raise FileNotFoundError(f"Файл конфигурации не найден: {CONFIG_PATH}") with open(CONFIG_PATH, 'r', encoding='utf-8') as f: config = yaml.load(f) or {} # Проверяем и адаптируем пути для текущей ОС if 'commands' in config and 'commands_file' in config['commands']: commands_file = config['commands']['commands_file'] # Преобразуем путь для текущей ОС, если необходимо commands_file = os.path.normpath(commands_file) config['commands']['commands_file'] = commands_file logger.debug(f"Путь к файлу команд: {commands_file}") if 'stt' in config and 'model_path' in config['stt']: model_path = config['stt']['model_path'] # Преобразуем путь для текущей ОС, если необходимо model_path = os.path.normpath(model_path) config['stt']['model_path'] = model_path logger.debug(f"Путь к модели STT: {model_path}") return config except Exception as e: logger.error(f"Ошибка загрузки конфигурации: {e}", exc_info=True) raise @lru_cache() def get_settings() -> Settings: """Получение настроек приложения (кэшируется)""" config = load_yaml_config() # Создаем объект настроек settings = Settings( auth=AuthConfig(**config.get('auth', {})), vad=VADConfig(**config.get('vad', {})), system=SystemConfig(**config.get('system', {})), stt=STTConfig(**config.get('stt', {})), commands=CommandsConfig(**config.get('commands', {})), tts=TTSConfig(**config.get('tts', {})), audio_settings=AudioSettingsConfig(**config.get('audio_settings', {})) ) # Настраиваем логирование setup_logging(settings.system.logging_level) return settings def save_settings(settings: Settings) -> bool: """Сохранение настроек в YAML файл""" logger.debug("Сохранение конфигурации") try: # Загружаем текущую конфигурацию yaml = YAML() yaml.indent(mapping=2, sequence=4, offset=2) yaml.preserve_quotes = True if not os.path.exists(CONFIG_PATH): logger.error(f"Файл конфигурации не найден: {CONFIG_PATH}") raise FileNotFoundError(f"Файл конфигурации не найден: {CONFIG_PATH}") with open(CONFIG_PATH, 'r', encoding='utf-8') as f: config = yaml.load(f) or {} # Обновляем конфигурацию config['auth'] = settings.auth.dict() config['vad'] = settings.vad.dict() config['system'] = settings.system.dict() config['stt'] = settings.stt.dict() config['commands'] = settings.commands.dict() config['tts'] = settings.tts.dict() config['audio_settings'] = settings.audio_settings.dict() # Сохраняем обновленную конфигурацию with open(CONFIG_PATH, 'w', encoding='utf-8') as f: yaml.dump(config, f) # Сбрасываем кэш настроек get_settings.cache_clear() logger.info("Конфигурация сохранена") return True except Exception as e: logger.error(f"Ошибка сохранения конфигурации: {e}", exc_info=True) return False