|
""" |
|
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 |
|
""" |
|
|
|
app_name: str = "BackgroundFX Pro" |
|
version: str = "2.0.0" |
|
|
|
|
|
keyframe_interval: int = int(os.getenv('KEYFRAME_INTERVAL', '5')) |
|
frame_skip: int = int(os.getenv('FRAME_SKIP', '1')) |
|
|
|
|
|
memory_cleanup_interval: int = int(os.getenv('MEMORY_CLEANUP_INTERVAL', '30')) |
|
memory_threshold_mb: int = int(os.getenv('MEMORY_THRESHOLD_MB', '1024')) |
|
|
|
|
|
max_video_length: int = int(os.getenv('MAX_VIDEO_LENGTH', '300')) |
|
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_preset: str = os.getenv('QUALITY_PRESET', 'balanced') |
|
|
|
|
|
sam2_model_size: str = os.getenv('SAM2_MODEL_SIZE', 'large') |
|
matanyone_precision: str = os.getenv('MATANYONE_PRECISION', 'fp32') |
|
model_device: str = os.getenv('MODEL_DEVICE', 'auto') |
|
|
|
|
|
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')) |
|
|
|
|
|
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_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_mode: str = os.getenv('MATANYONE_PROCESSING_MODE', 'refine') |
|
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')) |
|
|
|
|
|
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_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_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')) |
|
|
|
|
|
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_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') |
|
|
|
|
|
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')) |
|
|
|
|
|
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')) |
|
|
|
|
|
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_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')) |
|
|
|
|
|
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')) |
|
|
|
|
|
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' |
|
|
|
|
|
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') |
|
|
|
|
|
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_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_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_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_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_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' |
|
|
|
|
|
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')) |
|
|
|
|
|
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_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')) |
|
|
|
|
|
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')) |
|
|
|
|
|
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')) |
|
|
|
|
|
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_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' |
|
|
|
|
|
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')) |
|
|
|
|
|
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_dir: str = os.getenv('OUTPUT_DIR', 'outputs') |
|
output_format: str = os.getenv('OUTPUT_FORMAT', 'mp4') |
|
output_quality: str = os.getenv('OUTPUT_QUALITY', 'high') |
|
output_codec: str = os.getenv('OUTPUT_CODEC', 'h264') |
|
write_fps: Optional[float] = None |
|
preserve_audio: bool = os.getenv('PRESERVE_AUDIO', 'true').lower() == 'true' |
|
|
|
|
|
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')) |
|
|
|
|
|
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_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) |
|
|
|
|
|
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) |
|
|
|
|
|
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' |
|
|
|
|
|
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_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""" |
|
|
|
self.keyframe_interval = max(1, self.keyframe_interval) |
|
self.frame_skip = max(1, self.frame_skip) |
|
|
|
|
|
self.memory_cleanup_interval = max(1, self.memory_cleanup_interval) |
|
self.memory_threshold_mb = max(256, self.memory_threshold_mb) |
|
|
|
|
|
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)) |
|
|
|
|
|
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' |
|
|
|
|
|
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' |
|
|
|
|
|
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' |
|
|
|
|
|
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)) |
|
|
|
|
|
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' |
|
|
|
|
|
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)) |
|
|
|
|
|
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)) |
|
|
|
|
|
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)) |
|
|
|
|
|
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' |
|
|
|
|
|
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)) |
|
|
|
|
|
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)) |
|
|
|
|
|
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' |
|
|
|
|
|
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)) |
|
|
|
|
|
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 |
|
self.gaussian_sigma = max(0.0, min(10.0, self.gaussian_sigma)) |
|
|
|
|
|
self.gpu_device_id = max(0, self.gpu_device_id) |
|
|
|
|
|
valid_codecs = ['mp4v', 'h264', 'h265', 'xvid', 'mjpeg'] |
|
if self.video_codec not in valid_codecs: |
|
self.video_codec = 'mp4v' |
|
|
|
|
|
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)) |
|
|
|
|
|
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)) |
|
|
|
|
|
self.contour_approx_epsilon = max(0.0, min(1.0, self.contour_approx_epsilon)) |
|
self.contour_min_points = max(3, self.contour_min_points) |
|
|
|
|
|
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)) |
|
|
|
|
|
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) |
|
|
|
|
|
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)) |
|
|
|
|
|
self.chroma_tolerance = max(0.0, min(1.0, self.chroma_tolerance)) |
|
self.chroma_softness = max(0.0, min(1.0, self.chroma_softness)) |
|
|
|
|
|
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) |
|
|
|
|
|
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)) |
|
|
|
|
|
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)) |
|
|
|
|
|
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) |
|
|
|
|
|
self.preview_scale = max(0.1, min(2.0, self.preview_scale)) |
|
|
|
|
|
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)) |
|
|
|
|
|
self.max_cache_size_mb = max(0, self.max_cache_size_mb) |
|
self.clear_cache_interval = max(1, self.clear_cache_interval) |
|
|
|
|
|
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' |
|
|
|
|
|
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) |
|
|
|
|
|
self.api_port = max(1024, min(65535, self.api_port)) |
|
|
|
|
|
self.confidence_threshold = max(0.0, min(1.0, self.confidence_threshold)) |
|
|
|
|
|
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' |
|
] |
|
|
|
|
|
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() |
|
|
|
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 |
|
}, |
|
'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 |
|
}, |
|
'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, |
|
'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 |
|
) |
|
|
|
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") |
|
|
|
|
|
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 |
|
|
|
|
|
_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}") |
|
|
|
|
|
_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 |