Files
livef12rocks/app/config.py
T
Frank Schwenk d5b44b221e feat: replace SFTP drop with Syncthing share path
Remove SFTPGo; mount event data from the Syncthing folder, watch
incoming/, and name variants after the source stem.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-18 14:06:57 +02:00

100 lines
3.6 KiB
Python

"""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 / "incoming"
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")