"""Paths and settings. Everything is environment-driven so the same image runs locally and in Docker without code changes (REQ-072).""" import os from dotenv import load_dotenv load_dotenv() # huggingface_hub reads HF_TOKEN / HUGGING_FACE_HUB_TOKEN; accept either name in # .env so pasting a token under the obvious name just works. if os.environ.get("HF_TOKEN") and not os.environ.get("HUGGING_FACE_HUB_TOKEN"): os.environ["HUGGING_FACE_HUB_TOKEN"] = os.environ["HF_TOKEN"] REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DATA_DIR = os.path.abspath(os.environ.get("APP_DATA_DIR", os.path.join(REPO_ROOT, "data"))) PROJECTS_DIR = os.path.join(DATA_DIR, "projects") DB_PATH = os.path.join(DATA_DIR, "app.db") def resolve_data_path(path): """Resolve a DB-stored weight path against today's DATA_DIR (REQ-187).""" if not path: return path if os.path.isfile(path): return path if not os.path.isabs(path): candidate = os.path.join(DATA_DIR, path) return candidate if os.path.isfile(candidate) else path # legacy absolute path from before the data dir moved: .../data/ marker = os.sep + "data" + os.sep idx = path.find(marker) if idx != -1: candidate = os.path.join(DATA_DIR, path[idx + len(marker):]) if os.path.isfile(candidate): return candidate return path def rel_data_path(path): """Store paths under DATA_DIR relative to it (REQ-187); others unchanged.""" try: rel = os.path.relpath(path, DATA_DIR) except ValueError: return path return path if rel.startswith(os.pardir) else rel # Where the video archive is mounted. Projects store a path relative to nothing — # they store an absolute one — but this is the default the UI starts browsing from. VIDEO_ROOT = os.path.abspath(os.environ.get("VIDEO_ARCHIVE", os.path.join(DATA_DIR, "archive"))) def archive_host_paths() -> dict: """Where the archive sits on the host — for the copy-path buttons (REQ-179). VIDEO_ARCHIVE_HOST may stay relative (portable across machines): compose injects the host cwd as ARCHIVE_HOST_PWD (compose always has PWD — a Windows-side run yields a drive-letter path, handled below), bare-metal resolves against the process cwd (start.sh cds to the repo root).""" import re host = os.environ.get("VIDEO_ARCHIVE_HOST", "") if host and not os.path.isabs(host): base = os.environ.get("ARCHIVE_HOST_PWD") or os.getcwd() host = os.path.abspath(os.path.join(base, host)) if not host and VIDEO_ROOT != "/videos": host = VIDEO_ROOT # bare-metal with no VIDEO_ARCHIVE_HOST at all if not host: return {"linux": None, "windows": None} linux = host.rstrip("/") if re.match(r"^[A-Za-z]:[\\/]", linux): return {"linux": None, "windows": linux.replace("/", "\\")} distro = os.environ.get("WSL_DISTRO_NAME", "") windows = f"\\\\wsl.localhost\\{distro}\\" + linux.lstrip("/").replace("/", "\\") if distro else None return {"linux": linux, "windows": windows} # Vite dev server needs cross-origin access; in Docker nginx proxies /api and # this is irrelevant. CORS_ORIGINS = [ origin.strip() for origin in os.environ.get("CORS_ORIGINS", "http://localhost:5173").split(",") if origin.strip() ] def ensure_dirs() -> None: os.makedirs(PROJECTS_DIR, exist_ok=True) def project_dir(slug: str) -> str: return os.path.join(PROJECTS_DIR, slug)