""" BackgroundFX Pro Configuration Module Centralizes all application configuration and environment variable handling Note: Named 'app_config.py' to avoid conflicts with existing 'Configs/' folder """ import os from dataclasses import dataclass, asdict, field from typing import Dict, Any, Optional, List from pathlib import Path import logging import json import yaml logger = logging.getLogger(__name__) @dataclass class ProcessingConfig: """ Main processing configuration with environment variable defaults """ # Application info app_name: str = "BackgroundFX Pro" version: str = "2.0.0" # Frame processing settings keyframe_interval: int = int(os.getenv('KEYFRAME_INTERVAL', '5')) frame_skip: int = int(os.getenv('FRAME_SKIP', '1')) # Memory management memory_cleanup_interval: int = int(os.getenv('MEMORY_CLEANUP_INTERVAL', '30')) memory_threshold_mb: int = int(os.getenv('MEMORY_THRESHOLD_MB', '1024')) # Video constraints max_video_length: int = int(os.getenv('MAX_VIDEO_LENGTH', '300')) # seconds max_video_resolution: str = os.getenv('MAX_VIDEO_RESOLUTION', '1920x1080') min_video_fps: int = int(os.getenv('MIN_VIDEO_FPS', '15')) max_video_fps: int = int(os.getenv('MAX_VIDEO_FPS', '60')) # Quality settings quality_preset: str = os.getenv('QUALITY_PRESET', 'balanced') # Model settings sam2_model_size: str = os.getenv('SAM2_MODEL_SIZE', 'large') # tiny, small, base, large matanyone_precision: str = os.getenv('MATANYONE_PRECISION', 'fp32') # fp16, fp32 model_device: str = os.getenv('MODEL_DEVICE', 'auto') # auto, cuda, cpu # MatAnyone specific settings matanyone_enabled: bool = os.getenv('MATANYONE_ENABLED', 'false').lower() == 'true' matanyone_model_path: str = os.getenv('MATANYONE_MODEL_PATH', 'models/matanyone/checkpoint.pth') matanyone_threshold: float = float(os.getenv('MATANYONE_THRESHOLD', '0.5')) matanyone_hair_refinement: bool = os.getenv('MATANYONE_HAIR_REFINEMENT', 'true').lower() == 'true' matanyone_edge_enhancement: bool = os.getenv('MATANYONE_EDGE_ENHANCEMENT', 'true').lower() == 'true' matanyone_detail_refinement: bool = os.getenv('MATANYONE_DETAIL_REFINEMENT', 'true').lower() == 'true' matanyone_morphology_ops: bool = os.getenv('MATANYONE_MORPHOLOGY_OPS', 'true').lower() == 'true' matanyone_morphology_kernel_size: int = int(os.getenv('MATANYONE_MORPHOLOGY_KERNEL_SIZE', '5')) # Component paths for separate mask files hair_masks_dir: str = os.getenv('HAIR_MASKS_DIR', 'assets/hair_masks') edge_masks_dir: str = os.getenv('EDGE_MASKS_DIR', 'assets/edge_masks') detail_masks_dir: str = os.getenv('DETAIL_MASKS_DIR', 'assets/detail_masks') use_component_masks: bool = os.getenv('USE_COMPONENT_MASKS', 'false').lower() == 'true' # MatAnyone component weights matanyone_weight_base: float = float(os.getenv('MATANYONE_WEIGHT_BASE', '1.0')) matanyone_weight_hair: float = float(os.getenv('MATANYONE_WEIGHT_HAIR', '1.2')) matanyone_weight_edge: float = float(os.getenv('MATANYONE_WEIGHT_EDGE', '1.5')) matanyone_weight_detail: float = float(os.getenv('MATANYONE_WEIGHT_DETAIL', '1.1')) # MatAnyone processing modes matanyone_processing_mode: str = os.getenv('MATANYONE_PROCESSING_MODE', 'refine') # refine, replace, blend matanyone_blend_alpha: float = float(os.getenv('MATANYONE_BLEND_ALPHA', '0.7')) matanyone_trimap_enabled: bool = os.getenv('MATANYONE_TRIMAP_ENABLED', 'false').lower() == 'true' matanyone_trimap_dilation: int = int(os.getenv('MATANYONE_TRIMAP_DILATION', '10')) # Processing settings temporal_consistency: bool = os.getenv('TEMPORAL_CONSISTENCY', 'true').lower() == 'true' edge_refinement: bool = os.getenv('EDGE_REFINEMENT', 'true').lower() == 'true' mask_blur_radius: int = int(os.getenv('MASK_BLUR_RADIUS', '5')) confidence_threshold: float = float(os.getenv('CONFIDENCE_THRESHOLD', '0.85')) background_preset: str = os.getenv('BACKGROUND_PRESET', 'minimalist') # Mask processing attributes (ADDED) mask_gamma: float = float(os.getenv('MASK_GAMMA', '1.0')) mask_blur: int = int(os.getenv('MASK_BLUR', '0')) mask_threshold: float = float(os.getenv('MASK_THRESHOLD', '0.5')) mask_edge_softness: int = int(os.getenv('MASK_EDGE_SOFTNESS', '2')) mask_spill_suppression: float = float(os.getenv('MASK_SPILL_SUPPRESSION', '0.35')) # Hard cutoff thresholds for mask processing (ADDED) hard_low: float = float(os.getenv('HARD_LOW', '0.2')) hard_medium: float = float(os.getenv('HARD_MEDIUM', '0.5')) hard_high: float = float(os.getenv('HARD_HIGH', '0.8')) hard_threshold: float = float(os.getenv('HARD_THRESHOLD', '0.5')) # Morphological operations for mask processing (ADDED) dilate_px: int = int(os.getenv('DILATE_PX', '0')) erode_px: int = int(os.getenv('ERODE_PX', '0')) open_px: int = int(os.getenv('OPEN_PX', '0')) close_px: int = int(os.getenv('CLOSE_PX', '0')) morph_iterations: int = int(os.getenv('MORPH_ITERATIONS', '1')) # Edge processing attributes (ADDED) edge_blur_px: int = int(os.getenv('EDGE_BLUR_PX', '0')) edge_feather_px: int = int(os.getenv('EDGE_FEATHER_PX', '2')) edge_threshold: float = float(os.getenv('EDGE_THRESHOLD', '0.5')) edge_detect_method: str = os.getenv('EDGE_DETECT_METHOD', 'canny') # Tracking and temporal consistency attributes (ADDED) min_iou_to_accept: float = float(os.getenv('MIN_IOU_TO_ACCEPT', '0.3')) max_iou_distance: float = float(os.getenv('MAX_IOU_DISTANCE', '0.7')) min_track_length: int = int(os.getenv('MIN_TRACK_LENGTH', '3')) track_buffer_size: int = int(os.getenv('TRACK_BUFFER_SIZE', '30')) temporal_smoothing: float = float(os.getenv('TEMPORAL_SMOOTHING', '0.7')) motion_threshold: float = float(os.getenv('MOTION_THRESHOLD', '0.1')) # Segmentation refinement attributes (ADDED) refine_iterations: int = int(os.getenv('REFINE_ITERATIONS', '1')) refine_threshold: float = float(os.getenv('REFINE_THRESHOLD', '0.5')) use_crf: bool = os.getenv('USE_CRF', 'false').lower() == 'true' crf_iterations: int = int(os.getenv('CRF_ITERATIONS', '5')) bilateral_sigma_color: float = float(os.getenv('BILATERAL_SIGMA_COLOR', '80.0')) bilateral_sigma_space: float = float(os.getenv('BILATERAL_SIGMA_SPACE', '10.0')) # Feathering and blending attributes (ADDED) feather_amount: int = int(os.getenv('FEATHER_AMOUNT', '5')) blend_mode: str = os.getenv('BLEND_MODE', 'normal') alpha_matting: bool = os.getenv('ALPHA_MATTING', 'false').lower() == 'true' alpha_threshold: float = float(os.getenv('ALPHA_THRESHOLD', '0.5')) composite_mode: str = os.getenv('COMPOSITE_MODE', 'over') # Color correction attributes (ADDED) color_correct: bool = os.getenv('COLOR_CORRECT', 'false').lower() == 'true' brightness_adjust: float = float(os.getenv('BRIGHTNESS_ADJUST', '1.0')) contrast_adjust: float = float(os.getenv('CONTRAST_ADJUST', '1.0')) saturation_adjust: float = float(os.getenv('SATURATION_ADJUST', '1.0')) hue_shift: float = float(os.getenv('HUE_SHIFT', '0.0')) # Noise reduction attributes (ADDED) denoise: bool = os.getenv('DENOISE', 'false').lower() == 'true' denoise_strength: float = float(os.getenv('DENOISE_STRENGTH', '10.0')) median_filter_size: int = int(os.getenv('MEDIAN_FILTER_SIZE', '0')) gaussian_sigma: float = float(os.getenv('GAUSSIAN_SIGMA', '1.0')) # Performance optimization attributes (ADDED) use_gpu: bool = os.getenv('USE_GPU', 'true').lower() == 'true' gpu_device_id: int = int(os.getenv('GPU_DEVICE_ID', '0')) use_fp16: bool = os.getenv('USE_FP16', 'false').lower() == 'true' use_tensorrt: bool = os.getenv('USE_TENSORRT', 'false').lower() == 'true' use_quantization: bool = os.getenv('USE_QUANTIZATION', 'false').lower() == 'true' # Video codec attributes (ADDED) use_nvenc: bool = os.getenv('USE_NVENC', 'false').lower() == 'true' prefer_mp4: bool = os.getenv('PREFER_MP4', 'true').lower() == 'true' video_codec: str = os.getenv('VIDEO_CODEC', 'mp4v') audio_copy: bool = os.getenv('AUDIO_COPY', 'true').lower() == 'true' ffmpeg_path: str = os.getenv('FFMPEG_PATH', 'ffmpeg') # Model size constraints (ADDED) max_model_size: int = int(os.getenv('MAX_MODEL_SIZE', '0')) max_model_size_bytes: int = int(os.getenv('MAX_MODEL_SIZE_BYTES', '0')) # Temporal consistency and smoothing (COMPREHENSIVE) temporal_ema_alpha: float = float(os.getenv('TEMPORAL_EMA_ALPHA', '0.7')) temporal_window_size: int = int(os.getenv('TEMPORAL_WINDOW_SIZE', '5')) temporal_blend_factor: float = float(os.getenv('TEMPORAL_BLEND_FACTOR', '0.5')) temporal_consistency_threshold: float = float(os.getenv('TEMPORAL_CONSISTENCY_THRESHOLD', '0.8')) use_temporal_smoothing: bool = os.getenv('USE_TEMPORAL_SMOOTHING', 'true').lower() == 'true' temporal_buffer_frames: int = int(os.getenv('TEMPORAL_BUFFER_FRAMES', '10')) # Mask post-processing (COMPREHENSIVE) mask_min_area: int = int(os.getenv('MASK_MIN_AREA', '100')) mask_max_area: int = int(os.getenv('MASK_MAX_AREA', '0')) mask_fill_holes: bool = os.getenv('MASK_FILL_HOLES', 'true').lower() == 'true' mask_convex_hull: bool = os.getenv('MASK_CONVEX_HULL', 'false').lower() == 'true' mask_largest_component: bool = os.getenv('MASK_LARGEST_COMPONENT', 'true').lower() == 'true' mask_smooth_radius: int = int(os.getenv('MASK_SMOOTH_RADIUS', '0')) mask_binary_threshold: float = float(os.getenv('MASK_BINARY_THRESHOLD', '0.5')) mask_confidence_threshold: float = float(os.getenv('MASK_CONFIDENCE_THRESHOLD', '0.7')) # Contour processing (COMPREHENSIVE) contour_approx_epsilon: float = float(os.getenv('CONTOUR_APPROX_EPSILON', '0.01')) contour_min_points: int = int(os.getenv('CONTOUR_MIN_POINTS', '4')) contour_max_points: int = int(os.getenv('CONTOUR_MAX_POINTS', '0')) contour_smoothing: bool = os.getenv('CONTOUR_SMOOTHING', 'false').lower() == 'true' contour_simplification: bool = os.getenv('CONTOUR_SIMPLIFICATION', 'false').lower() == 'true' # Trimap generation (COMPREHENSIVE) trimap_dilation_size: int = int(os.getenv('TRIMAP_DILATION_SIZE', '10')) trimap_erosion_size: int = int(os.getenv('TRIMAP_EROSION_SIZE', '5')) trimap_unknown_width: int = int(os.getenv('TRIMAP_UNKNOWN_WIDTH', '20')) trimap_confidence_threshold: float = float(os.getenv('TRIMAP_CONFIDENCE_THRESHOLD', '0.5')) use_trimap: bool = os.getenv('USE_TRIMAP', 'false').lower() == 'true' # SAM2 specific parameters (COMPREHENSIVE) sam2_points_per_side: int = int(os.getenv('SAM2_POINTS_PER_SIDE', '32')) sam2_pred_iou_thresh: float = float(os.getenv('SAM2_PRED_IOU_THRESH', '0.88')) sam2_stability_score_thresh: float = float(os.getenv('SAM2_STABILITY_SCORE_THRESH', '0.95')) sam2_crop_n_layers: int = int(os.getenv('SAM2_CROP_N_LAYERS', '0')) sam2_crop_n_points_downscale_factor: int = int(os.getenv('SAM2_CROP_N_POINTS_DOWNSCALE_FACTOR', '1')) sam2_min_mask_region_area: int = int(os.getenv('SAM2_MIN_MASK_REGION_AREA', '0')) sam2_use_m2m: bool = os.getenv('SAM2_USE_M2M', 'false').lower() == 'true' sam2_multimask_output: bool = os.getenv('SAM2_MULTIMASK_OUTPUT', 'true').lower() == 'true' # Background processing (COMPREHENSIVE) bg_blur_radius: int = int(os.getenv('BG_BLUR_RADIUS', '0')) bg_blur_type: str = os.getenv('BG_BLUR_TYPE', 'gaussian') bg_fill_mode: str = os.getenv('BG_FILL_MODE', 'stretch') bg_color: str = os.getenv('BG_COLOR', '#000000') bg_opacity: float = float(os.getenv('BG_OPACITY', '1.0')) # Green screen / chroma key (COMPREHENSIVE) chroma_tolerance: float = float(os.getenv('CHROMA_TOLERANCE', '0.3')) chroma_softness: float = float(os.getenv('CHROMA_SOFTNESS', '0.1')) chroma_defringe: bool = os.getenv('CHROMA_DEFRINGE', 'true').lower() == 'true' chroma_despill: bool = os.getenv('CHROMA_DESPILL', 'true').lower() == 'true' chroma_key_color: str = os.getenv('CHROMA_KEY_COLOR', '#00FF00') # Motion detection (COMPREHENSIVE) motion_detect: bool = os.getenv('MOTION_DETECT', 'false').lower() == 'true' motion_threshold_percent: float = float(os.getenv('MOTION_THRESHOLD_PERCENT', '5.0')) motion_blur_kernel: int = int(os.getenv('MOTION_BLUR_KERNEL', '21')) motion_min_area: int = int(os.getenv('MOTION_MIN_AREA', '500')) # Stabilization (COMPREHENSIVE) stabilize: bool = os.getenv('STABILIZE', 'false').lower() == 'true' stabilize_smoothing: float = float(os.getenv('STABILIZE_SMOOTHING', '30.0')) stabilize_crop_percent: float = float(os.getenv('STABILIZE_CROP_PERCENT', '0.05')) # Advanced processing flags (COMPREHENSIVE) use_guided_filter: bool = os.getenv('USE_GUIDED_FILTER', 'false').lower() == 'true' guided_filter_radius: int = int(os.getenv('GUIDED_FILTER_RADIUS', '8')) guided_filter_eps: float = float(os.getenv('GUIDED_FILTER_EPS', '0.2')) use_grabcut: bool = os.getenv('USE_GRABCUT', 'false').lower() == 'true' grabcut_iterations: int = int(os.getenv('GRABCUT_ITERATIONS', '5')) use_watershed: bool = os.getenv('USE_WATERSHED', 'false').lower() == 'true' watershed_markers: int = int(os.getenv('WATERSHED_MARKERS', '10')) # Frame sampling (COMPREHENSIVE) sample_rate: int = int(os.getenv('SAMPLE_RATE', '1')) start_frame: int = int(os.getenv('START_FRAME', '0')) end_frame: int = int(os.getenv('END_FRAME', '-1')) process_every_nth_frame: int = int(os.getenv('PROCESS_EVERY_NTH_FRAME', '1')) interpolate_frames: bool = os.getenv('INTERPOLATE_FRAMES', 'false').lower() == 'true' # Preview and debug (COMPREHENSIVE) preview_enabled: bool = os.getenv('PREVIEW_ENABLED', 'false').lower() == 'true' preview_scale: float = float(os.getenv('PREVIEW_SCALE', '0.5')) debug_masks: bool = os.getenv('DEBUG_MASKS', 'false').lower() == 'true' debug_contours: bool = os.getenv('DEBUG_CONTOURS', 'false').lower() == 'true' save_debug_frames: bool = os.getenv('SAVE_DEBUG_FRAMES', 'false').lower() == 'true' # Threading and parallelization (COMPREHENSIVE) num_threads: int = int(os.getenv('NUM_THREADS', '4')) use_multiprocessing: bool = os.getenv('USE_MULTIPROCESSING', 'false').lower() == 'true' chunk_size: int = int(os.getenv('CHUNK_SIZE', '10')) prefetch_frames: int = int(os.getenv('PREFETCH_FRAMES', '5')) # Memory optimization (COMPREHENSIVE) low_memory_mode: bool = os.getenv('LOW_MEMORY_MODE', 'false').lower() == 'true' cache_frames: bool = os.getenv('CACHE_FRAMES', 'true').lower() == 'true' max_cache_size_mb: int = int(os.getenv('MAX_CACHE_SIZE_MB', '1024')) clear_cache_interval: int = int(os.getenv('CLEAR_CACHE_INTERVAL', '100')) # Output settings output_dir: str = os.getenv('OUTPUT_DIR', 'outputs') output_format: str = os.getenv('OUTPUT_FORMAT', 'mp4') output_quality: str = os.getenv('OUTPUT_QUALITY', 'high') # low, medium, high output_codec: str = os.getenv('OUTPUT_CODEC', 'h264') write_fps: Optional[float] = None preserve_audio: bool = os.getenv('PRESERVE_AUDIO', 'true').lower() == 'true' # Cache settings model_cache_dir: str = os.getenv('MODEL_CACHE_DIR', 'models/cache') temp_dir: str = os.getenv('TEMP_DIR', 'temp') cleanup_temp_files: bool = os.getenv('CLEANUP_TEMP_FILES', 'true').lower() == 'true' cache_size_limit_gb: float = float(os.getenv('CACHE_SIZE_LIMIT_GB', '10.0')) # Performance settings max_concurrent_processes: int = int(os.getenv('MAX_CONCURRENT_PROCESSES', '1')) gpu_memory_fraction: float = float(os.getenv('GPU_MEMORY_FRACTION', '0.8')) batch_size: int = int(os.getenv('BATCH_SIZE', '4')) num_workers: int = int(os.getenv('NUM_WORKERS', '4')) # API settings api_enabled: bool = os.getenv('API_ENABLED', 'false').lower() == 'true' api_host: str = os.getenv('API_HOST', '0.0.0.0') api_port: int = int(os.getenv('API_PORT', '8000')) api_key: Optional[str] = os.getenv('API_KEY', None) # Web UI settings gradio_server_name: str = os.getenv('GRADIO_SERVER_NAME', '0.0.0.0') gradio_server_port: int = int(os.getenv('GRADIO_SERVER_PORT', '7860')) gradio_share: bool = os.getenv('GRADIO_SHARE', 'false').lower() == 'true' gradio_auth: Optional[str] = os.getenv('GRADIO_AUTH', None) # username:password # Debug settings debug_mode: bool = os.getenv('DEBUG_MODE', 'false').lower() == 'true' save_intermediate_results: bool = os.getenv('SAVE_INTERMEDIATE_RESULTS', 'false').lower() == 'true' log_level: str = os.getenv('LOG_LEVEL', 'INFO') profile_performance: bool = os.getenv('PROFILE_PERFORMANCE', 'false').lower() == 'true' # Feature flags enable_two_stage: bool = os.getenv('ENABLE_TWO_STAGE', 'true').lower() == 'true' enable_preview_modes: bool = os.getenv('ENABLE_PREVIEW_MODES', 'true').lower() == 'true' enable_batch_processing: bool = os.getenv('ENABLE_BATCH_PROCESSING', 'false').lower() == 'true' # Legacy compatibility legacy_mode: bool = os.getenv('LEGACY_MODE', 'true').lower() == 'true' legacy_configs_dir: str = os.getenv('LEGACY_CONFIGS_DIR', 'Configs') def __post_init__(self): """Validate configuration after initialization""" self._validate_config() self._create_directories() self._setup_logging() if self.debug_mode: self._log_config() def _validate_config(self): """Validate configuration values""" # Validate frame settings self.keyframe_interval = max(1, self.keyframe_interval) self.frame_skip = max(1, self.frame_skip) # Validate memory settings self.memory_cleanup_interval = max(1, self.memory_cleanup_interval) self.memory_threshold_mb = max(256, self.memory_threshold_mb) # Validate video constraints self.max_video_length = max(1, self.max_video_length) self.min_video_fps = max(1, min(self.min_video_fps, 60)) self.max_video_fps = max(self.min_video_fps, min(self.max_video_fps, 120)) # Validate resolution format if 'x' not in self.max_video_resolution: logger.warning(f"Invalid resolution format: {self.max_video_resolution}. Setting to 1920x1080.") self.max_video_resolution = '1920x1080' # Validate quality preset valid_presets = ['fast', 'balanced', 'high', 'ultra'] if self.quality_preset not in valid_presets: logger.warning(f"Invalid quality preset: {self.quality_preset}. Setting to 'balanced'.") self.quality_preset = 'balanced' # Validate model settings valid_sam2_sizes = ['tiny', 'small', 'base', 'large'] if self.sam2_model_size not in valid_sam2_sizes: logger.warning(f"Invalid SAM2 model size: {self.sam2_model_size}. Setting to 'large'.") self.sam2_model_size = 'large' valid_precisions = ['fp16', 'fp32'] if self.matanyone_precision not in valid_precisions: logger.warning(f"Invalid precision: {self.matanyone_precision}. Setting to 'fp32'.") self.matanyone_precision = 'fp32' # Validate MatAnyone settings self.matanyone_threshold = max(0.0, min(1.0, self.matanyone_threshold)) self.matanyone_weight_base = max(0.1, min(2.0, self.matanyone_weight_base)) self.matanyone_weight_hair = max(0.1, min(2.0, self.matanyone_weight_hair)) self.matanyone_weight_edge = max(0.1, min(2.0, self.matanyone_weight_edge)) self.matanyone_weight_detail = max(0.1, min(2.0, self.matanyone_weight_detail)) self.matanyone_blend_alpha = max(0.0, min(1.0, self.matanyone_blend_alpha)) self.matanyone_morphology_kernel_size = max(3, min(15, self.matanyone_morphology_kernel_size)) self.matanyone_trimap_dilation = max(1, min(50, self.matanyone_trimap_dilation)) # Validate MatAnyone processing mode valid_modes = ['refine', 'replace', 'blend'] if self.matanyone_processing_mode not in valid_modes: logger.warning(f"Invalid MatAnyone processing mode: {self.matanyone_processing_mode}. Setting to 'refine'.") self.matanyone_processing_mode = 'refine' # Validate mask settings (ADDED) self.mask_gamma = max(0.1, min(5.0, self.mask_gamma)) self.mask_blur = max(0, min(20, self.mask_blur)) self.mask_threshold = max(0.0, min(1.0, self.mask_threshold)) self.mask_edge_softness = max(0, min(10, self.mask_edge_softness)) self.mask_spill_suppression = max(0.0, min(1.0, self.mask_spill_suppression)) # Validate hard threshold settings (ADDED) self.hard_low = max(0.0, min(1.0, self.hard_low)) self.hard_medium = max(0.0, min(1.0, self.hard_medium)) self.hard_high = max(0.0, min(1.0, self.hard_high)) self.hard_threshold = max(0.0, min(1.0, self.hard_threshold)) # Validate morphological operations (ADDED) self.dilate_px = max(0, min(20, self.dilate_px)) self.erode_px = max(0, min(20, self.erode_px)) self.open_px = max(0, min(20, self.open_px)) self.close_px = max(0, min(20, self.close_px)) self.morph_iterations = max(1, min(10, self.morph_iterations)) # Validate edge processing (ADDED) self.edge_blur_px = max(0, min(30, self.edge_blur_px)) self.edge_feather_px = max(0, min(20, self.edge_feather_px)) self.edge_threshold = max(0.0, min(1.0, self.edge_threshold)) valid_edge_methods = ['canny', 'sobel', 'laplacian', 'scharr'] if self.edge_detect_method not in valid_edge_methods: self.edge_detect_method = 'canny' # Validate tracking and temporal consistency (ADDED) self.min_iou_to_accept = max(0.0, min(1.0, self.min_iou_to_accept)) self.max_iou_distance = max(0.0, min(1.0, self.max_iou_distance)) self.min_track_length = max(1, min(100, self.min_track_length)) self.track_buffer_size = max(1, min(300, self.track_buffer_size)) self.temporal_smoothing = max(0.0, min(1.0, self.temporal_smoothing)) self.motion_threshold = max(0.0, min(1.0, self.motion_threshold)) # Validate segmentation refinement (ADDED) self.refine_iterations = max(0, min(10, self.refine_iterations)) self.refine_threshold = max(0.0, min(1.0, self.refine_threshold)) self.crf_iterations = max(1, min(20, self.crf_iterations)) self.bilateral_sigma_color = max(1.0, min(200.0, self.bilateral_sigma_color)) self.bilateral_sigma_space = max(1.0, min(50.0, self.bilateral_sigma_space)) # Validate feathering and blending (ADDED) self.feather_amount = max(0, min(50, self.feather_amount)) valid_blend_modes = ['normal', 'multiply', 'screen', 'overlay', 'soft_light', 'hard_light'] if self.blend_mode not in valid_blend_modes: self.blend_mode = 'normal' self.alpha_threshold = max(0.0, min(1.0, self.alpha_threshold)) valid_composite_modes = ['over', 'under', 'atop', 'xor', 'plus'] if self.composite_mode not in valid_composite_modes: self.composite_mode = 'over' # Validate color correction (ADDED) self.brightness_adjust = max(0.0, min(2.0, self.brightness_adjust)) self.contrast_adjust = max(0.0, min(2.0, self.contrast_adjust)) self.saturation_adjust = max(0.0, min(2.0, self.saturation_adjust)) self.hue_shift = max(-180.0, min(180.0, self.hue_shift)) # Validate noise reduction (ADDED) self.denoise_strength = max(0.0, min(100.0, self.denoise_strength)) self.median_filter_size = max(0, min(15, self.median_filter_size)) if self.median_filter_size % 2 == 0 and self.median_filter_size > 0: self.median_filter_size += 1 # Must be odd self.gaussian_sigma = max(0.0, min(10.0, self.gaussian_sigma)) # Validate performance optimization (ADDED) self.gpu_device_id = max(0, self.gpu_device_id) # Validate video codec (ADDED) valid_codecs = ['mp4v', 'h264', 'h265', 'xvid', 'mjpeg'] if self.video_codec not in valid_codecs: self.video_codec = 'mp4v' # Validate temporal consistency (COMPREHENSIVE) self.temporal_ema_alpha = max(0.0, min(1.0, self.temporal_ema_alpha)) self.temporal_window_size = max(1, min(100, self.temporal_window_size)) self.temporal_blend_factor = max(0.0, min(1.0, self.temporal_blend_factor)) self.temporal_consistency_threshold = max(0.0, min(1.0, self.temporal_consistency_threshold)) self.temporal_buffer_frames = max(1, min(300, self.temporal_buffer_frames)) # Validate mask post-processing self.mask_min_area = max(0, self.mask_min_area) self.mask_binary_threshold = max(0.0, min(1.0, self.mask_binary_threshold)) self.mask_confidence_threshold = max(0.0, min(1.0, self.mask_confidence_threshold)) self.mask_smooth_radius = max(0, min(50, self.mask_smooth_radius)) # Validate contour processing self.contour_approx_epsilon = max(0.0, min(1.0, self.contour_approx_epsilon)) self.contour_min_points = max(3, self.contour_min_points) # Validate trimap self.trimap_dilation_size = max(0, min(50, self.trimap_dilation_size)) self.trimap_erosion_size = max(0, min(50, self.trimap_erosion_size)) self.trimap_unknown_width = max(1, min(100, self.trimap_unknown_width)) self.trimap_confidence_threshold = max(0.0, min(1.0, self.trimap_confidence_threshold)) # Validate SAM2 parameters self.sam2_points_per_side = max(1, min(100, self.sam2_points_per_side)) self.sam2_pred_iou_thresh = max(0.0, min(1.0, self.sam2_pred_iou_thresh)) self.sam2_stability_score_thresh = max(0.0, min(1.0, self.sam2_stability_score_thresh)) self.sam2_crop_n_layers = max(0, min(10, self.sam2_crop_n_layers)) self.sam2_crop_n_points_downscale_factor = max(1, min(16, self.sam2_crop_n_points_downscale_factor)) self.sam2_min_mask_region_area = max(0, self.sam2_min_mask_region_area) # Validate background processing self.bg_blur_radius = max(0, min(100, self.bg_blur_radius)) valid_blur_types = ['gaussian', 'box', 'median', 'bilateral'] if self.bg_blur_type not in valid_blur_types: self.bg_blur_type = 'gaussian' valid_fill_modes = ['stretch', 'fit', 'fill', 'tile'] if self.bg_fill_mode not in valid_fill_modes: self.bg_fill_mode = 'stretch' self.bg_opacity = max(0.0, min(1.0, self.bg_opacity)) # Validate chroma key self.chroma_tolerance = max(0.0, min(1.0, self.chroma_tolerance)) self.chroma_softness = max(0.0, min(1.0, self.chroma_softness)) # Validate motion detection self.motion_threshold_percent = max(0.0, min(100.0, self.motion_threshold_percent)) self.motion_blur_kernel = max(1, min(99, self.motion_blur_kernel)) if self.motion_blur_kernel % 2 == 0: self.motion_blur_kernel += 1 self.motion_min_area = max(0, self.motion_min_area) # Validate stabilization self.stabilize_smoothing = max(1.0, min(100.0, self.stabilize_smoothing)) self.stabilize_crop_percent = max(0.0, min(0.5, self.stabilize_crop_percent)) # Validate guided filter self.guided_filter_radius = max(1, min(50, self.guided_filter_radius)) self.guided_filter_eps = max(0.0, min(1.0, self.guided_filter_eps)) self.grabcut_iterations = max(1, min(20, self.grabcut_iterations)) self.watershed_markers = max(2, min(100, self.watershed_markers)) # Validate frame sampling self.sample_rate = max(1, self.sample_rate) self.start_frame = max(0, self.start_frame) self.process_every_nth_frame = max(1, self.process_every_nth_frame) # Validate preview self.preview_scale = max(0.1, min(2.0, self.preview_scale)) # Validate threading self.num_threads = max(1, min(32, self.num_threads)) self.chunk_size = max(1, min(1000, self.chunk_size)) self.prefetch_frames = max(0, min(100, self.prefetch_frames)) # Validate memory self.max_cache_size_mb = max(0, self.max_cache_size_mb) self.clear_cache_interval = max(1, self.clear_cache_interval) # Validate output settings valid_formats = ['mp4', 'avi', 'mov', 'webm', 'mkv'] if self.output_format not in valid_formats: logger.warning(f"Invalid output format: {self.output_format}. Setting to 'mp4'.") self.output_format = 'mp4' valid_qualities = ['low', 'medium', 'high', 'ultra'] if self.output_quality not in valid_qualities: logger.warning(f"Invalid output quality: {self.output_quality}. Setting to 'high'.") self.output_quality = 'high' # Validate performance settings self.max_concurrent_processes = max(1, self.max_concurrent_processes) self.gpu_memory_fraction = max(0.1, min(1.0, self.gpu_memory_fraction)) self.batch_size = max(1, self.batch_size) self.num_workers = max(0, self.num_workers) # Validate API settings self.api_port = max(1024, min(65535, self.api_port)) # Validate confidence threshold self.confidence_threshold = max(0.0, min(1.0, self.confidence_threshold)) # Validate cache size self.cache_size_limit_gb = max(0.1, self.cache_size_limit_gb) def _create_directories(self): """Create necessary directories if they don't exist""" directories = [ self.model_cache_dir, self.temp_dir, self.output_dir, Path(self.output_dir) / 'masks', Path(self.output_dir) / 'greenscreen', Path(self.output_dir) / 'final', Path(self.output_dir) / 'two_stage' ] # Add MatAnyone component directories if enabled if self.use_component_masks: directories.extend([ self.hair_masks_dir, self.edge_masks_dir, self.detail_masks_dir ]) for directory in directories: try: Path(directory).mkdir(parents=True, exist_ok=True) logger.debug(f"Ensured directory exists: {directory}") except Exception as e: logger.error(f"Failed to create directory {directory}: {e}") def _setup_logging(self): """Setup logging based on configuration""" log_levels = { 'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR, 'CRITICAL': logging.CRITICAL } level = log_levels.get(self.log_level.upper(), logging.INFO) logging.getLogger().setLevel(level) def _log_config(self): """Log current configuration in debug mode""" logger.info("=" * 60) logger.info(f"{self.app_name} v{self.version} Configuration") logger.info("=" * 60) config_dict = self.to_dict() # Hide sensitive information if config_dict.get('api_key'): config_dict['api_key'] = '***hidden***' if config_dict.get('gradio_auth'): config_dict['gradio_auth'] = '***hidden***' for key, value in config_dict.items(): logger.info(f"{key}: {value}") logger.info("=" * 60) def to_dict(self) -> Dict[str, Any]: """Convert configuration to dictionary""" return asdict(self) def to_json(self, filepath: Optional[str] = None) -> str: """Export configuration to JSON""" config_dict = self.to_dict() if filepath: with open(filepath, 'w') as f: json.dump(config_dict, f, indent=2) logger.info(f"Configuration saved to {filepath}") return json.dumps(config_dict, indent=2) def to_yaml(self, filepath: Optional[str] = None) -> str: """Export configuration to YAML""" config_dict = self.to_dict() if filepath: with open(filepath, 'w') as f: yaml.dump(config_dict, f, default_flow_style=False) logger.info(f"Configuration saved to {filepath}") return yaml.dump(config_dict, default_flow_style=False) @classmethod def from_json(cls, filepath: str) -> 'ProcessingConfig': """Load configuration from JSON file""" with open(filepath, 'r') as f: config_dict = json.load(f) return cls(**config_dict) @classmethod def from_yaml(cls, filepath: str) -> 'ProcessingConfig': """Load configuration from YAML file""" with open(filepath, 'r') as f: config_dict = yaml.safe_load(f) return cls(**config_dict) def get_matanyone_config(self) -> Dict[str, Any]: """Get MatAnyone-specific configuration as a dictionary""" return { 'enabled': self.matanyone_enabled, 'model_path': self.matanyone_model_path, 'threshold': self.matanyone_threshold, 'edge_refinement': self.matanyone_edge_enhancement, 'hair_refinement': self.matanyone_hair_refinement, 'detail_refinement': self.matanyone_detail_refinement, 'morphology_ops': self.matanyone_morphology_ops, 'morphology_kernel_size': self.matanyone_morphology_kernel_size, 'processing_mode': self.matanyone_processing_mode, 'blend_alpha': self.matanyone_blend_alpha, 'trimap_enabled': self.matanyone_trimap_enabled, 'trimap_dilation': self.matanyone_trimap_dilation, 'component_weights': { 'base': self.matanyone_weight_base, 'hair': self.matanyone_weight_hair, 'edge': self.matanyone_weight_edge, 'detail': self.matanyone_weight_detail }, 'component_paths': { 'hair': self.hair_masks_dir if self.use_component_masks else None, 'edge': self.edge_masks_dir if self.use_component_masks else None, 'detail': self.detail_masks_dir if self.use_component_masks else None }, 'precision': self.matanyone_precision } def get_quality_settings(self) -> Dict[str, Any]: """Get quality-specific settings based on preset""" quality_maps = { 'fast': { 'keyframe_interval': max(self.keyframe_interval, 10), 'frame_skip': max(self.frame_skip, 2), 'edge_refinement': False, 'temporal_consistency': False, 'model_precision': 'fp16', 'batch_size': min(self.batch_size * 2, 16), 'output_quality_params': '-preset ultrafast -crf 28', 'matanyone_enabled': False # Disable MatAnyone for fast mode }, 'balanced': { 'keyframe_interval': self.keyframe_interval, 'frame_skip': self.frame_skip, 'edge_refinement': True, 'temporal_consistency': True, 'model_precision': 'fp32', 'batch_size': self.batch_size, 'output_quality_params': '-preset medium -crf 23', 'matanyone_enabled': self.matanyone_enabled }, 'high': { 'keyframe_interval': max(self.keyframe_interval // 2, 1), 'frame_skip': 1, 'edge_refinement': True, 'temporal_consistency': True, 'model_precision': 'fp32', 'batch_size': max(self.batch_size // 2, 1), 'output_quality_params': '-preset slow -crf 18', 'matanyone_enabled': True # Enable MatAnyone for high quality }, 'ultra': { 'keyframe_interval': 1, 'frame_skip': 1, 'edge_refinement': True, 'temporal_consistency': True, 'model_precision': 'fp32', 'batch_size': 1, 'output_quality_params': '-preset veryslow -crf 15', 'matanyone_enabled': True, # Always enable MatAnyone for ultra 'matanyone_morphology_ops': True, 'matanyone_trimap_enabled': True } } return quality_maps.get(self.quality_preset, quality_maps['balanced']) def get_resolution_limits(self) -> tuple[int, int]: """Get max width and height from resolution setting""" try: width, height = map(int, self.max_video_resolution.split('x')) return width, height except ValueError: logger.error(f"Invalid resolution format: {self.max_video_resolution}") return 1920, 1080 def get_output_params(self) -> Dict[str, str]: """Get FFmpeg output parameters based on settings""" quality_settings = self.get_quality_settings() codec_map = { 'h264': 'libx264', 'h265': 'libx265', 'vp9': 'libvpx-vp9', 'av1': 'libaom-av1' } return { 'codec': codec_map.get(self.output_codec, 'libx264'), 'quality': quality_settings['output_quality_params'], 'format': self.output_format, 'audio': '-c:a copy' if self.preserve_audio else '-an' } def is_high_performance_mode(self) -> bool: """Check if configuration is set for high performance""" return ( self.quality_preset in ['high', 'ultra'] and self.edge_refinement and self.temporal_consistency and self.keyframe_interval <= 3 and self.matanyone_enabled # Include MatAnyone in high performance check ) def get_memory_limits(self) -> Dict[str, Any]: """Get memory-related limits""" return { 'gpu_memory_fraction': self.gpu_memory_fraction, 'cleanup_interval': self.memory_cleanup_interval, 'max_concurrent': self.max_concurrent_processes, 'threshold_mb': self.memory_threshold_mb, 'cache_size_gb': self.cache_size_limit_gb } def validate_for_production(self) -> List[str]: """Validate configuration for production deployment""" warnings = [] if self.debug_mode: warnings.append("Debug mode is enabled in production") if self.save_intermediate_results: warnings.append("Intermediate results saving is enabled (disk usage)") if not self.cleanup_temp_files: warnings.append("Temp file cleanup is disabled (disk usage)") if self.gradio_share: warnings.append("Gradio share is enabled (security risk)") if not self.api_key and self.api_enabled: warnings.append("API is enabled without authentication") if self.gpu_memory_fraction > 0.9: warnings.append("GPU memory fraction is very high (>90%)") if self.max_concurrent_processes > 4: warnings.append("High concurrent processes may cause instability") # MatAnyone-specific warnings if self.matanyone_enabled and self.quality_preset == 'fast': warnings.append("MatAnyone is enabled with 'fast' quality preset (may impact performance)") if self.use_component_masks and not Path(self.hair_masks_dir).exists(): warnings.append(f"Component masks enabled but directory not found: {self.hair_masks_dir}") return warnings # Singleton instance for application-wide use _config_instance: Optional[ProcessingConfig] = None def get_config() -> ProcessingConfig: """Get global configuration instance""" global _config_instance if _config_instance is None: _config_instance = ProcessingConfig() return _config_instance def reload_config() -> ProcessingConfig: """Reload configuration from environment variables""" global _config_instance _config_instance = ProcessingConfig() logger.info("Configuration reloaded from environment variables") return _config_instance def update_config(**kwargs) -> ProcessingConfig: """Update configuration with new values""" global _config_instance if _config_instance is None: _config_instance = ProcessingConfig() for key, value in kwargs.items(): if hasattr(_config_instance, key): setattr(_config_instance, key, value) logger.debug(f"Updated config: {key} = {value}") else: logger.warning(f"Unknown configuration key: {key}") # Re-validate after updates _config_instance._validate_config() return _config_instance def load_config_from_file(filepath: str) -> ProcessingConfig: """Load configuration from file (JSON or YAML)""" global _config_instance file_path = Path(filepath) if not file_path.exists(): raise FileNotFoundError(f"Configuration file not found: {filepath}") if file_path.suffix.lower() in ['.json']: _config_instance = ProcessingConfig.from_json(filepath) elif file_path.suffix.lower() in ['.yaml', '.yml']: _config_instance = ProcessingConfig.from_yaml(filepath) else: raise ValueError(f"Unsupported configuration file format: {file_path.suffix}") logger.info(f"Configuration loaded from {filepath}") return _config_instance