fix(plex): gate the part-key re-probe to single-version items; drop dead i18n key; cover cross-facet narrowing
Deep-review round 2 on the round-1 fixes (all low): - part_key churn (state): round 1's part-key reset re-probed EVERY sync for a multi-version item (an original + a Plex 'Optimized' copy), because _media_facts reads media[0]/part[0] whose order Plex doesn't guarantee stable, oscillating transcode_hw between the two versions. Gate the part-key signal on a single-version item (len(Media)<=1); codec-change reset still applies to all. A multi-version same-codec re-encode not self-healing is an acceptable rare edge; a per-sync re-probe loop is not. - dead i18n key: this diff's playableClass() collapses to transcode_hw/sw, so the bare plex.playable.transcode key it orphaned is now unused (knip can't see dynamic keys). Dropped from both locales, parity kept. - test: cross-facet narrowing by an active playable class (a co-facet's year range collapses to the selected subset) — the flip side of self-exclusion, previously unpinned. The aria-label+title double-announcement on the card dot is a deliberate accepted trade-off (reliable SR name + mouse tooltip). Backend + FE gates green.
This commit is contained in:
@@ -472,15 +472,20 @@ def _apply_item(row: PlexItem, lib_id: int, kind: str, meta: dict) -> None:
|
||||
mf = _media_facts(meta)
|
||||
new_vcodec = mf.get("codec_video")
|
||||
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:
|
||||
# The physical file was replaced when its video codec changes OR — for a SINGLE-version item — when its
|
||||
# Plex Part key changes (a re-encode / quality upgrade gets a new part id). 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.
|
||||
#
|
||||
# The part-key check is gated on single-version because `_media_facts` reads media[0]/part[0], and for a
|
||||
# MULTI-version item (an original + a Plex "Optimized" copy) that order isn't guaranteed stable across
|
||||
# listings — an unguarded part-key compare would then reset+re-probe every sync and oscillate the
|
||||
# transcode_hw bucket between the two versions. A multi-version same-codec re-encode not self-healing is
|
||||
# an acceptable rare edge; a per-sync re-probe loop is not.
|
||||
single_version = len(meta.get("Media") or []) <= 1
|
||||
codec_changed = (row.codec_video or "").lower() != (new_vcodec or "").lower()
|
||||
if codec_changed or (single_version and row.part_key != new_part_key):
|
||||
row.video_bit_depth = None
|
||||
row.media_probed_at = None
|
||||
row.file_path = mf.get("file_path")
|
||||
|
||||
@@ -451,3 +451,45 @@ def test_facet_playable_self_excludes_the_active_selection(db, monkeypatch):
|
||||
assert resp.status_code == 200, resp.text
|
||||
counts = {p["value"]: p["count"] for p in resp.json()["playable"]}
|
||||
assert counts == {"direct": 1, "remux": 1, "transcode_hw": 1, "transcode_sw": 1}
|
||||
|
||||
|
||||
def test_active_playable_narrows_the_other_facets(db, monkeypatch):
|
||||
# The flip side of self-exclusion: an active playable class DOES narrow the OTHER facet groups (so
|
||||
# e.g. the year range reflects only the selected class). Seed a Direct movie (2000) and a
|
||||
# software-transcode movie (2020); selecting playable=direct must collapse the year facet to 2000.
|
||||
lib = PlexLibrary(plex_key="1", title="Movies", kind="movie", enabled=True)
|
||||
db.add(lib)
|
||||
db.flush()
|
||||
db.add_all(
|
||||
[
|
||||
PlexItem(
|
||||
rating_key="d2000",
|
||||
library_id=lib.id,
|
||||
kind="movie",
|
||||
title="Direct2000",
|
||||
playable="direct",
|
||||
year=2000,
|
||||
),
|
||||
PlexItem(
|
||||
rating_key="s2020",
|
||||
library_id=lib.id,
|
||||
kind="movie",
|
||||
title="SW2020",
|
||||
playable="transcode",
|
||||
codec_video="mpeg4",
|
||||
transcode_hw=False,
|
||||
year=2020,
|
||||
),
|
||||
]
|
||||
)
|
||||
user = User(email="pnarrow@example.com")
|
||||
db.add(user)
|
||||
db.commit()
|
||||
client = _client(db, user, monkeypatch)
|
||||
try:
|
||||
wide = client.get("/api/plex/facets?scope=movie").json()
|
||||
narrow = client.get("/api/plex/facets?scope=movie&playable=direct").json()
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
assert (wide["year_min"], wide["year_max"]) == (2000, 2020) # both classes span the range
|
||||
assert (narrow["year_min"], narrow["year_max"]) == (2000, 2000) # narrowed to the Direct subset
|
||||
|
||||
@@ -293,7 +293,6 @@
|
||||
"playable": {
|
||||
"direct": "Plays directly in the browser",
|
||||
"remux": "Direct Stream — a light remux (no video re-encode)",
|
||||
"transcode": "Needs transcoding (heavier)",
|
||||
"transcode_hw": "Transcoded on the hardware encoder",
|
||||
"transcode_sw": "Transcoded in software (heaviest)"
|
||||
},
|
||||
|
||||
@@ -293,7 +293,6 @@
|
||||
"playable": {
|
||||
"direct": "Közvetlenül játszható a böngészőben",
|
||||
"remux": "Közvetlen streamelés — könnyű remux (nincs videó-újrakódolás)",
|
||||
"transcode": "Átkódolás szükséges (nehezebb)",
|
||||
"transcode_hw": "Hardveres kódolóval átkódolva",
|
||||
"transcode_sw": "Szoftveresen átkódolva (legnehezebb)"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user