A Plex agent can hand a summary over as HTML (<p> paragraphs, á entities); the info page and the hover card showed the tags. One cleaner, app/plex/plaintext.py, now runs on every summary and tagline the sync, the Hungarian overlay and the rich-detail fetch store, and migration 0101 cleans the rows written before through the same function. Paragraph breaks survive as newlines, which the info page and the hover card now keep.
61 lines
3.1 KiB
Python
61 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 (routes.plex.browse) 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
|