Improve library identification and track inspection

This commit is contained in:
scoped
2026-05-15 17:04:26 +00:00
parent 1ffb68e74c
commit 79308a84b9
6 changed files with 530 additions and 51 deletions

View File

@@ -2,6 +2,8 @@ from __future__ import annotations
import json
import os
import threading
import time
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlparse
@@ -9,10 +11,10 @@ from urllib.parse import parse_qs, unquote
from .config import load_config, public_config
from .downloads import downloads_snapshot
from .library import library_snapshot, normalize_library
from .library import enrich_library_metadata, library_snapshot, normalize_library
from .logging_setup import configure_logging
from .media_probe import edit_track, media_probe
from .metadata import test_tmdb
from .metadata import movie_metadata_by_id, search_metadata, series_metadata_by_id, test_tmdb
from .organizer import execute_bundle_plan
from .releases import fetch_releases
from .scanner import Scanner
@@ -155,6 +157,73 @@ configure_logging(CONFIG["paths"]["logs"], CONFIG["app"].get("log_level", "INFO"
STORE = JsonStore(CONFIG["paths"]["data"])
apply_settings(CONFIG, STORE.snapshot().get("settings", {}))
SCANNER = Scanner(CONFIG, STORE)
METADATA_REFRESH = {"running": False, "started_at": None, "finished_at": None, "error": None}
METADATA_LOCK = threading.Lock()
def start_metadata_refresh() -> bool:
with METADATA_LOCK:
if METADATA_REFRESH["running"]:
return False
METADATA_REFRESH.update({"running": True, "started_at": time.time(), "finished_at": None, "error": None})
def worker() -> None:
try:
snap = STORE.snapshot()
library = snap.get("library") or {}
if not library.get("items"):
library = library_snapshot(CONFIG, snap.get("library"))
enriched = enrich_library_metadata(CONFIG, library)
STORE.set_library(enriched)
with METADATA_LOCK:
METADATA_REFRESH.update({"running": False, "finished_at": time.time(), "error": None})
except Exception as exc:
with METADATA_LOCK:
METADATA_REFRESH.update({"running": False, "finished_at": time.time(), "error": str(exc)})
threading.Thread(target=worker, daemon=True).start()
return True
def public_library_payload(library: dict) -> dict:
public = normalize_library(library)
public.pop("items", None)
return public
def find_collection(library: dict, key: str, media_library: str) -> dict | None:
collections = library.get("collections") or {}
group = "series" if media_library == "tv" else "movies"
return next((item for item in collections.get(group, []) if item.get("key") == key), None)
def identify_collection(payload: dict) -> dict:
media_library = "tv" if payload.get("library") == "tv" else "movie"
key = str(payload.get("key") or "")
tmdb_id = int(payload.get("tmdb_id") or 0)
snap = STORE.snapshot()
library = snap.get("library") or {}
collection = find_collection(library, key, media_library)
if not collection:
raise ValueError("library item was not found")
if media_library == "tv":
seasons = {int(season.get("season") or 0) for season in collection.get("seasons", []) if int(season.get("season") or 0) > 0}
metadata = series_metadata_by_id(CONFIG, tmdb_id, seasons)
else:
metadata = movie_metadata_by_id(CONFIG, tmdb_id)
if metadata.get("release_date"):
collection["year"] = int(metadata["release_date"][:4])
collection["metadata"] = metadata
collection["title"] = metadata.get("title") or collection.get("title")
identifications = library.setdefault("identifications", {})
identifications[key] = {
"library": media_library,
"tmdb_id": tmdb_id,
"metadata": metadata,
"updated_at": time.time(),
}
STORE.set_library(library)
return collection
class Handler(BaseHTTPRequestHandler):
@@ -222,9 +291,9 @@ class Handler(BaseHTTPRequestHandler):
"truncated": False,
"cached": False,
}
library = normalize_library(library)
library.pop("items", None)
self.send_json({"library": library})
self.send_json({"library": public_library_payload(library)})
elif path == "/api/library/metadata/status":
self.send_json({"metadata": METADATA_REFRESH})
elif path == "/api/downloads":
self.send_json({"downloads": downloads_snapshot(CONFIG, STORE.snapshot())})
elif path == "/api/releases":
@@ -294,9 +363,25 @@ class Handler(BaseHTTPRequestHandler):
STORE.set_organizer_queue(updated)
self.send_json({"ok": True})
elif path == "/api/library/scan":
library = library_snapshot(CONFIG)
snap = STORE.snapshot()
library = library_snapshot(CONFIG, snap.get("library"))
STORE.set_library(library)
self.send_json({"library": library})
elif path == "/api/library/metadata/refresh":
started = start_metadata_refresh()
self.send_json({"started": started, "metadata": METADATA_REFRESH}, HTTPStatus.ACCEPTED)
elif path == "/api/library/identify/search":
length = int(self.headers.get("Content-Length", "0") or "0")
body = self.rfile.read(length).decode() if length else "{}"
payload = json.loads(body)
results = search_metadata(CONFIG, "tv" if payload.get("library") == "tv" else "movie", str(payload.get("query") or ""), payload.get("year"))
self.send_json({"results": results})
elif path == "/api/library/identify/apply":
length = int(self.headers.get("Content-Length", "0") or "0")
body = self.rfile.read(length).decode() if length else "{}"
payload = json.loads(body)
collection = identify_collection(payload)
self.send_json({"collection": collection})
elif path == "/api/tools/transcoder/run-next":
result = run_next_transcode(CONFIG, STORE.snapshot().get("library"))
STORE.add_event("info", f"transcoder: {result.get('status')}")