feat(plex): S5b persist rich detail + serve item/show detail from DB
Persist the info-page rich fields that were previously read LIVE from plex.metadata() on every view —
imdb_rating (IMDb-specific, vs `rating`=audienceRating), imdb_id, tagline, and full_cast
([{name,role,thumb}]) — onto plex_items/plex_shows, and serve the detail endpoints from the row.
- migration 0065: ADD imdb_rating/imdb_id/tagline/full_cast (nullable → no rewrite/lock, unlike 0064);
NULL people_enriched_at on movie/show rows so the existing cast backfill re-fetches each once and
fills the new columns.
- new app/plex/detail.py: rich_detail(meta) (moved out of browse._rich_meta so sync + serve share one
parser) + apply_rich_detail(row, meta); wired into _enrich_people_batch and the info-page lazy fill.
- item_detail: movies serve cast/IMDb/tagline/genres from the row and keep plex.metadata(markers=True)
ONLY for markers + audio/subtitle streams (so detail survives a live-Plex outage); episodes (never
enriched) still parse detail from that live meta.
- show_detail: serves cast/IMDb from the row; `related` stays live best-effort and also lazily fills a
not-yet-enriched show.
- tests: rich_detail/apply_rich_detail parsing + persistence, backfill stamping, item_detail
outage-resilience + lazy fill + episode-live, show_detail row serving. Full suite 285 pass / 2 skip;
migration verified reversible (up→down→up).
Precondition for S6's pure-DB language-aware serving.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
"""plex rich-detail item/show columns (E5 S5b)
|
||||
|
||||
On plex_items and plex_shows: the fields that were previously read LIVE from plex.metadata() on every
|
||||
info-page view — `imdb_rating` (the IMDb-specific score, distinct from `rating` = audienceRating),
|
||||
`imdb_id` (tt…), `tagline`, and `full_cast` (ordered [{name, role, thumb}]). Persisting them lets the
|
||||
detail endpoints serve from the DB row instead of a live Plex call (outage-resilient, and the
|
||||
precondition for S6's pure-DB language-aware serving).
|
||||
|
||||
These are filled by the same rich backfill that already fills the full cast (`_enrich_people_batch`),
|
||||
so the data step below resets `people_enriched_at` to NULL on movie/show rows: the backfill re-fetches
|
||||
each once (60/run, self-throttled) and stamps the new columns. ADD-only, all nullable → no table
|
||||
rewrite, no generated column, no ACCESS EXCLUSIVE lock (unlike 0064).
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "0065_plex_detail_fields"
|
||||
down_revision: Union[str, None] = "0064_plex_hu_overlay_cols"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
_TABLES = ("plex_items", "plex_shows")
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
for t in _TABLES:
|
||||
op.add_column(t, sa.Column("imdb_rating", sa.Float(), nullable=True))
|
||||
op.add_column(t, sa.Column("imdb_id", sa.String(16), nullable=True))
|
||||
op.add_column(t, sa.Column("tagline", sa.Text(), nullable=True))
|
||||
op.add_column(t, sa.Column("full_cast", postgresql.JSONB(), nullable=True))
|
||||
# Force a one-time re-enrich so already-enriched rows gain the new columns (episodes are never
|
||||
# enriched, so their NULL people_enriched_at is unaffected; they keep serving detail live).
|
||||
op.execute("UPDATE plex_items SET people_enriched_at = NULL WHERE kind = 'movie'")
|
||||
op.execute("UPDATE plex_shows SET people_enriched_at = NULL")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
for t in _TABLES:
|
||||
op.drop_column(t, "full_cast")
|
||||
op.drop_column(t, "tagline")
|
||||
op.drop_column(t, "imdb_id")
|
||||
op.drop_column(t, "imdb_rating")
|
||||
Reference in New Issue
Block a user