#pragma once #include "inference/backend_config.hpp" #include inline const std::string kDefaultDetectorModel = std::string(SAE_MODELS_DIR) + "/scrfd_500m_bnkps.onnx"; inline const std::string kDefaultArcfaceModel = std::string(SAE_MODELS_DIR) + "/LVFace-B_Glint360K.onnx"; inline const std::string kDefaultSceneModel = std::string(SAE_MODELS_DIR) + "/transnetv2.onnx"; enum class Verbosity { minimal, // actor names + merged time windows only standard, // per-frame detail: bbox, similarity, unknowns logged xray, // Jellyfin-Xray format: {"second": ["Actor", ...], ...} }; // debug verbosity = compile with -DSAE_DEBUG → scene_analyze_debug binary struct Config { // ── Input ───────────────────────────────────────────────────────────────── std::string movie_path; std::string gallery_path; // TRACES: IR-002 | SR-003 // "global" (matched against the whole library) or "limited" (this title's // credited cast only). The strongest single quality signal when two // manifests compete for the same cut: identical gallery_size can mean very // different recall depending on which was used. std::string gallery_scope{"global"}; // gallery.json produced by build_gallery // ── Output ─────────────────────────────────────────────────────────────── std::string output_path; // annotations.json Verbosity verbosity{Verbosity::minimal}; // When set, tee the embedder output to an HDF5 dump (schema: // scripts/optimizer/SCHEMA.md) for offline threshold-sweep replay via sae_kpn. std::string dump_embeddings_path; // ── Sampling ───────────────────────────────────────────────────────────── float sample_fps{1.0f}; // frames to analyse per second of movie float max_decode_fps{0.f}; // wall-clock cap on source decode rate (0 = uncapped) double start_sec{0.0}; // seek to this timestamp before sampling double end_sec{-1.0}; // stop at this timestamp (-1 = end of file) // ── Detection (SCRFD-500MF via cv::dnn::Net) ────────────────────────────── std::string detector_model; std::string detector_engine; // optional path to pre-built TRT engine; bypasses ORT int max_faces{10}; // pipeline cap: keep only the N largest faces float min_face_px{40.f}; // discard detections narrower or shorter than this float detector_conf{0.5f}; float detector_nms{0.4f}; /// TRACES: GR-004 | SR-001 // Gallery ↔ embedder binding. A gallery built with a different model than the // one loaded here is a hard error, always. This flag additionally promotes // "cannot prove they match" (unstamped legacy gallery, or a name-only match // because the ONNX could not be hashed) from a loud warning to a hard error. // Also settable via SAE_REQUIRE_GALLERY_STAMP=1. Measurement runs want it on. bool require_gallery_stamp{false}; // --require-gallery-stamp // ── Recognition (ArcFace ONNX) ──────────────────────────────────────────── std::string arcface_model; std::string arcface_engine; // optional path to a pre-built TRT engine; bypasses ORT int embed_batch_size{4}; // max faces per ORT Run() call — bounds per-call latency float match_prior{0.5f}; // base-rate prior; 0.5 = use calibrated sigmoid directly // prob_threshold tuned by Differential Evolution against Amazon X-Ray per-scene // presence over 4 films, per-second metric (see docs/rep4-optimizer-results.md). // Best model+mode: LVFace-B_Glint360K, full gallery, expansion on. Supersedes the // earlier 9-film scene-union-metric tuning (0.76) — that metric is now known to // have hidden out-of-cast false positives (see docs/optimizer-experiments.md). float prob_threshold{0.754f}; // posterior P(match | sim, prior) threshold float match_threshold{0.45f}; // cosine distance hard ceiling fallback (no calibration) float match_ratio{0.80f}; // ratio test fallback: accept if best/second < ratio float match_ratio_ceil{0.65f}; // ratio test only fires below this absolute distance // ── Cut detection ──────────────────────────────────────────────────────── float cut_threshold{0.70f}; // grayscale histogram correlation below this → hard cut // ── Scene detection (TransNetV2, opt-in) ───────────────────────────────── // When enabled, the source decodes densely (native FPS) and a decimator // splits the stream: full-res 1-FPS frames to the face pipeline, and a // downscaled dense stream to the TransNetV2 scene detector. Shot boundaries // it finds are surfaced as Frame::is_scene_boundary. This is separate from // the always-on histogram cut, which flags intra-scene camera-angle changes. bool scene_detect{false}; // master switch (--scene-detect) std::string scene_model; // TransNetV2 .onnx (default set in main) std::string scene_engine; // optional pre-built TRT .engine; bypasses ORT float scene_threshold{0.60f}; // sigmoid boundary prob above this → boundary // (this export's non-boundary baseline sits // at ~0.50; real boundaries spike to ~0.7+) int scene_stride{50}; // frames advanced between windows (≤ kWindow) // Dense-decode throughput knobs (only active with scene_detect). Dense decode // of every native-rate frame is the pipeline's cost driver; these trade a // little boundary precision for a large speedup. // scene_decode_fps: rate the source decodes at in dense mode. Lower = // fewer frames decoded. TransNetV2 tolerates ~12fps; boundary timestamps // stay correct (keyed off each frame's real timestamp). 0 = native fps. // dense_scale: downscale factor applied to decoded frames in dense mode // (0