improved jellyfin support

This commit is contained in:
2026-06-12 20:57:33 +02:00
parent a1d6759abc
commit fc16d4a0e1
8 changed files with 335 additions and 65 deletions
+28 -13
View File
@@ -261,7 +261,7 @@ def fetch_actor_images(base_url: str, api_key: str, pid: str, info: dict,
def embed_actor(pid: str, info: dict, image_paths: list[Path],
imdb_id: str | None, tmdb_id: str | None, embedder, fetch_imdb_ids: bool,
embed_executor: concurrent.futures.ThreadPoolExecutor) -> dict | None:
embed_executor: concurrent.futures.ThreadPoolExecutor) -> tuple[dict | None, str | None]:
"""GPU-bound: run sae_embed, always on embed_executor's single dedicated thread.
onnxruntime's CUDA EP / cudnn_frontend execution plans are not safe to run
@@ -274,7 +274,7 @@ def embed_actor(pid: str, info: dict, image_paths: list[Path],
name = info["name"]
if not image_paths:
print(f" {name}: no image available, skipping", file=sys.stderr)
return None
return None, "no image available"
embeddings = []
source_images = []
@@ -289,7 +289,7 @@ def embed_actor(pid: str, info: dict, image_paths: list[Path],
if not embeddings:
print(f" {name}: no valid embeddings, skipping actor", file=sys.stderr)
return None
return None, "no valid embeddings"
print(f" {name}: → {len(embeddings)} embedding(s) stored", file=sys.stderr)
return {
@@ -300,13 +300,13 @@ def embed_actor(pid: str, info: dict, image_paths: list[Path],
"source_images": source_images,
"embeddings": embeddings,
"appearances": info["appearances"],
}
}, None
def process_actor(pid: str, info: dict, base_url: str, api_key: str,
embedder, images_per_actor: int, image_root: Path,
fetch_imdb_ids: bool, tmdb_key: str | None,
embed_executor: concurrent.futures.ThreadPoolExecutor) -> dict | None:
embed_executor: concurrent.futures.ThreadPoolExecutor) -> tuple[dict | None, str | None]:
safe_name = info["name"].replace(" ", "_")
actor_dir = image_root / f"{pid}_{safe_name}"
image_paths, imdb_id, tmdb_id = fetch_actor_images(
@@ -319,44 +319,54 @@ def process_actor(pid: str, info: dict, base_url: str, api_key: str,
def build_gallery(base_url: str, api_key: str, embedder, item_types: list[str],
images_per_actor: int, image_root: Path,
fetch_imdb_ids: bool, existing_actors: dict,
tmdb_key: str | None = None, workers: int = 8) -> dict:
tmdb_key: str | None = None, workers: int = 8) -> tuple[dict, list[dict]]:
actors = collect_actors(base_url, api_key, item_types)
gallery_actors = []
todo = []
n_retry = 0
for pid, info in actors.items():
if pid in existing_actors:
gallery_actors.append(existing_actors[pid])
existing = existing_actors.get(pid)
if existing is not None and existing.get("embeddings"):
gallery_actors.append(existing)
else:
if existing is not None:
n_retry += 1
todo.append((pid, info))
if len(gallery_actors):
print(f"Skipping {len(gallery_actors)} actor(s) already present in existing gallery", file=sys.stderr)
if n_retry:
print(f"Retrying {n_retry} actor(s) with no embeddings in existing gallery", file=sys.stderr)
print(f"Processing {len(todo)} new actor(s) with {workers} worker(s)…", file=sys.stderr)
missing = []
n_done = 0
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor, \
concurrent.futures.ThreadPoolExecutor(max_workers=1, thread_name_prefix="embed") as embed_executor:
futures = {
executor.submit(process_actor, pid, info, base_url, api_key, embedder,
images_per_actor, image_root,
fetch_imdb_ids, tmdb_key, embed_executor): info["name"]
fetch_imdb_ids, tmdb_key, embed_executor): (pid, info["name"])
for pid, info in todo
}
for future in concurrent.futures.as_completed(futures):
n_done += 1
name = futures[future]
pid, name = futures[future]
try:
actor = future.result()
actor, reason = future.result()
except Exception as e:
print(f" [error] {name}: {e}", file=sys.stderr)
missing.append({"jellyfin_id": pid, "name": name, "reason": str(e)})
continue
if actor:
gallery_actors.append(actor)
else:
missing.append({"jellyfin_id": pid, "name": name, "reason": reason})
if n_done % 25 == 0:
print(f" progress: {n_done}/{len(todo)} actors processed", file=sys.stderr)
return {"actors": gallery_actors}
return {"actors": gallery_actors}, missing
# ── Entry point ───────────────────────────────────────────────────────────────
@@ -423,7 +433,7 @@ def main():
existing_actors[pid] = actor
print(f"Loaded {len(existing_actors)} actor(s) from existing gallery for merge", file=sys.stderr)
gallery = build_gallery(
gallery, missing = build_gallery(
base_url=jellyfin_url,
api_key=args.api_key,
embedder=embedder,
@@ -447,6 +457,11 @@ def main():
output.write_text(json.dumps(gallery, indent=2) + "\n")
print(f"Saved: {output}", file=sys.stderr)
if missing:
missing_path = output.with_name(output.stem + ".missing_images.json")
missing_path.write_text(json.dumps(missing, indent=2) + "\n")
print(f"{len(missing)} actor(s) need images — see {missing_path}", file=sys.stderr)
if __name__ == "__main__":
main()