faster calibration curve generation
jellyfin intergration
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""run_from_jellyfin.py — resolve a Jellyfin title to its media file and run scene_analyze.
|
||||
|
||||
Looks up a Movie/Episode in Jellyfin, reads its on-disk Path (Jellyfin and this
|
||||
tool must share the same media mount), filters the gallery down to that
|
||||
title's credited cast (via filter_gallery's logic, fewer look-alike
|
||||
mismatches), and runs scene_analyze against the resolved file.
|
||||
|
||||
Usage:
|
||||
python scripts/run_from_jellyfin.py \\
|
||||
--jellyfin-url http://jellyfin.local:8096 \\
|
||||
--api-key YOUR_API_KEY \\
|
||||
--title "The Matrix" \\
|
||||
--gallery whole_gallery.json \\
|
||||
-- --fps 5 --verbosity 2
|
||||
|
||||
Anything after "--" is passed through unchanged to scene_analyze.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from filter_gallery import jf_get, find_item_id, fetch_cast_person_ids
|
||||
|
||||
|
||||
def fetch_item_path(base_url: str, api_key: str, item_id: str) -> tuple[str, str]:
|
||||
"""Return (Name, Path) for a Jellyfin item."""
|
||||
data = jf_get(base_url, api_key, f"/Items/{item_id}", Fields="Path")
|
||||
path = data.get("Path")
|
||||
if not path:
|
||||
raise ValueError(f"Item {item_id} has no Path (not a single media file?)")
|
||||
return data.get("Name", item_id), path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
parser.add_argument("--jellyfin-url", required=True)
|
||||
parser.add_argument("--api-key", required=True)
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument("--item-id", help="Jellyfin item id of the title")
|
||||
group.add_argument("--title", help="Title to search for (uses first match)")
|
||||
parser.add_argument("--item-types", default="Movie,Episode",
|
||||
help="Item types to search when using --title (default: Movie,Episode)")
|
||||
parser.add_argument("--gallery", required=True,
|
||||
help="Global gallery.json built by make_jellyfin_gallery.py")
|
||||
parser.add_argument("--no-filter", action="store_true",
|
||||
help="Skip per-title cast filtering and pass --gallery through as-is")
|
||||
parser.add_argument("--output", default=None,
|
||||
help="scene_analyze output JSON (default: <title>.json)")
|
||||
parser.add_argument("--bin", default="build/scene_analyze",
|
||||
help="Path to scene_analyze binary (default: build/scene_analyze)")
|
||||
parser.add_argument("--dry-run", action="store_true",
|
||||
help="Resolve and print the scene_analyze command without running it")
|
||||
args, extra = parser.parse_known_args()
|
||||
if extra and extra[0] == "--":
|
||||
extra = extra[1:]
|
||||
|
||||
item_id = args.item_id
|
||||
if item_id is None:
|
||||
item_id = find_item_id(args.jellyfin_url, args.api_key, args.title, args.item_types.split(","))
|
||||
print(f"Resolved title to item id {item_id}", file=sys.stderr)
|
||||
|
||||
name, movie_path = fetch_item_path(args.jellyfin_url, args.api_key, item_id)
|
||||
print(f"Resolved {name!r} -> {movie_path}", file=sys.stderr)
|
||||
if not Path(movie_path).is_file():
|
||||
sys.exit(f"Resolved path does not exist on this filesystem: {movie_path}\n"
|
||||
f"(this tool must share Jellyfin's media mount)")
|
||||
|
||||
output = args.output or f"{name}.json"
|
||||
|
||||
gallery_path = args.gallery
|
||||
filtered_file = None
|
||||
if not args.no_filter:
|
||||
cast_ids = fetch_cast_person_ids(args.jellyfin_url, args.api_key, item_id)
|
||||
gallery = json.loads(Path(args.gallery).read_text())
|
||||
actors = [a for a in gallery.get("actors", [])
|
||||
if (a.get("jellyfin_id") or a.get("jellyfin_person_id")) in cast_ids]
|
||||
print(f"Filtered gallery to {len(actors)}/{len(gallery.get('actors', []))} "
|
||||
f"actor(s) credited in {name!r}", file=sys.stderr)
|
||||
filtered_file = tempfile.NamedTemporaryFile(
|
||||
mode="w", suffix=".json", prefix="sae_gallery_", delete=False)
|
||||
json.dump({"actors": actors}, filtered_file)
|
||||
filtered_file.close()
|
||||
gallery_path = filtered_file.name
|
||||
|
||||
cmd = [args.bin, "--movie", movie_path, "--gallery", gallery_path,
|
||||
"--output", output, *extra]
|
||||
print("Running:", " ".join(cmd), file=sys.stderr)
|
||||
|
||||
if args.dry_run:
|
||||
return
|
||||
|
||||
try:
|
||||
subprocess.run(cmd, check=True)
|
||||
finally:
|
||||
if filtered_file is not None:
|
||||
Path(filtered_file.name).unlink(missing_ok=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user