"""Central configuration, loaded from environment variables. Every setting has a sane default so the app runs locally without a `.env` file, but production deploys should set these via `compose.yml` / `.env`. """ from __future__ import annotations import os from pathlib import Path def _int_env(name: str, default: int) -> int: try: return int(os.environ.get(name, str(default))) except ValueError: return default def _float_env(name: str, default: float) -> float: try: return float(os.environ.get(name, str(default))) except ValueError: return default # --- Paths ------------------------------------------------------------- DATA_DIR = Path(os.environ.get("DATA_DIR", "/data")) INBOX_DIR = DATA_DIR / "inbox" JOBS_DIR = DATA_DIR / "jobs" ASSETS_DIR = DATA_DIR / "assets" PROCESSED_FILE = DATA_DIR / "processed.json" BACKGROUND_FILE = ASSETS_DIR / "background" FOREGROUND_FILE = ASSETS_DIR / "foreground" BLEND_MODES_FILE = ASSETS_DIR / "blend_modes" FILTERS_JSON = ASSETS_DIR / "filters.json" # --- Processing ---------------------------------------------------------- OUTPUT_COUNT = _int_env("OUTPUT_COUNT", 3) # Fallback / remix default. Auto variants pick randomly in # [BLEND_OPACITY_MIN, BLEND_OPACITY_MAX] instead. BLEND_OPACITY = os.environ.get("BLEND_OPACITY", "30%") BLEND_OPACITY_MIN = _int_env("BLEND_OPACITY_MIN", 10) BLEND_OPACITY_MAX = _int_env("BLEND_OPACITY_MAX", 50) FILTER_TIMEOUT = _int_env("FILTER_TIMEOUT", 120) # Second-chance timeout for variants that hit FILTER_TIMEOUT during the # normal pass — retried once at the end of the job (not during filter probes). FILTER_TIMEOUT_LONG = _int_env("FILTER_TIMEOUT_LONG", 300) MAX_FILTER_ATTEMPTS = _int_env("MAX_FILTER_ATTEMPTS", 8) # Longest edge after preprocess (ImageMagick). Keeps rembg/gmic sane on # large camera JPEGs. MAX_EDGE_PX = _int_env("MAX_EDGE_PX", 2000) # Generous on purpose: the very first rembg call also downloads the model, # which can take a while depending on the link. REMBG_TIMEOUT = _int_env("REMBG_TIMEOUT", 600) # birefnet-general OOMs on the ~8GB event box; u2net + alpha matting fits # and is the practical equivalent of `rembg i -a`. REMBG_MODEL = os.environ.get("REMBG_MODEL", "u2net") REMBG_ALPHA = os.environ.get("REMBG_ALPHA", "1").strip().lower() in {"1", "true", "yes", "on"} NICE_LEVEL = _int_env("NICE_LEVEL", 18) # gmic CLI binary, override for local dev if not on PATH. rembg has no # equivalent here — it runs via `python -m app.rembg_cli` (see pipeline.py # run_rembg / app/rembg_cli.py), not a standalone binary. GMIC_BIN = os.environ.get("GMIC_BIN", "gmic") # ImageMagick binary (`magick` on IM7, `convert` on IM6). Empty = auto-detect. MAGICK_BIN = os.environ.get("MAGICK_BIN", "") # Fixed post-processing chain applied to every composed variant, ported # verbatim from make_random.py. POST_FILTERS: tuple[str, ...] = ( "fx_equalize_local_histograms 75,2,4,100,8,1,0", "fx_map_tones 0.5,0.7,0.1,30,0", "fx_LCE 80,0.5,1,1,0,0", ) # Opacity choices offered in the remix form (blend "amount" percentages). OPACITY_CHOICES: tuple[str, ...] = ( "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "100%", ) SUPPORTED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp"} # --- Inbox watcher --------------------------------------------------------- POLL_INTERVAL_SECONDS = _float_env("POLL_INTERVAL_SECONDS", 5) STABLE_WAIT_SECONDS = _float_env("STABLE_WAIT_SECONDS", 2) # --- Web --------------------------------------------------------------- SITE_TITLE = os.environ.get("SITE_TITLE", "live.f12.rocks")