"""Application configuration settings.""" from pydantic_settings import BaseSettings from pydantic import Field from pathlib import Path from typing import List, Dict, Any, Optional import os class Settings(BaseSettings): """Application settings with environment variable support.""" # Application settings app_name: str = "Video to Audio Extractor" app_version: str = "1.0.0" debug: bool = Field(default=False, env="DEBUG") # Storage configuration storage_type: str = Field(default="filesystem", env="STORAGE_TYPE") # "filesystem" or "r2" # File processing settings temp_dir: Path = Field(default=Path("/tmp/audio_extractor"), env="TEMP_DIR") max_direct_file_size_mb: float = Field(default=10.0, env="MAX_DIRECT_FILE_SIZE_MB") cleanup_interval_seconds: int = Field(default=3600, env="CLEANUP_INTERVAL_SECONDS") file_retention_hours: int = Field(default=2, env="FILE_RETENTION_HOURS") # Cloudflare R2 storage settings (optional, only needed if storage_type == "r2") cloudflare_r2_account_id: Optional[str] = Field(default=None, env="CLOUDFLARE_R2_ACCOUNT_ID") cloudflare_r2_access_key_id: Optional[str] = Field(default=None, env="CLOUDFLARE_R2_ACCESS_KEY_ID") cloudflare_r2_secret_access_key: Optional[str] = Field(default=None, env="CLOUDFLARE_R2_SECRET_ACCESS_KEY") cloudflare_r2_bucket_name: Optional[str] = Field(default=None, env="CLOUDFLARE_R2_BUCKET_NAME") # FFmpeg settings ffmpeg_path: str = Field(default="/usr/bin/ffmpeg", env="FFMPEG_PATH") ffmpeg_timeout_seconds: int = Field(default=1800, env="FFMPEG_TIMEOUT_SECONDS") # 30 minutes # Supported formats supported_video_formats: List[str] = Field( default=['.mp4', '.avi', '.mov', '.mkv', '.webm', '.flv', '.wmv', '.m4v'], env="SUPPORTED_VIDEO_FORMATS" ) supported_audio_formats: List[str] = Field( default=['mp3', 'aac', 'wav', 'flac', 'm4a', 'ogg'], env="SUPPORTED_AUDIO_FORMATS" ) # Quality presets quality_presets: Dict[str, Dict[str, Dict[str, Any]]] = { 'mp3': { 'high': {'bitrate': '320k', 'codec': 'libmp3lame'}, 'medium': {'bitrate': '192k', 'codec': 'libmp3lame'}, 'low': {'bitrate': '128k', 'codec': 'libmp3lame'} }, 'aac': { 'high': {'bitrate': '256k', 'codec': 'aac'}, 'medium': {'bitrate': '192k', 'codec': 'aac'}, 'low': {'bitrate': '128k', 'codec': 'aac'} }, 'wav': { 'high': {'codec': 'pcm_s24le'}, 'medium': {'codec': 'pcm_s16le'}, 'low': {'codec': 'pcm_s16le'} }, 'flac': { 'high': {'codec': 'flac', 'compression_level': 12}, 'medium': {'codec': 'flac', 'compression_level': 8}, 'low': {'codec': 'flac', 'compression_level': 0} }, 'm4a': { 'high': {'bitrate': '256k', 'codec': 'aac'}, 'medium': {'bitrate': '192k', 'codec': 'aac'}, 'low': {'bitrate': '128k', 'codec': 'aac'} }, 'ogg': { 'high': {'bitrate': '256k', 'codec': 'libvorbis'}, 'medium': {'bitrate': '192k', 'codec': 'libvorbis'}, 'low': {'bitrate': '128k', 'codec': 'libvorbis'} } } # MIME types audio_mime_types: Dict[str, str] = { 'mp3': 'audio/mpeg', 'aac': 'audio/aac', 'wav': 'audio/wav', 'flac': 'audio/flac', 'm4a': 'audio/mp4', 'ogg': 'audio/ogg' } # N8N Configuration n8n_base_url: str = Field(default="http://localhost:5678", env="N8N_BASE_URL") n8n_timeout: int = Field(default=30, env="N8N_TIMEOUT") n8n_enabled: bool = Field(default=True, env="N8N_ENABLED") # Authentication Configuration enforce_authentication: bool = Field(default=True, env="ENFORCE_AUTHENTICATION") enable_external_job_ids: bool = Field(default=True, env="ENABLE_EXTERNAL_JOB_IDS") jwt_validation_strict: bool = Field(default=False, env="JWT_VALIDATION_STRICT") class Config: env_file = ".env" env_file_encoding = "utf-8" case_sensitive = False def __init__(self, **kwargs): super().__init__(**kwargs) # Ensure temp directory exists (only for filesystem storage) if self.storage_type.lower() == "filesystem": self.temp_dir.mkdir(parents=True, exist_ok=True) # Validate R2 configuration if R2 storage is selected if self.storage_type.lower() == "r2": self._validate_r2_config() def _validate_r2_config(self): """Validate R2 configuration when R2 storage is selected.""" required_fields = [ 'cloudflare_r2_account_id', 'cloudflare_r2_access_key_id', 'cloudflare_r2_secret_access_key', 'cloudflare_r2_bucket_name' ] missing_fields = [] for field in required_fields: value = getattr(self, field) if not value: missing_fields.append(field.upper()) if missing_fields: raise ValueError( f"R2 storage selected but missing required environment variables: " f"{', '.join(missing_fields)}" ) # Singleton instance settings = Settings()