feat(tooling): X-Ray threshold optimizer, gallery utilities, artifact registry, docs build
Optimizer (scripts/optimizer/): replay.py runs the real C++ tracker/matcher/ scene_tracker chain over a dumped-embeddings HDF5 via sae_kpn, so a threshold sweep never re-decodes video or re-embeds faces. optimize.py drives scipy's differential_evolution over the knob space, with DE-level parallelism (multiple population candidates evaluated concurrently via a ThreadPoolExecutor) on top of per-film replay parallelism. second_score.py is the per-second X-Ray scoring metric (TPI/FPI/FN, out-of-cast misID weighted 10x, fair recall masked to gallery-known cast) that superseded an earlier scene-union metric. dump_error_frames.py / dump_scene_montage.py extract annotated video frames (bounding boxes, TPI/FPI/FN captions, onscreen-vs-offscreen split) for visual review of a replay against ground truth. Gallery utilities: cast_restrict.py, gallery_membership.py, fetch_missing_actors.py, reembed_gallery.py. scripts/validation/: X-Ray ground-truth loading and provider-agnostic identity matching (identity.py's keys_for — an actor is the union of every id we can derive, since pipeline output and ground truth don't share one id space). scripts/artifacts/: push/pull scripts for the Gitea generic package registry — galleries, montage frames, and experiment data (manifests/trajectories/results) are pushed there instead of committed, since none are needed to run the app, only benchmarks. Versioned by git short-SHA. scripts/docs/: MkDocs site build (build_site.sh) and the calibration-curve comparison chart (calibration_chart.py, matplotlib, reads each gallery's embedded calibration). Gallery-building scripts (make_jellyfin_gallery.py, make_gallery.py, filter_gallery.py, run_from_jellyfin.py, movienet_eval.py, movienet_prep.py, sae_gallery.py) updated to read/write HDF5 galleries exclusively, matching the engine-side format switch. run_from_jellyfin.py and the optimizer no longer carry movie source paths in shared manifests (some source filenames include scene-release tags) — resolved locally via a gitignored file-lut.json instead.
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
"""make_jellyfin_gallery.py — build a gallery.json spanning an entire Jellyfin library.
|
||||
"""make_jellyfin_gallery.py — build a gallery.h5 spanning an entire Jellyfin library.
|
||||
|
||||
Queries the Jellyfin API for every Movie/Series, collects the unique cast
|
||||
across the whole library, downloads each actor's headshot directly from
|
||||
Jellyfin (no TMDB key needed), embeds them with the sae_embed module (SCRFD +
|
||||
ArcFace, loaded once), and writes one global gallery.json.
|
||||
ArcFace, loaded once), and writes one global gallery.h5.
|
||||
|
||||
Because identity_matcher scores every detected face against the whole
|
||||
gallery, scene_analyze can then recognise any actor in your library in any
|
||||
@@ -20,21 +20,21 @@ Usage:
|
||||
python scripts/make_jellyfin_gallery.py \\
|
||||
--jellyfin-url http://jellyfin.local:8096 \\
|
||||
--api-key YOUR_API_KEY \\
|
||||
--output gallery.json
|
||||
--output gallery.h5
|
||||
|
||||
# Re-run later to pick up newly added titles without re-embedding
|
||||
# actors already in the gallery:
|
||||
python scripts/make_jellyfin_gallery.py \\
|
||||
--jellyfin-url http://jellyfin.local:8096 \\
|
||||
--api-key YOUR_API_KEY \\
|
||||
--output gallery.json --merge
|
||||
--output gallery.h5 --merge
|
||||
|
||||
# Fall back to TMDB profile images for actors with no usable Jellyfin image:
|
||||
python scripts/make_jellyfin_gallery.py \\
|
||||
--jellyfin-url http://jellyfin.local:8096 \\
|
||||
--api-key YOUR_API_KEY \\
|
||||
--tmdb-key YOUR_TMDB_KEY \\
|
||||
--output gallery.json
|
||||
--output gallery.h5
|
||||
|
||||
Get a Jellyfin API key from Dashboard → Advanced → API Keys.
|
||||
Get a free TMDB API key at: https://www.themoviedb.org/settings/api
|
||||
@@ -52,7 +52,8 @@ import requests
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import sae_env # noqa: F401 — loads .env into os.environ on import
|
||||
from sae_embed_loader import load_embedder
|
||||
from sae_gallery import download_image, download_images, save_gallery, wikidata_image_urls
|
||||
from sae_gallery import (download_image, download_images, load_gallery_hdf5,
|
||||
save_gallery, wikidata_image_urls)
|
||||
from sae_jellyfin import actor_jellyfin_id, jf_get, normalize_jellyfin_url
|
||||
from sae_tmdb import (
|
||||
tmdb_person_by_name,
|
||||
@@ -323,7 +324,7 @@ def build_gallery(base_url: str, api_key: str, embedder, item_types: list[str],
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Build a gallery.json spanning an entire Jellyfin library",
|
||||
description="Build a gallery.h5 spanning an entire Jellyfin library",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
parser.add_argument("--jellyfin-url", default=os.environ.get("JELLYFIN_URL"),
|
||||
@@ -333,7 +334,7 @@ def main():
|
||||
parser.add_argument("--api-key", default=os.environ.get("JELLYFIN_API_KEY"),
|
||||
required=not os.environ.get("JELLYFIN_API_KEY"),
|
||||
help="Jellyfin API key (Dashboard → Advanced → API Keys). Env: JELLYFIN_API_KEY")
|
||||
parser.add_argument("--output", required=True, help="Output gallery.json path")
|
||||
parser.add_argument("--output", required=True, help="Output gallery.h5 path")
|
||||
parser.add_argument("--item-types", default="Movie,Series",
|
||||
help="Comma-separated Jellyfin item types to scan (default: Movie,Series)")
|
||||
parser.add_argument("--build-dir", default="build",
|
||||
@@ -375,7 +376,7 @@ def main():
|
||||
|
||||
existing_actors = {}
|
||||
if args.merge and output.is_file():
|
||||
existing = json.loads(output.read_text())
|
||||
existing = load_gallery_hdf5(output)
|
||||
for actor in existing.get("actors", []):
|
||||
pid = actor_jellyfin_id(actor)
|
||||
if pid:
|
||||
|
||||
Reference in New Issue
Block a user