The HTTP files browse, detail, people, collections and _shared do no database work of their own now. New plex/queries.py (the rows a page shows, a user's watch state through userscope), plex/service.py (the info pages' enrichment, a collection's editable flag, the row kept after a failed follow-up read, the person bio's background fetch; none commits), plex/library.py (the unified browse feed, its search and facets) and plex/collections.py (a collection's source, editability, card, certificate and member order). Every watch-state join in the moved code names its owner through owned_by. Pins 354 -> 178 in 4 files; basedpyright baseline 132 -> 118.
62 lines
3.1 KiB
Python
62 lines
3.1 KiB
Python
"""Parse the "info page" rich metadata out of a full Plex metadata dict, and persist the subset that
|
|
the detail endpoints serve from the DB row.
|
|
|
|
Shared by the live info-page path (`plex.service.enrich_movie` / `enrich_show`) and the background
|
|
rich re-sync (plex.sync) so the two never diverge — the same one-parser rule that keeps `people.py`'s
|
|
cast handling consistent. `rich_detail` is the pure parser; `apply_rich_detail` writes the rich-ONLY
|
|
fields onto a movie/show row (genres/directors/studio/content_rating already land from the cheap
|
|
listing at `_apply_facet_fields`)."""
|
|
|
|
from app.plex import people
|
|
from app.plex.plaintext import plain_text
|
|
|
|
|
|
def rich_detail(meta: dict) -> dict:
|
|
"""Pull the info-page extras out of a full Plex metadata dict: an IMDb score + link, content
|
|
rating, genres, director(s), studio, tagline, and cast (name/role/photo). Best-effort — any
|
|
missing field is just omitted."""
|
|
# Rating: IMDb-ONLY — the explicit `imdb://` entry in the Rating array, or NULL. We deliberately do
|
|
# NOT fall back to audienceRating/rating here: that is a Plex audience score, not an IMDb score, and
|
|
# labelling it "imdb_rating" mislabels it. The audience score is served separately (item/show detail
|
|
# expose the `rating` column) so the UI can show it when there's no IMDb score.
|
|
imdb_rating = None
|
|
for r in meta.get("Rating") or []:
|
|
if str(r.get("image") or "").startswith("imdb://"):
|
|
try:
|
|
imdb_rating = round(float(r.get("value")), 1)
|
|
# silent-ok: no IMDb score, which is the common and legitimate case — the UI has a
|
|
# separate audience score to fall back on and shows nothing if neither exists.
|
|
except (TypeError, ValueError):
|
|
pass
|
|
break
|
|
# IMDb id (tt…) from the Guid list → a clickable link.
|
|
imdb_id = None
|
|
for g in meta.get("Guid") or []:
|
|
gid = str(g.get("id") or "")
|
|
if gid.startswith("imdb://"):
|
|
imdb_id = gid.split("imdb://", 1)[1]
|
|
break
|
|
return {
|
|
"imdb_rating": imdb_rating,
|
|
"imdb_id": imdb_id,
|
|
"imdb_url": f"https://www.imdb.com/title/{imdb_id}/" if imdb_id else None,
|
|
"content_rating": meta.get("contentRating"),
|
|
"genres": [g.get("tag") for g in (meta.get("Genre") or []) if g.get("tag")][:8],
|
|
"directors": [d.get("tag") for d in (meta.get("Director") or []) if d.get("tag")][:4],
|
|
"studio": meta.get("studio"),
|
|
"tagline": plain_text(meta.get("tagline")),
|
|
"cast": people.cast_from_meta(meta),
|
|
}
|
|
|
|
|
|
def apply_rich_detail(row, meta: dict) -> None:
|
|
"""Persist the rich-ONLY detail fields onto a movie/show row so the info page serves them from the
|
|
DB instead of a live Plex call. Call wherever a rich metadata dict is fetched (the cast backfill +
|
|
the info-page lazy enrich). Empty cast persists as SQL NULL (none_as_null), so an EN fallback and
|
|
the `full_cast IS NULL` "not yet enriched" signal stay meaningful."""
|
|
d = rich_detail(meta)
|
|
row.imdb_rating = d["imdb_rating"]
|
|
row.imdb_id = d["imdb_id"]
|
|
row.tagline = d["tagline"]
|
|
row.full_cast = d["cast"] or None
|