from __future__ import annotations from dataclasses import dataclass, field from pathlib import Path from typing import Any import yaml @dataclass class ServerConfig: host: str = "0.0.0.0" port: int = 8765 max_ws_size: int = 10_485_760 @dataclass class VideoConfig: process_every_n_frames: int = 1 @dataclass class ModelConfig: path: str = "" prefer_gpu: bool = True @property def resolved_path(self) -> str: if self.path: return self.path return str(Path(__file__).resolve().parent.parent / "pose_models" / "pose_landmarker_full.task") @dataclass class DeadBugConfig: visibility_threshold: float = 0.45 extension_confirm_frames: int = 4 reset_confirm_frames: int = 3 @dataclass class AudioConfig: rep_announcer_enabled: bool = True rep_announcer_rate: int = 185 rep_announcer_volume: float = 1.0 @dataclass class LoggingConfig: dir: str = "logs" rotation: str = "20 MB" retention: str = "14 days" @property def dir_path(self) -> Path: return Path(__file__).resolve().parent.parent / self.dir @dataclass class AppConfig: server: ServerConfig = field(default_factory=ServerConfig) video: VideoConfig = field(default_factory=VideoConfig) model: ModelConfig = field(default_factory=ModelConfig) dead_bug: DeadBugConfig = field(default_factory=DeadBugConfig) audio: AudioConfig = field(default_factory=AudioConfig) logging: LoggingConfig = field(default_factory=LoggingConfig) def _dict_to_dataclass(cls: type, data: dict[str, Any] | None) -> dict[str, Any]: """Convert a dict to dataclass constructor kwargs, using only known fields.""" import dataclasses if data is None: return {} fields = {f.name for f in dataclasses.fields(cls)} return {k: v for k, v in data.items() if k in fields} def load_config(config_path: str | Path | None = None) -> AppConfig: if config_path is None: config_path = Path(__file__).resolve().parent.parent / "config.yaml" raw: dict[str, Any] = {} if Path(config_path).exists(): with open(config_path, encoding="utf-8") as f: raw = yaml.safe_load(f) or {} return AppConfig( server=ServerConfig(**_dict_to_dataclass(ServerConfig, raw.get("server"))), video=VideoConfig(**_dict_to_dataclass(VideoConfig, raw.get("video"))), model=ModelConfig(**_dict_to_dataclass(ModelConfig, raw.get("model"))), dead_bug=DeadBugConfig(**_dict_to_dataclass(DeadBugConfig, raw.get("dead_bug"))), audio=AudioConfig(**_dict_to_dataclass(AudioConfig, raw.get("audio"))), logging=LoggingConfig(**_dict_to_dataclass(LoggingConfig, raw.get("logging"))), ) config = load_config()