from __future__ import annotations from dataclasses import dataclass, field from pathlib import Path from typing import Optional SUPPORTED_FORMATS: frozenset[str] = frozenset({"avif", "jpg", "jpeg", "png", "webp"}) SUPPORTED_MIME_TYPES: frozenset[str] = frozenset({ "image/avif", "image/jpeg", "image/jpg", "image/png", "image/webp", }) @dataclass class Config: """Runtime configuration for the Image Processor Pro pipeline.""" output_root: Path = Path("output") logs_root: Path = Path("logs") jpeg_quality: int = 85 max_width: Optional[int] = None max_height: Optional[int] = None download_timeout: int = 30 max_retries: int = 3 retry_backoff: float = 1.5 max_workers: int = 8 chunk_size: int = 16384 user_agent: str = "ImageProcessorPro/1.0 (+https://example.local)" strip_metadata: bool = True background_color: tuple[int, int, int] = (255, 255, 255) # Watermark removal — color-agnostic text detection inside a fixed region. # Uses morphological top-hat + black-hat to isolate thin text strokes # (works for white OR black text on any background), filtered by # connected-component area so uniform backgrounds aren't masked. remove_watermark: bool = False watermark_engine: str = "lama" # inpaint backend: "lama" (AI) or "classical" (cv2) watermark_device: str = "cpu" # LaMa compute device: "cpu" or "mps" watermark_x_frac: float = 0.0 # search region left edge (fraction) watermark_y_frac: float = 0.93 # search region top edge (fraction) watermark_w_frac: float = 0.55 # search region width (fraction) watermark_h_frac: float = 0.07 # search region height (fraction) watermark_text_kernel: int = 9 # morphological kernel size (px, odd) watermark_text_threshold: int = 25 # contrast threshold (0-255) watermark_min_component_area: int = 4 # smallest CC kept (noise filter) watermark_max_area_frac: float = 0.6 # reject a blob bigger than this fraction of the ROI watermark_inpaint_radius: int = 3 # cv2.inpaint radius in pixels watermark_mask_feather: int = 2 # px of dilation on the mask edge (covers anti-aliased text halo) allowed_formats: frozenset[str] = field(default_factory=lambda: SUPPORTED_FORMATS) def output_dir_for_today(self) -> Path: from datetime import datetime day = datetime.now().strftime("%Y-%m-%d") path = self.output_root / day path.mkdir(parents=True, exist_ok=True) return path def ensure_dirs(self) -> None: self.output_root.mkdir(parents=True, exist_ok=True) self.logs_root.mkdir(parents=True, exist_ok=True)