from __future__ import annotations import os from dataclasses import dataclass from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent VIDEOS_ROOT = BASE_DIR / "videos" OUTPUTS_ROOT = BASE_DIR / "outputs" TEMP_ROOT = BASE_DIR / "temp" @dataclass(frozen=True) class RabbitMQSettings: host: str = os.environ.get("RABBITMQ_HOST", "rabbitmq") port: int = int(os.environ.get("RABBITMQ_PORT", 5672)) user: str = os.environ.get("RABBITMQ_USER", "admin") password: str = os.environ.get("RABBITMQ_PASS") consume_queue: str = os.environ.get("RABBITMQ_QUEUE", "to-render") publish_queue: str = os.environ.get("RABBITMQ_UPLOAD_QUEUE", "to-upload") prefetch_count: int = int(os.environ.get("RABBITMQ_PREFETCH", 1)) heartbeat: int = int(os.environ.get("RABBITMQ_HEARTBEAT", 60)) blocked_timeout: int = int(os.environ.get("RABBITMQ_BLOCKED_TIMEOUT", 300)) @dataclass(frozen=True) class GeminiSettings: api_key: str = os.environ.get("GEMINI_API_KEY", "") model: str = os.environ.get("GEMINI_MODEL", "gemini-2.5-pro") safety_settings: str | None = os.environ.get("GEMINI_SAFETY_SETTINGS") temperature: float = float(os.environ.get("GEMINI_TEMPERATURE", 0.2)) top_k: int | None = ( int(os.environ["GEMINI_TOP_K"]) if os.environ.get("GEMINI_TOP_K") else None ) top_p: float | None = ( float(os.environ["GEMINI_TOP_P"]) if os.environ.get("GEMINI_TOP_P") else None ) prompt_path: str = os.environ.get("GEMINI_PROMPT_PATH", "prompts/generate.txt") @dataclass(frozen=True) class OpenRouterSettings: api_key: str = os.environ.get("OPENROUTER_API_KEY", "") model: str = os.environ.get( "OPENROUTER_MODEL", "anthropic/claude-3-haiku:beta" ) temperature: float = float(os.environ.get("OPENROUTER_TEMPERATURE", 0.6)) max_output_tokens: int = int(os.environ.get("OPENROUTER_MAX_OUTPUT_TOKENS", 256)) @dataclass(frozen=True) class WhisperSettings: model_size: str = os.environ.get("FASTER_WHISPER_MODEL_SIZE", "medium") device: str | None = os.environ.get("FASTER_WHISPER_DEVICE") compute_type: str | None = os.environ.get("FASTER_WHISPER_COMPUTE_TYPE") download_root: Path = Path( os.environ.get("FASTER_WHISPER_DOWNLOAD_ROOT", str(BASE_DIR / ".whisper")) ) @dataclass(frozen=True) class RenderingSettings: frame_width: int = int(os.environ.get("RENDER_WIDTH", 1080)) frame_height: int = int(os.environ.get("RENDER_HEIGHT", 1920)) fps: int = int(os.environ.get("RENDER_FPS", 30)) video_codec: str = os.environ.get("RENDER_CODEC", "libx264") audio_codec: str = os.environ.get("RENDER_AUDIO_CODEC", "aac") bitrate: str = os.environ.get("RENDER_BITRATE", "5000k") preset: str = os.environ.get("RENDER_PRESET", "faster") highlight_color: str = os.environ.get("SUBTITLE_HIGHLIGHT_COLOR", "#FFD200") base_color: str = os.environ.get("SUBTITLE_BASE_COLOR", "#FFFFFF") font_path: Path = Path(os.environ.get("RENDER_FONT_PATH", "./Montserrat.ttf")) title_font_size: int = int(os.environ.get("RENDER_TITLE_FONT_SIZE", 110)) subtitle_font_size: int = int(os.environ.get("RENDER_SUBTITLE_FONT_SIZE", 64)) caption_min_words: int = int(os.environ.get("CAPTION_MIN_WORDS", 3)) caption_max_words: int = int(os.environ.get("CAPTION_MAX_WORDS", 4)) @dataclass(frozen=True) class Settings: rabbitmq: RabbitMQSettings = RabbitMQSettings() gemini: GeminiSettings = GeminiSettings() openrouter: OpenRouterSettings = OpenRouterSettings() whisper: WhisperSettings = WhisperSettings() rendering: RenderingSettings = RenderingSettings() videos_dir: Path = VIDEOS_ROOT outputs_dir: Path = OUTPUTS_ROOT temp_dir: Path = TEMP_ROOT def load_settings() -> Settings: settings = Settings() if not settings.rabbitmq.password: raise RuntimeError("RABBITMQ_PASS must be provided") settings.videos_dir.mkdir(parents=True, exist_ok=True) settings.outputs_dir.mkdir(parents=True, exist_ok=True) settings.temp_dir.mkdir(parents=True, exist_ok=True) return settings