E5 S5: enriched Plex detail page (ratings + facts + keywords + crew)

Consumes the TMDB block the S2 engine already persists + serves (movie/show
detail's `tmdb`), turning the movie info page into a Seerr-style enriched detail
(page variant only — the player overlay stays lean):

- Multi-source ratings row: IMDb (links out) · TMDb (with vote count) · Plex
  audience — whichever are present. Only the primary score (IMDb, else audience)
  carries the ratingMin filter, mirroring the backend rating column.
- Facts panel: release · runtime · cert · budget · revenue · studios · countries.
- Keyword chips → tap to filter the library. Backend: a new `keywords` filter on
  LibraryFilters + meta_filter_conds (`keywords @> [kw]`, both models), wired
  through PlexFilters (appendPlexFilters / plexFilterCount / active-chip / clear).
- Full crew (TMDB key-crew) → tap a name to open their person page.

Tests: test_plex_keyword_filter.py (3) + PlexInfo.dom.test.tsx (5, incl. the
overlay-stays-lean guard). New i18n plex.info.facts.* / keywords / crew / audience
(en+hu). Gate: vitest 337, backend lane 396.
This commit is contained in:
2026-08-20 23:50:50 +02:00
parent f9b63745f9
commit f10915c223
8 changed files with 491 additions and 40 deletions
+4
View File
@@ -140,6 +140,8 @@ def meta_filter_conds(model, p: dict, lang: str = "en") -> list:
stds = split_csv(p.get("studios"))
if stds: # OR
conds.append(model.studio.in_(stds))
for kw in split_csv(p.get("keywords")): # AND — TMDB keyword chips from the detail page (E5 S5)
conds.append(model.keywords.contains([kw]))
if p.get("collection"):
conds.append(model.collection_keys.contains([p["collection"]]))
return conds
@@ -171,6 +173,7 @@ class LibraryFilters:
"any" # how multiple actors combine: any (OR, default) | all (AND)
)
studios: str | None = None
keywords: str | None = None # TMDB keyword chips (E5 S5), CSV; language-neutral
collection: str | None = None
def as_dict(self) -> dict:
@@ -188,6 +191,7 @@ class LibraryFilters:
"actors": self.actors,
"actor_mode": self.actor_mode,
"studios": self.studios,
"keywords": self.keywords,
"collection": self.collection,
}