improvement(plex): serve right-sized poster/art images via Plex transcode

Posters/art were proxied at full resolution (many 1-7MB, some 9.5MB) into ≤176px
grid/info cells — heavy to fetch + decode, so fast scrolling lagged 1-2s even
with a warm cache (the cache removed the Plex round-trip, not the image weight).

Now PlexClient.image_bytes optionally hits Plex's /photo/:/transcode to resize
server-side; the /image endpoint requests thumb=400x600, art=1280x720 and keys
the cache by width (old full-size files orphaned, re-cached small). Measured:
a poster 5.5MB->50KB, art 8.1MB->137KB, at 400x600 (crisp on 2x DPR). Grid
scroll is near-instant and the disk cache shrinks ~8x.
This commit is contained in:
npeter83 2026-07-06 08:04:13 +02:00
parent 6df43445df
commit 23fc67cb64
4 changed files with 35 additions and 6 deletions

View file

@ -534,6 +534,9 @@ def _image_key(db: Session, rating_key: str, variant: str) -> str | None:
_IMG_CACHE = Path(settings.download_root) / ".plex-img-cache"
_EXT_CT = [("jpg", "image/jpeg"), ("png", "image/png"), ("webp", "image/webp")]
_CT_EXT = {ct: ext for ext, ct in _EXT_CT}
# Target box per variant — Plex resizes to these server-side. Posters fill ≤176px cells (grid/info/
# strips), so 400px covers 2× DPR; the faint backdrop art only needs ~1280px. Keeps images ~tens of KB.
_IMG_SIZES = {"thumb": (400, 600), "art": (1280, 720)}
def _img_cache_read(key: str) -> tuple[bytes, str] | None:
@ -584,7 +587,11 @@ def image(rating_key: str, request: Request, variant: str = "thumb") -> Response
fetch. Auth is the signed-session check (no DB user-load)."""
_require_session(request)
v = "art" if variant == "art" else "thumb"
cache_key = f"item_{rating_key}_{v}"
w, h = _IMG_SIZES[v]
# Size is part of the cache key: Plex resizes server-side (see PlexClient.image_bytes), so we cache
# the small variant. The width tag also means changing a target size just re-caches, never serves a
# stale differently-sized file (old full-size files under the un-tagged key are simply orphaned).
cache_key = f"item_{rating_key}_{v}_{w}"
hit = _img_cache_read(cache_key)
if hit:
return _img_response(*hit)
@ -599,7 +606,7 @@ def image(rating_key: str, request: Request, variant: str = "thumb") -> Response
raise HTTPException(status_code=400, detail=str(e))
try:
with plex:
data, ct = plex.image_bytes(key)
data, ct = plex.image_bytes(key, w, h)
except PlexError as e:
raise HTTPException(status_code=502, detail=f"Plex image fetch failed: {e}")
_img_cache_write(cache_key, data, ct)