From 6d03bbc8e753a20b37965053f64d7ea732050cf5 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 31 Oct 2026 07:27:47 -0700 Subject: [PATCH] fix(media): the review pile is not a series, and a folder carries the year once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two destination defects the first tvdb-grade plan exposed in its own output — both visible by reading the plan's folder names, neither caught by any gate. 1. Jellyfin sanitizes a hidden folder's display name: `_unsorted` comes back as `unsorted`. The park-echo guard compared the row's series against the folder with the underscore, so 1,091 pile rows survived as a real series and the plan renamed 617 files out of the pile into a VISIBLE folder named `unsorted` — inventing the junk show the guard exists to prevent. The comparison now strips leading underscores from both sides. 2. TVDb hands back some series with the year already on the name (`Acapulco (2021)`). label_of appended the year anyway, producing `Acapulco (2021) (2021)` — a folder no matcher and no human reaches for twice. A name that already ends in `(YYYY)` is left alone. Both are covered by fixtures: a pile row whose series is the sanitized spelling must park, never file under the pile's name; a provider name carrying its year produces one year in the path and the plan contains no `(YYYY) (YYYY)` anywhere. --- scripts/media-shows.py | 17 ++++++++++++----- tests/media_shows.bats | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/scripts/media-shows.py b/scripts/media-shows.py index 481674b..5da7470 100755 --- a/scripts/media-shows.py +++ b/scripts/media-shows.py @@ -428,10 +428,13 @@ held = {r["path"]: r for r in rows} def row_names_the_park(path, name): """The library's own review pile is not a show. When the park sat INSIDE the library the server indexed the whole folder as one series named after it, and - a row like that would rename a real show to `_unsorted` — so a row whose - series is the hidden top-level folder the file itself sits in is dropped.""" + a row like that would rename a real show to the pile — so a row whose series is + the hidden top-level folder the file itself sits in is dropped. The server also + sanitizes the display name, dropping the leading underscore, so `unsorted` and + `_unsorted` are the same pile: compare them without their leading punctuation, + or the pile survives as a visible series folder.""" top = rel(path).split("/")[0] - return top.startswith("_") and eng.norm(name) == eng.norm(top) + return top.startswith("_") and eng.norm(name.lstrip("_")) == eng.norm(top.lstrip("_")) def in_review_pile(path): @@ -687,10 +690,14 @@ def series_key(name): def label_of(s): - """` ()`, the form both matchers read.""" + """` ()`, the form both matchers read. A provider's name can + arrive WITH the year already on it — TVDb hands back `Acapulco (2021)` for the + reboot — and appending a second one names a folder nothing will match again.""" nm, yr = identity(s) nm = clean(nm) - return f"{nm} ({yr})" if yr else nm + if not yr or re.search(r"\(\d{4}\)\s*$", nm): + return nm + return f"{nm} ({yr})" def episode_title(r, s, season, ep): diff --git a/tests/media_shows.bats b/tests/media_shows.bats index 71fe428..d5c3b9b 100755 --- a/tests/media_shows.bats +++ b/tests/media_shows.bats @@ -93,6 +93,7 @@ setup() { # The park inside the library: the server indexed the whole pile as ONE show # named after the folder, so its rows state a series that is not a show. at "$PARK/Park Show S01E01.mkv" 5000 + at "$PARK/Twisted.Metal.S01E01.1080p.WEB-DL.mkv" 5200 # The enum census lists every file; the planner reads that instead of walking # the tree, so the fixture writes the same three columns. @@ -226,6 +227,17 @@ PY = "$LIB/Show Box (2016)/Season 01/Show Box (2016) - S01E01 - Pilot.mkv" ] } +@test "a provider name that already carries the year is not given it twice" { + # TVDb answers the reboot as `Acapulco (2021)`. The folder contract is ONE year, + # and `Acapulco (2021) (2021)` is a folder neither matcher nor human reuses. + printf '{"tvdb:series:78850": {"name": "Show Box (2016)", "year": "2016"}}\n' > "$CACHE" + plan + [ "$(dst "$BOX/Show.Box.S01E01.1080p.WEB-DL.mkv")" \ + = "$LIB/Show Box (2016)/Season 01/Show Box (2016) - S01E01 - Pilot.mkv" ] + run grep -F '(2016) (2016)' "$OUT" + [ -z "$output" ] +} + @test "a series no authority resolves keeps the provider's name and is reported" { plan [ "$(dst "$LIB/SNL Pack/Season 19/01 Charles Barkley, Nirvana.avi")" \ @@ -241,6 +253,21 @@ PY [[ "$output" == "$OUTSIDE/"* ]] } +@test "the pile's sanitized display name is still the pile, not a series" { + # Jellyfin drops the leading underscore when it shows the hidden folder, so the + # row says the series is "unsorted" while the file sits in `_unsorted`. Match + # only the underscored spelling and the plan renames real files into a visible + # folder named after the review pile — which is how 617 of them got there. + title_row "$PARK/Twisted.Metal.S01E01.1080p.WEB-DL.mkv" "unsorted" "" 1 1 \ + "" "" "Twisted Metal" + plan_park + run awk -F '\t' -v l="$LIB" \ + '$1 == "move" && index($3, l "/unsorted/") == 1 {print $3}' "$OUT" + [ -z "$output" ] + run awk -F '\t' '$1 == "move" && $2 ~ /Twisted\.Metal\.S01E01/ {print $3}' "$OUT" + [[ "$output" == "$OUTSIDE/"* ]] +} + @test "a subtitle follows its episode and keeps its language flag" { plan [ "$(dst "$SEASON/The.Examples.S01E01.Pilot.1080p.x265.en.srt")" \