fix(plex): the recs rail's anchor and a held recommendation are named in the reader's language (E30 S1b)
Found in the S1b UAT: 'Mert megnézted: Batman Forever' kept the English title, and the info page's TMDB recommendations kept TMDB's English titles even for titles we hold. A held recommendation is now named as the library names it, in the reader's language; one we lack keeps TMDB's title.
This commit is contained in:
@@ -21,6 +21,7 @@ from sqlalchemy.orm import Session
|
|||||||
from app import logs, sysconfig
|
from app import logs, sysconfig
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.models import PlexItem, PlexPerson, PlexShow
|
from app.models import PlexItem, PlexPerson, PlexShow
|
||||||
|
from app.plex.cards import display_title
|
||||||
|
|
||||||
log = logs.get_logger(__name__)
|
log = logs.get_logger(__name__)
|
||||||
# Politeness throttle: TMDB tolerates ~50 req/s; ~33/s leaves comfortable headroom for a shared host.
|
# Politeness throttle: TMDB tolerates ~50 req/s; ~33/s leaves comfortable headroom for a shared host.
|
||||||
@@ -406,10 +407,11 @@ def resolve_library_guids(
|
|||||||
return movies, shows
|
return movies, shows
|
||||||
|
|
||||||
|
|
||||||
def resolve_recs(db: Session, recs: list[dict] | None) -> list[dict]:
|
def resolve_recs(db: Session, recs: list[dict] | None, *, lang: str) -> list[dict]:
|
||||||
"""Tag each recommendation with whether it's in OUR library (matched by guid `tmdb:<id>`, bucketed
|
"""Tag each recommendation with whether it's in OUR library (matched by guid `tmdb:<id>`, bucketed
|
||||||
by media_type — see resolve_library_guids) and, if so, its rating_key — so the client can deep-link
|
by media_type — see resolve_library_guids) and, if so, its rating_key — so the client can deep-link
|
||||||
the ones we have and merely display the rest."""
|
the ones we have and merely display the rest. One we have is named as the library names it, in
|
||||||
|
`lang` (E30); one we lack keeps the title TMDB gave."""
|
||||||
if not recs:
|
if not recs:
|
||||||
return []
|
return []
|
||||||
movies, shows = resolve_library_guids(db, recs)
|
movies, shows = resolve_library_guids(db, recs)
|
||||||
@@ -417,13 +419,19 @@ def resolve_recs(db: Session, recs: list[dict] | None) -> list[dict]:
|
|||||||
for r in recs:
|
for r in recs:
|
||||||
g = f"tmdb:{r.get('tmdb_id')}"
|
g = f"tmdb:{r.get('tmdb_id')}"
|
||||||
row = shows.get(g) if r.get("media_type") == "tv" else movies.get(g)
|
row = shows.get(g) if r.get("media_type") == "tv" else movies.get(g)
|
||||||
|
named = {"title": display_title(row, lang)} if row is not None else {}
|
||||||
out.append(
|
out.append(
|
||||||
{**r, "in_library": row is not None, "rating_key": row.rating_key if row else None}
|
{
|
||||||
|
**r,
|
||||||
|
**named,
|
||||||
|
"in_library": row is not None,
|
||||||
|
"rating_key": row.rating_key if row else None,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def serve_block(db: Session, row) -> dict | None:
|
def serve_block(db: Session, row, *, lang: str) -> dict | None:
|
||||||
"""The TMDB extras for a movie/show detail response, served from the row (no live TMDB call).
|
"""The TMDB extras for a movie/show detail response, served from the row (no live TMDB call).
|
||||||
None until the row has been TMDB-enriched (the client then shows nothing extra)."""
|
None until the row has been TMDB-enriched (the client then shows nothing extra)."""
|
||||||
if row.tmdb_enriched_at is None:
|
if row.tmdb_enriched_at is None:
|
||||||
@@ -438,7 +446,7 @@ def serve_block(db: Session, row) -> dict | None:
|
|||||||
"crew": row.crew or [],
|
"crew": row.crew or [],
|
||||||
"studios": row.studios or [],
|
"studios": row.studios or [],
|
||||||
"production_countries": row.production_countries or [],
|
"production_countries": row.production_countries or [],
|
||||||
"recommendations": resolve_recs(db, row.recommendations),
|
"recommendations": resolve_recs(db, row.recommendations, lang=lang),
|
||||||
"backdrops": row.backdrops or [],
|
"backdrops": row.backdrops or [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ def show_detail(
|
|||||||
"episode_count": show_roll["episode_count"],
|
"episode_count": show_roll["episode_count"],
|
||||||
"collection_keys": sh.collection_keys or [],
|
"collection_keys": sh.collection_keys or [],
|
||||||
# TMDB enrichment (E5 S2), served from the row.
|
# TMDB enrichment (E5 S2), served from the row.
|
||||||
"tmdb": plex_tmdb.serve_block(db, sh),
|
"tmdb": plex_tmdb.serve_block(db, sh, lang=lang),
|
||||||
},
|
},
|
||||||
"seasons": [_season_block(se, by_season.get(se.id, [])) for se in seasons],
|
"seasons": [_season_block(se, by_season.get(se.id, [])) for se in seasons],
|
||||||
"related": related,
|
"related": related,
|
||||||
@@ -517,5 +517,5 @@ def item_detail(
|
|||||||
"next_id": next_id,
|
"next_id": next_id,
|
||||||
"collections": _collection_strips(db, it, user.id, lang),
|
"collections": _collection_strips(db, it, user.id, lang),
|
||||||
# TMDB enrichment (E5 S2), served from the row — movies only (episodes aren't enriched).
|
# TMDB enrichment (E5 S2), served from the row — movies only (episodes aren't enriched).
|
||||||
"tmdb": plex_tmdb.serve_block(db, it) if it.kind == "movie" else None,
|
"tmdb": plex_tmdb.serve_block(db, it, lang=lang) if it.kind == "movie" else None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ def _because_you_watched(
|
|||||||
)
|
)
|
||||||
if anchor is None:
|
if anchor is None:
|
||||||
return None
|
return None
|
||||||
resolved = resolve_recs(db, anchor.recommendations)
|
resolved = resolve_recs(db, anchor.recommendations, lang=lang)
|
||||||
# Preserve TMDB's recommendation order, keep only in-library hits, and drop the anchor itself.
|
# Preserve TMDB's recommendation order, keep only in-library hits, and drop the anchor itself.
|
||||||
rks = [
|
rks = [
|
||||||
r["rating_key"]
|
r["rating_key"]
|
||||||
@@ -362,7 +362,10 @@ def _because_you_watched(
|
|||||||
items.append(scards[rk])
|
items.append(scards[rk])
|
||||||
if not items:
|
if not items:
|
||||||
return None
|
return None
|
||||||
return {"anchor": {"id": anchor.rating_key, "title": anchor.title}, "items": items}
|
return {
|
||||||
|
"anchor": {"id": anchor.rating_key, "title": display_title(anchor, lang)},
|
||||||
|
"items": items,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/discover")
|
@router.get("/discover")
|
||||||
|
|||||||
@@ -230,6 +230,25 @@ def _seed_rails(db):
|
|||||||
PlexPlaylistItem(playlist_id=pl.id, item_id=ep.id, position=1),
|
PlexPlaylistItem(playlist_id=pl.id, item_id=ep.id, position=1),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
# TMDB extras: m2 recommends m1 (held) and a title we lack; m1, watched, anchors the recs rail.
|
||||||
|
m1.guid = "tmdb:1"
|
||||||
|
m1.tmdb_enriched_at = datetime(2024, 1, 1, tzinfo=timezone.utc)
|
||||||
|
m1.recommendations = [{"tmdb_id": 2, "title": "EN Only", "media_type": "movie"}]
|
||||||
|
m2 = db.query(PlexItem).filter_by(rating_key="m2").one()
|
||||||
|
m2.guid = "tmdb:2"
|
||||||
|
m2.tmdb_enriched_at = datetime(2024, 1, 1, tzinfo=timezone.utc)
|
||||||
|
m2.recommendations = [
|
||||||
|
{"tmdb_id": 1, "title": "English Title", "media_type": "movie"},
|
||||||
|
{"tmdb_id": 77, "title": "Elsewhere", "media_type": "movie"},
|
||||||
|
]
|
||||||
|
db.add(
|
||||||
|
PlexState(
|
||||||
|
user_id=user.id,
|
||||||
|
item_id=m1.id,
|
||||||
|
status="watched",
|
||||||
|
watched_at=datetime(2024, 3, 1, tzinfo=timezone.utc),
|
||||||
|
)
|
||||||
|
)
|
||||||
sysconfig.set_value(db, "plex_enabled", True)
|
sysconfig.set_value(db, "plex_enabled", True)
|
||||||
db.commit()
|
db.commit()
|
||||||
return user, pl.id
|
return user, pl.id
|
||||||
@@ -259,6 +278,14 @@ def test_every_card_rail_serves_the_readers_language(db, monkeypatch, override_d
|
|||||||
("Magyar Cím", None),
|
("Magyar Cím", None),
|
||||||
("Kezdet", "Magyar Sorozat"),
|
("Kezdet", "Magyar Sorozat"),
|
||||||
]
|
]
|
||||||
|
# A recommendation we hold is named as the library names it; the rail's anchor too.
|
||||||
|
recs = get("/api/plex/item/m2?lang=hu")["tmdb"]["recommendations"]
|
||||||
|
assert [(r["title"], r["in_library"]) for r in recs] == [
|
||||||
|
("Magyar Cím", True),
|
||||||
|
("Elsewhere", False),
|
||||||
|
]
|
||||||
|
byw = get("/api/plex/discover?scope=both&lang=hu")["because_you_watched"]
|
||||||
|
assert byw["anchor"]["title"] == "Magyar Cím"
|
||||||
home = get("/api/discover?lang=hu")["continue_watching"]
|
home = get("/api/discover?lang=hu")["continue_watching"]
|
||||||
plex = [c for c in home if c["source"] == "plex"]
|
plex = [c for c in home if c["source"] == "plex"]
|
||||||
assert [c["title"] for c in plex] == ["Magyar Sorozat"]
|
assert [c["title"] for c in plex] == ["Magyar Sorozat"]
|
||||||
|
|||||||
@@ -255,13 +255,13 @@ def test_serve_block_none_until_enriched_and_resolves_recs(db):
|
|||||||
db.add_all([sequel, fresh])
|
db.add_all([sequel, fresh])
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
assert tmdb.serve_block(db, fresh) is None # not enriched yet
|
assert tmdb.serve_block(db, fresh, lang="en") is None # not enriched yet
|
||||||
|
|
||||||
tmdb.apply_tmdb_detail(fresh, tmdb.parse_tmdb(MOVIE_PAYLOAD, "movie"))
|
tmdb.apply_tmdb_detail(fresh, tmdb.parse_tmdb(MOVIE_PAYLOAD, "movie"))
|
||||||
fresh.tmdb_enriched_at = datetime.now(timezone.utc)
|
fresh.tmdb_enriched_at = datetime.now(timezone.utc)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
block = tmdb.serve_block(db, fresh)
|
block = tmdb.serve_block(db, fresh, lang="en")
|
||||||
assert block is not None
|
assert block is not None
|
||||||
assert block["budget"] == 6000000
|
assert block["budget"] == 6000000
|
||||||
assert block["keywords"] == ["mafia", "crime family"]
|
assert block["keywords"] == ["mafia", "crime family"]
|
||||||
@@ -409,6 +409,7 @@ def test_resolve_recs_matches_by_media_type_not_bare_id(db):
|
|||||||
{"tmdb_id": 500, "title": "Movie 500", "media_type": "movie"},
|
{"tmdb_id": 500, "title": "Movie 500", "media_type": "movie"},
|
||||||
{"tmdb_id": 900, "title": "Show 900", "media_type": "tv"},
|
{"tmdb_id": 900, "title": "Show 900", "media_type": "tv"},
|
||||||
],
|
],
|
||||||
|
lang="en",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
assert miss[500]["in_library"] is False and miss[500]["rating_key"] is None
|
assert miss[500]["in_library"] is False and miss[500]["rating_key"] is None
|
||||||
@@ -423,6 +424,7 @@ def test_resolve_recs_matches_by_media_type_not_bare_id(db):
|
|||||||
{"tmdb_id": 900, "media_type": "movie"},
|
{"tmdb_id": 900, "media_type": "movie"},
|
||||||
{"tmdb_id": 500, "media_type": "tv"},
|
{"tmdb_id": 500, "media_type": "tv"},
|
||||||
],
|
],
|
||||||
|
lang="en",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
assert hit[900]["in_library"] is True and hit[900]["rating_key"] == "m900"
|
assert hit[900]["in_library"] is True and hit[900]["rating_key"] == "m900"
|
||||||
|
|||||||
Reference in New Issue
Block a user