""" Central configuration for DeepFakeScanner. All runtime knobs are read from environment variables with sensible defaults so the service can run unchanged from local dev → HF Spaces → Cloud Run. Read this once at import; do NOT scatter os.getenv() calls throughout the code. """ from __future__ import annotations import os from dataclasses import dataclass def _env_bool(name: str, default: bool) -> bool: raw = os.getenv(name) if raw is None: return default return raw.strip().lower() in {"1", "true", "yes", "on"} def _env_int(name: str, default: int) -> int: raw = os.getenv(name) if raw is None: return default try: return int(raw) except ValueError: return default @dataclass(frozen=True) class Settings: # ------------------------------------------------------------------ API api_title: str = "DeepFakeScanner" api_version: str = "0.2.0" max_upload_bytes: int = _env_int("MAX_UPLOAD_BYTES", 10 * 1024 * 1024) # 10 MB # --------------------------------------------------------- Image policy # Privacy-by-default: visitor uploads are NEVER persisted. # Logged-in paid users will be able to opt in later via a separate flag. store_uploads: bool = _env_bool("STORE_UPLOADS", False) # ---------------------------------------------------------- Detectors # Stage 1: only the CLIP-based classifier is wired up. # Stage 2 will flip these on as the detectors are trained / verified. enable_clip_detector: bool = _env_bool("ENABLE_CLIP_DETECTOR", True) enable_frequency_detector: bool = _env_bool("ENABLE_FREQUENCY_DETECTOR", False) enable_face_swap_detector: bool = _env_bool("ENABLE_FACE_SWAP_DETECTOR", False) # The CLIP backbone we use for feature extraction. # OpenAI CLIP ViT-B/32 weights are MIT-licensed (commercially safe). clip_model_name: str = os.getenv("CLIP_MODEL_NAME", "openai/clip-vit-base-patch32") # Path to the trained classifier-head checkpoint. ClipClassifier auto- # loads this in __init__ if the file exists. The same env var works # locally (dev machine) and inside the inference Docker image. # Stage 3A: head_v3a.pt (multi-generator: Flux+SDXL+SD3.5+AuraFlow). head_checkpoint_path: str = os.getenv( "HEAD_CHECKPOINT_PATH", "data/checkpoints/head_v3a.pt" ) # Fallback for production: if the local checkpoint file isn't present, # ClipClassifier downloads the weights from this HF Hub model repo at # startup. The repo is PRIVATE — the container authenticates with the # HF_TOKEN secret set in the HF Space settings (huggingface_hub reads # HF_TOKEN from the environment automatically). Setting this to an # empty string disables the fallback (used in tests). # Stage 3A: head_v3a.pt. The Stage 2 head_v1.pt remains in the same # private repo for rollback — set HEAD_CHECKPOINT_HF_FILENAME=head_v1.pt # (and MODEL_VERSION=v0.3.0-stage2) to revert without a redeploy. head_checkpoint_hf_repo: str = os.getenv( "HEAD_CHECKPOINT_HF_REPO", "Veridicate/scanner-head-v1" ) head_checkpoint_hf_filename: str = os.getenv( "HEAD_CHECKPOINT_HF_FILENAME", "head_v3a.pt" ) # ---------------------------------------------------------- Provenance enable_c2pa_check: bool = _env_bool("ENABLE_C2PA_CHECK", True) # ------------------------------------------------------------ Storage # Postgres URL for scan records (None → in-memory fallback for local dev). database_url: str | None = os.getenv("DATABASE_URL") # S3-compatible blob storage (e.g. Cloudflare R2). Only used when # store_uploads is True AND a paid user opts in. blob_endpoint: str | None = os.getenv("BLOB_ENDPOINT") blob_bucket: str | None = os.getenv("BLOB_BUCKET") # ---------------------------------------------------- Model identifier # Surfaced in API responses so clients can pin behaviour to a version. # v0.4.0-stage3a = CLIP head trained on a multi-generator dataset # (50k Flux + 20k SDXL + 20k SD 3.5 + 10k AuraFlow + matched authentic). # In-distribution test accuracy 98.43%; +5.55 pp vs the Stage 2 head on # the SDXL hold-out (a generator never seen in training). See # docs/stage3a-implementation.md. # Previous: v0.3.0-stage2 (Flux-only, head_v1.pt) — still available in the # HF Hub repo for rollback (see head_checkpoint_hf_filename above). model_version: str = os.getenv("MODEL_VERSION", "v0.4.0-stage3a") # --------------------------------------------------------------- CORS # Browser preflight allowlist for the static-HTML frontend. The default # covers the production domain, any Cloudflare Pages preview, and # localhost (any port) for local dev. Override via env var if a new # origin needs access. cors_allow_origin_regex: str = os.getenv( "CORS_ALLOW_ORIGIN_REGEX", r"^(https://(www\.)?veridicate\.com|https://[a-z0-9-]+\.pages\.dev|https://[a-z0-9-]+\.dulipcf\.workers\.dev|http://localhost(:\d+)?|http://127\.0\.0\.1(:\d+)?)$", ) settings = Settings()