mirror of
https://github.com/MrSleeps/jellyfin-scene-search.git
synced 2026-11-14 22:11:27 +00:00
Expose reusable Jellyfin indexing progress
This commit is contained in:
+143
-27
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import shutil
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
@@ -31,6 +32,35 @@ def _library_name(library: dict) -> str:
|
||||
return str(library.get("Name") or library.get("Id") or "(unnamed)")
|
||||
|
||||
|
||||
ProgressCallback = Callable[[dict], None]
|
||||
|
||||
|
||||
def resolve_library_ids(
|
||||
client: JellyfinClient,
|
||||
library_ids: list[str],
|
||||
) -> list[dict]:
|
||||
requested = {str(value).strip() for value in library_ids if str(value).strip()}
|
||||
if not requested:
|
||||
raise RuntimeError("Select at least one Jellyfin library.")
|
||||
|
||||
available = client.media_folders()
|
||||
by_id = {
|
||||
str(library.get("Id") or ""): library
|
||||
for library in available
|
||||
if str(library.get("Id") or "")
|
||||
}
|
||||
missing = sorted(requested.difference(by_id))
|
||||
if missing:
|
||||
raise RuntimeError(
|
||||
"Jellyfin library id(s) not found: " + ", ".join(missing)
|
||||
)
|
||||
|
||||
return sorted(
|
||||
[by_id[library_id] for library_id in requested],
|
||||
key=lambda library: _library_name(library).casefold(),
|
||||
)
|
||||
|
||||
|
||||
def resolve_libraries(
|
||||
client: JellyfinClient,
|
||||
requested: list[str] | None,
|
||||
@@ -197,6 +227,9 @@ def _embed_frames(
|
||||
frame_paths: list[Path],
|
||||
media_id: str,
|
||||
interval_ms: int,
|
||||
*,
|
||||
progress: ProgressCallback | None = None,
|
||||
progress_context: dict | None = None,
|
||||
) -> tuple[list[tuple], int]:
|
||||
rows: list[tuple] = []
|
||||
dim: int | None = None
|
||||
@@ -232,10 +265,17 @@ def _embed_frames(
|
||||
)
|
||||
)
|
||||
|
||||
print(
|
||||
f" embedded "
|
||||
f"{min(start + len(batch_paths), len(frame_paths))}/{len(frame_paths)}"
|
||||
)
|
||||
processed = min(start + len(batch_paths), len(frame_paths))
|
||||
print(f" embedded {processed}/{len(frame_paths)}")
|
||||
if progress is not None:
|
||||
progress(
|
||||
{
|
||||
**(progress_context or {}),
|
||||
"stage": "embedding",
|
||||
"frames_processed": processed,
|
||||
"frames_total": len(frame_paths),
|
||||
}
|
||||
)
|
||||
|
||||
if dim is None:
|
||||
raise RuntimeError("No frames were embedded")
|
||||
@@ -276,6 +316,7 @@ def _index_library(
|
||||
embedder: SiglipEmbedder | None,
|
||||
limit: int | None,
|
||||
force: bool,
|
||||
progress: ProgressCallback | None = None,
|
||||
) -> tuple[SiglipEmbedder | None, int, int, int, int]:
|
||||
library_id = str(library["Id"])
|
||||
library_name = _library_name(library)
|
||||
@@ -291,12 +332,37 @@ def _index_library(
|
||||
failed = 0
|
||||
current_item_ids: set[str] = set()
|
||||
|
||||
if progress is not None:
|
||||
progress(
|
||||
{
|
||||
"stage": "library",
|
||||
"library_id": library_id,
|
||||
"library": library_name,
|
||||
"item_number": 0,
|
||||
"item_total": len(summaries),
|
||||
}
|
||||
)
|
||||
|
||||
for number, summary in enumerate(summaries, 1):
|
||||
try:
|
||||
detail = summary
|
||||
item_id = str(detail.get("Id") or summary["Id"])
|
||||
current_item_ids.add(item_id)
|
||||
title = str(detail.get("Name") or summary.get("Name") or item_id)
|
||||
if progress is not None:
|
||||
progress(
|
||||
{
|
||||
"stage": "item",
|
||||
"library_id": library_id,
|
||||
"library": library_name,
|
||||
"item_id": item_id,
|
||||
"item": title,
|
||||
"item_number": number,
|
||||
"item_total": len(summaries),
|
||||
"frames_processed": 0,
|
||||
"frames_total": 0,
|
||||
}
|
||||
)
|
||||
tags = item_tags(detail)
|
||||
normalized_tags = {tag.casefold() for tag in tags}
|
||||
|
||||
@@ -429,6 +495,15 @@ def _index_library(
|
||||
frame_paths,
|
||||
media_id,
|
||||
interval_ms,
|
||||
progress=progress,
|
||||
progress_context={
|
||||
"library_id": library_id,
|
||||
"library": library_name,
|
||||
"item_id": item_id,
|
||||
"item": title,
|
||||
"item_number": number,
|
||||
"item_total": len(summaries),
|
||||
},
|
||||
)
|
||||
|
||||
size_bytes = int(source.get("Size") or 0)
|
||||
@@ -501,6 +576,58 @@ def _index_library(
|
||||
return embedder, indexed, skipped, failed, pruned
|
||||
|
||||
|
||||
def index_libraries(
|
||||
client: JellyfinClient,
|
||||
libraries: list[dict],
|
||||
*,
|
||||
embedder: SiglipEmbedder | None = None,
|
||||
limit: int | None = None,
|
||||
force: bool = False,
|
||||
progress: ProgressCallback | None = None,
|
||||
) -> tuple[SiglipEmbedder | None, dict]:
|
||||
total_indexed = 0
|
||||
total_skipped = 0
|
||||
total_failed = 0
|
||||
total_pruned = 0
|
||||
|
||||
for library_number, library in enumerate(libraries, 1):
|
||||
if progress is not None:
|
||||
progress(
|
||||
{
|
||||
"stage": "library",
|
||||
"library_id": str(library.get("Id") or ""),
|
||||
"library": _library_name(library),
|
||||
"library_number": library_number,
|
||||
"library_total": len(libraries),
|
||||
}
|
||||
)
|
||||
|
||||
embedder, indexed, skipped, failed, pruned = _index_library(
|
||||
client,
|
||||
library,
|
||||
embedder=embedder,
|
||||
limit=limit,
|
||||
force=force,
|
||||
progress=progress,
|
||||
)
|
||||
total_indexed += indexed
|
||||
total_skipped += skipped
|
||||
total_failed += failed
|
||||
total_pruned += pruned
|
||||
|
||||
summary = {
|
||||
"library_total": len(libraries),
|
||||
"indexed": total_indexed,
|
||||
"skipped": total_skipped,
|
||||
"failed": total_failed,
|
||||
"pruned": total_pruned,
|
||||
}
|
||||
if progress is not None:
|
||||
progress({"stage": "indexed", **summary})
|
||||
|
||||
return embedder, summary
|
||||
|
||||
|
||||
def cmd_jellyfin_index(args: object) -> None:
|
||||
init_db(settings.db_path)
|
||||
client = build_jellyfin_client()
|
||||
@@ -519,30 +646,19 @@ def cmd_jellyfin_index(args: object) -> None:
|
||||
+ ", ".join(_library_name(library) for library in libraries)
|
||||
)
|
||||
|
||||
embedder: SiglipEmbedder | None = None
|
||||
total_indexed = 0
|
||||
total_skipped = 0
|
||||
total_failed = 0
|
||||
total_pruned = 0
|
||||
|
||||
for library in libraries:
|
||||
embedder, indexed, skipped, failed, pruned = _index_library(
|
||||
client,
|
||||
library,
|
||||
embedder=embedder,
|
||||
limit=limit,
|
||||
force=force,
|
||||
)
|
||||
total_indexed += indexed
|
||||
total_skipped += skipped
|
||||
total_failed += failed
|
||||
total_pruned += pruned
|
||||
_, summary = index_libraries(
|
||||
client,
|
||||
libraries,
|
||||
limit=limit,
|
||||
force=force,
|
||||
)
|
||||
|
||||
print(
|
||||
f"\nJellyfin indexing complete across {len(libraries)} librar"
|
||||
f"{'y' if len(libraries) == 1 else 'ies'}: "
|
||||
f"{total_indexed} indexed, {total_skipped} skipped, "
|
||||
f"{total_failed} failed, {total_pruned} pruned."
|
||||
f"\nJellyfin indexing complete across {summary['library_total']} librar"
|
||||
f"{'y' if summary['library_total'] == 1 else 'ies'}: "
|
||||
f"{summary['indexed']} indexed, {summary['skipped']} skipped, "
|
||||
f"{summary['failed']} failed, {summary['pruned']} pruned."
|
||||
)
|
||||
if total_indexed or total_pruned:
|
||||
if summary["indexed"] or summary["pruned"]:
|
||||
print("Next: python -m scene_search.cli caption --sync")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user