improved jellyfin support
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user