from __future__ import annotations from dataclasses import dataclass, field from pathlib import Path @dataclass class ServerConfig: """WebSocket服务器配置""" host: str = "0.0.0.0" port: int = 8765 max_ws_size: int = 10_485_760 @dataclass class VideoConfig: """视频帧处理配置""" process_every_n_frames: int = 2 log_every_n_frames: int = 30 perf_log_every_n_frames: int = 30 slow_frame_ms: float = 100.0 @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: """死虫式(Dead Bug)运动检测配置""" 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 rep_max_count: int = 200 rep_audio_dir: str = "" trim_leading_silence: bool = True trim_silence_threshold: int = 500 trim_silence_padding_ms: int = 20 @property def resolved_audio_dir(self) -> Path: """返回语音文件目录的绝对路径""" if self.rep_audio_dir: return Path(self.rep_audio_dir) return Path(__file__).resolve().parent.parent / "resources" / "audio" / "reps" @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)