import os from dataclasses import dataclass from typing import Dict, List, Optional, Union import logging from dotenv import load_dotenv load_dotenv() @dataclass class FFmpegConfig: """FFmpeg相关配置""" encoder_args: List[str] video_args: List[str] audio_args: List[str] default_args: List[str] old_ffmpeg: bool = False re_encode_video_args: Optional[List[str]] = None re_encode_encoder_args: Optional[List[str]] = None # 新增配置选项,消除硬编码 max_download_workers: int = 8 progress_args: Optional[List[str]] = None loglevel_args: Optional[List[str]] = None null_audio_args: Optional[List[str]] = None overlay_scale_mode: str = "scale2ref" # 新版本使用scale2ref,旧版本使用scale amix_args: Optional[List[str]] = None @classmethod def from_env(cls) -> "FFmpegConfig": encoder_args = os.getenv("ENCODER_ARGS", "-c:v h264").split(" ") video_args = os.getenv("VIDEO_ARGS", "-profile:v high -level:v 4").split(" ") audio_args = ["-c:a", "aac", "-b:a", "128k", "-ar", "48000", "-ac", "2"] default_args = ["-shortest"] re_encode_video_args = None re_encode_video_env = os.getenv("RE_ENCODE_VIDEO_ARGS") if re_encode_video_env: re_encode_video_args = re_encode_video_env.split(" ") re_encode_encoder_args = None re_encode_encoder_env = os.getenv("RE_ENCODE_ENCODER_ARGS") if re_encode_encoder_env: re_encode_encoder_args = re_encode_encoder_env.split(" ") # 新增配置项的默认值 progress_args = ["-progress", "-"] loglevel_args = ["-loglevel", "error"] null_audio_args = ["-f", "lavfi", "-i", "anullsrc=cl=stereo:r=48000"] amix_args = ["amix=duration=shortest:dropout_transition=0:normalize=0"] overlay_scale_mode = ( "scale" if bool(os.getenv("OLD_FFMPEG", False)) else "scale2ref" ) return cls( encoder_args=encoder_args, video_args=video_args, audio_args=audio_args, default_args=default_args, old_ffmpeg=bool(os.getenv("OLD_FFMPEG", False)), re_encode_video_args=re_encode_video_args, re_encode_encoder_args=re_encode_encoder_args, max_download_workers=int(os.getenv("MAX_DOWNLOAD_WORKERS", "8")), progress_args=progress_args, loglevel_args=loglevel_args, null_audio_args=null_audio_args, overlay_scale_mode=overlay_scale_mode, amix_args=amix_args, ) @dataclass class APIConfig: """API相关配置""" endpoint: str access_key: str timeout: int = 10 redirect_to_url: Optional[str] = None @classmethod def from_env(cls) -> "APIConfig": endpoint = os.getenv("API_ENDPOINT", "") if not endpoint: raise ValueError("API_ENDPOINT environment variable is required") access_key = os.getenv("ACCESS_KEY", "") if not access_key: raise ValueError("ACCESS_KEY environment variable is required") return cls( endpoint=endpoint, access_key=access_key, timeout=int(os.getenv("API_TIMEOUT", "10")), redirect_to_url=os.getenv("REDIRECT_TO_URL") or None, ) @dataclass class StorageConfig: """存储相关配置""" template_dir: str @classmethod def from_env(cls) -> "StorageConfig": template_dir = os.getenv("TEMPLATE_DIR", "./template") return cls(template_dir=template_dir) @dataclass class ServerConfig: """服务器相关配置""" host: str = "0.0.0.0" port: int = 9998 debug: bool = False @classmethod def from_env(cls) -> "ServerConfig": return cls( host=os.getenv("HOST", "0.0.0.0"), port=int(os.getenv("PORT", "9998")), debug=bool(os.getenv("DEBUG", False)), ) @dataclass class AppConfig: """应用总配置""" ffmpeg: FFmpegConfig api: APIConfig storage: StorageConfig server: ServerConfig @classmethod def from_env(cls) -> "AppConfig": return cls( ffmpeg=FFmpegConfig.from_env(), api=APIConfig.from_env(), storage=StorageConfig.from_env(), server=ServerConfig.from_env(), ) # 全局配置实例 _config: Optional[AppConfig] = None def get_config() -> AppConfig: """获取全局配置实例""" global _config if _config is None: _config = AppConfig.from_env() return _config def reload_config() -> AppConfig: """重新加载配置""" global _config _config = AppConfig.from_env() return _config # 向后兼容的配置获取函数 def get_ffmpeg_config() -> FFmpegConfig: return get_config().ffmpeg def get_api_config() -> APIConfig: return get_config().api def get_storage_config() -> StorageConfig: return get_config().storage def get_server_config() -> ServerConfig: return get_config().server