feat(plex): expanded metadata filters, clickable info page, image disk cache

Backend (migration 0045_plex_filter_meta): plex_items gains rating (audienceRating
~IMDb), content_rating, studio, originally_available_at, and GIN-indexed genres /
directors / cast_names — all mirrored from the cheap section listing (no per-item API
calls; they also seed a future watch-habit recommender). /browse gains genre / content-
rating / year / rating / duration / added-within / director / actor / studio filters
(@> containment, GIN) + sort by year|rating|duration|release; new /facets endpoint
returns available genres+ratings (with counts) and the year/rating/duration bounds. A
thin on-disk image cache (.plex-img-cache) serves posters/art/cast photos from local
disk after first fetch (~7-14x faster repeat loads).

Frontend: PlexSidebar grows the full filter set (facet-driven genre/age chips, rating
steps, year range inputs, duration buckets, added-within, active people/studio chips,
clear-all); filters persist per-account as one JSON blob. PlexInfo metadata (year,
genre, director, cast, studio, IMDb score) is clickable → sets the matching filter and
returns to the filtered grid (page variant only; the in-player overlay stays read-only
so a stray click can't stop playback). i18n en/hu/de.
This commit is contained in:
npeter83 2026-07-05 23:45:55 +02:00
parent 690e17611c
commit eefd7e3abd
15 changed files with 842 additions and 122 deletions

View file

@ -5,7 +5,11 @@
"searchPlaceholder": "Plex durchsuchen…",
"sort": {
"added": "Kürzlich hinzugefügt",
"title": "Titel"
"title": "Titel",
"year": "Jahr",
"rating": "IMDb-Wertung",
"duration": "Länge",
"release": "Erscheinungsdatum"
},
"filter": {
"library": "Bibliothek",
@ -16,7 +20,18 @@
"in_progress": "Läuft",
"watched": "Gesehen"
},
"sort": "Sortierung"
"sort": "Sortierung",
"clearAll": "Filter zurücksetzen",
"active": "Aktiv",
"any": "Alle",
"rating": "IMDb-Wertung",
"genre": "Genre",
"year": "Jahr",
"duration": "Länge",
"durationOpt": { "short": "Unter 90 Min.", "mid": "90120 Min.", "long": "Über 2 Std." },
"added": "Zu Plex hinzugefügt",
"addedOpt": { "24h": "24 Std.", "1w": "1 Woche", "1m": "1 Monat", "6m": "6 Monate", "1y": "1 Jahr" },
"contentRating": "Altersfreigabe"
},
"count": "{{count}} Titel",
"searchCount": "{{count}} Treffer",
@ -76,7 +91,10 @@
"cast": "Besetzung & Crew",
"markWatched": "Als gesehen markieren",
"markUnwatched": "Gesehen zurücknehmen",
"clearResume": "Fortsetzungsposition löschen"
"clearResume": "Fortsetzungsposition löschen",
"filterYear": "Titel aus {{year}}",
"filterRating": "Titel mit {{n}}+",
"filterActor": "Titel mit {{name}}"
},
"playable": {
"direct": "Spielt direkt im Browser",

View file

@ -5,7 +5,11 @@
"searchPlaceholder": "Search Plex…",
"sort": {
"added": "Recently added",
"title": "Title"
"title": "Title",
"year": "Year",
"rating": "IMDb rating",
"duration": "Length",
"release": "Release date"
},
"filter": {
"library": "Library",
@ -16,7 +20,18 @@
"in_progress": "In progress",
"watched": "Watched"
},
"sort": "Sort"
"sort": "Sort",
"clearAll": "Clear filters",
"active": "Active",
"any": "Any",
"rating": "IMDb rating",
"genre": "Genre",
"year": "Year",
"duration": "Length",
"durationOpt": { "short": "Under 90m", "mid": "90120m", "long": "Over 2h" },
"added": "Added to Plex",
"addedOpt": { "24h": "24h", "1w": "1 week", "1m": "1 month", "6m": "6 months", "1y": "1 year" },
"contentRating": "Age rating"
},
"count": "{{count}} titles",
"searchCount": "{{count}} matches",
@ -76,7 +91,10 @@
"cast": "Cast & crew",
"markWatched": "Mark watched",
"markUnwatched": "Mark unwatched",
"clearResume": "Clear resume position"
"clearResume": "Clear resume position",
"filterYear": "Show titles from {{year}}",
"filterRating": "Show titles rated {{n}}+",
"filterActor": "Show titles with {{name}}"
},
"playable": {
"direct": "Plays directly in the browser",

View file

@ -5,7 +5,11 @@
"searchPlaceholder": "Keresés a Plexben…",
"sort": {
"added": "Nemrég hozzáadott",
"title": "Cím"
"title": "Cím",
"year": "Év",
"rating": "IMDb pont",
"duration": "Hossz",
"release": "Megjelenés dátuma"
},
"filter": {
"library": "Könyvtár",
@ -16,7 +20,18 @@
"in_progress": "Folyamatban",
"watched": "Megnézett"
},
"sort": "Rendezés"
"sort": "Rendezés",
"clearAll": "Szűrők törlése",
"active": "Aktív",
"any": "Bármi",
"rating": "IMDb pont",
"genre": "Műfaj",
"year": "Év",
"duration": "Hossz",
"durationOpt": { "short": "90 perc alatt", "mid": "90120 perc", "long": "2 óra felett" },
"added": "Plexhez adva",
"addedOpt": { "24h": "24 óra", "1w": "1 hét", "1m": "1 hónap", "6m": "6 hónap", "1y": "1 év" },
"contentRating": "Korhatár"
},
"count": "{{count}} cím",
"searchCount": "{{count}} találat",
@ -76,7 +91,10 @@
"cast": "Szereplők & stáb",
"markWatched": "Megjelölés megnézettként",
"markUnwatched": "Megnézett visszavonása",
"clearResume": "Folytatási pozíció törlése"
"clearResume": "Folytatási pozíció törlése",
"filterYear": "{{year}} évi címek",
"filterRating": "{{n}}+ pontos címek",
"filterActor": "Címek {{name}} szereplésével"
},
"playable": {
"direct": "Közvetlenül játszható a böngészőben",