fix(plex): re-probe on file replace, HU parity, public class const, playback-class test gaps

Deep-review round 1 on feature/plex-playback-class-filter:

- Sticky watermark (medium): _apply_item reset the bit-depth probe watermark
  only on a codec-string change, so a same-codec re-encode (8-bit->10-bit HEVC)
  kept a stale video_bit_depth/transcode_hw forever (the backfill only re-probes
  media_probed_at IS NULL rows). Also reset when the Plex Part key changes (the
  file was replaced), not just the codec.

- i18n parity (medium): the HU playback-class labels were mixed-language (only
  'direct' translated). Translated remux/transcode_hw/transcode_sw and the
  tooltips consistently to Hungarian (Kozvetlen streameles / Atkodolas).

- Layering (low): _PLAYBACK_CLASSES was module-private yet imported across into
  routes.plex.browse; renamed to public PLAYBACK_CLASSES (it is the shared
  source of truth for facet order + zero-fill).

- a11y (low): the browse-card playback-class dot conveyed meaning only via a
  mouse-hover title; added role=img + aria-label so SR/touch users get the class.

- tests: the NULL/unprobed transcode_hw -> SW fold (filter + facet), facet
  self-exclusion under an active playable filter, the migration codec-set ==
  HW_DECODABLE_CODECS drift guard, the probe-batch bound + oldest-first order,
  the transient probe-failure retry path, and the same-codec re-encode re-probe.

Backend gate green (ruff + full pytest); FE gate green (tsc/lint/knip/prettier/
vitest). NOTE: the facet-vs-runtime disagreement for 10-bit HEVC (C1) is the
known merge-order constraint — this branch's runtime is codec-only; the facet
becomes truthful once BUG1/tonemap's bit-depth-aware routing lands with it. Left
as a sequencing constraint, not code-changed here.
This commit is contained in:
2026-08-30 17:51:33 +02:00
parent 840526b1b8
commit bf98b48517
6 changed files with 205 additions and 18 deletions
+11 -4
View File
@@ -471,13 +471,20 @@ def _apply_item(row: PlexItem, lib_id: int, kind: str, meta: dict) -> None:
_apply_facet_fields(row, meta) # filterable/orderable extras (shared with shows)
mf = _media_facts(meta)
new_vcodec = mf.get("codec_video")
# A changed video codec means the physical file was replaced — any probed bit-depth is stale, so
# reset the probe watermark and re-derive transcode_hw from codec alone until the backfill re-probes.
if (row.codec_video or "").lower() != (new_vcodec or "").lower():
new_part_key = mf.get("part_key")
# The physical file was replaced when its Plex Part key changes (a re-encode / quality upgrade gets a
# new part id) OR — a coarser signal — when the video codec changes. Either way any probed bit-depth is
# stale, so reset the probe watermark and re-derive transcode_hw from codec alone until the backfill
# re-probes. Codec alone is NOT enough: a same-codec bit-depth re-encode (8-bit→10-bit HEVC) keeps the
# codec `hevc` but flips the HW class, and only the part-key move catches it. A spurious reset (a rare
# part-id reassignment on an unchanged file) costs at most one extra bounded re-probe — safe direction.
if (row.codec_video or "").lower() != (
new_vcodec or ""
).lower() or row.part_key != new_part_key:
row.video_bit_depth = None
row.media_probed_at = None
row.file_path = mf.get("file_path")
row.part_key = mf.get("part_key")
row.part_key = new_part_key
row.container = mf.get("container")
row.codec_video = new_vcodec
row.codec_audio = mf.get("codec_audio")