"""Paths, constants, and the timebase convention. Timebase convention (FROZEN CONTRACT — do not change without a change request): ``t_global`` is the master timeline in seconds. For a video *v* with ``offset_ms`` and ``drift_ppm``:: t_video = (t_global - offset_ms / 1000) * (1 + drift_ppm * 1e-6) The reference video has ``offset_ms == 0`` (and ``drift_ppm == 0``). A positive ``offset_ms`` means the video started recording *later* than the master zero, so at a given ``t_global`` its local playhead is earlier. Use :func:`t_video_from_global` and :func:`t_global_from_video` everywhere — never re-derive the algebra inline. The frontend mirrors :func:`t_video_from_global` in ``transport.js``. """ from __future__ import annotations import os from pathlib import Path # --------------------------------------------------------------------------- # Paths. The data root can be overridden with FESTIVAL4D_DATA_DIR (used by tests # to run against a temporary project without touching the repo's data/). # --------------------------------------------------------------------------- REPO_ROOT = Path(__file__).resolve().parents[2] def _data_dir() -> Path: env = os.environ.get("FESTIVAL4D_DATA_DIR") return Path(env).resolve() if env else REPO_ROOT / "data" DATA_DIR = _data_dir() RAW_DIR = DATA_DIR / "raw" # user drops source videos here (gitignored) WORK_DIR = DATA_DIR / "work" # extracted wavs, frames, colmap workspace (gitignored) AUDIO_DIR = WORK_DIR / "audio" # per-video mono 16 kHz wavs (ingest) FRAMES_DIR = WORK_DIR / "frames" # sampled JPEGs for SfM COLMAP_DIR = WORK_DIR / "colmap" # COLMAP workspace DB_PATH = DATA_DIR / "project.db" # SQLite (gitignored) POINTS_PLY = WORK_DIR / "points.ply" # reconstructed / synthetic point cloud SPLAT_PLY = WORK_DIR / "splat.ply" # optional 3DGS splat (trained externally, e.g. via MODELBEAST — see docs/modelbeast-crossover.md); viewer prefers it over the point cloud SYNC_JSON = WORK_DIR / "sync.json" # exported sync solution (lane A) GROUND_TRUTH_JSON = WORK_DIR / "ground_truth.json" # synthetic fixture ground truth # --------------------------------------------------------------------------- # Constants # --------------------------------------------------------------------------- FRONTEND_ORIGINS = [ "http://localhost:5173", "http://127.0.0.1:5173", ] # Local-only, single-user, offline tool: allow any localhost origin so a Vite dev server on # a fallback port (5174, …) still works. Kept alongside the explicit 5173 default. FRONTEND_ORIGIN_REGEX = r"http://(localhost|127\.0\.0\.1)(:\d+)?" API_HOST = "127.0.0.1" API_PORT = 8000 AUDIO_SAMPLE_RATE = 16_000 # ingest extracts mono 16 kHz for sync (M1) # Synthetic fixture parameters (M0). Ground-truth offsets are deliberately odd, # non-round numbers so a naive integer-second guesser can't accidentally "pass". SYNTH_VIDEO_W = 640 SYNTH_VIDEO_H = 360 SYNTH_FPS = 30 SYNTH_DURATION_S = 20.0 SYNTH_AUDIO_SR = 48_000 SYNTH_OFFSETS_MS = (0.0, 1370.0, -842.0) # cam0 is the reference (offset 0) def t_video_from_global(t_global: float, offset_ms: float, drift_ppm: float = 0.0) -> float: """Map master-timeline seconds to a video's local playhead seconds (FROZEN).""" return (t_global - offset_ms / 1000.0) * (1.0 + drift_ppm * 1e-6) def t_global_from_video(t_video: float, offset_ms: float, drift_ppm: float = 0.0) -> float: """Inverse of :func:`t_video_from_global` (FROZEN).""" return t_video / (1.0 + drift_ppm * 1e-6) + offset_ms / 1000.0