From c70f9b3f6d481e03e75ed9ab69b26e1183440e8f Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 31 Oct 2026 07:38:54 -0700 Subject: [PATCH] fix(media): a film leaves the TV library only for a film it actually names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first tvdb-grade plan handed 12 files to the movie library, and two of them were wrong in a way the exceptions file could not show: an episode of the documentary series `Russia 1985-1999: TraumaZone` was renamed to the 1963 Bond film `From Russia with Love`, and `Gundam 00 Special Edition III - Return The World` became `Special Edition I - Celestial Being`. Two guards, each alone insufficient: - `title_of` salvages a short query from a long release name ("russia", "mobile suit gundam 00"), and tmdb() took the FIRST search result. The call site then compared the provider's words against that salvaged query, so any title merely CONTAINING the query passed. The provider's title must now be a run of words the FILE itself states: `Special Edition I: Celestial Being` is not in a filename that says `Special Edition III - Return The World`. - tmdb() read the year from `year`/`first_air_date`, which the MOVIE search does not return — it returns `release_date`. Every film answer therefore arrived year-less and the year guard at the call site could never fire. That is how a 1963 answer survived a filename stating 1991. Tests: a provider answer the file does not name parks, and nothing lands in the movie library; an answer from another year is refused. The year-from-the-wire half is proven by the live re-plan, which now dates every movie answer. --- scripts/media-shows.py | 18 ++++++++++++++---- tests/media_shows.bats | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/scripts/media-shows.py b/scripts/media-shows.py index 5da7470..6137661 100755 --- a/scripts/media-shows.py +++ b/scripts/media-shows.py @@ -278,9 +278,14 @@ def tmdb(kind, query): CACHE[ck] = None return None top = found[0] + # The two search endpoints date their rows differently: movies carry + # `release_date`, tv carries `first_air_date`. Reading only one of them leaves + # every film answer year-less, and a year-less answer can never be refused by + # the year guard below. + when = str(top.get("year") or "") or \ + str(top.get("release_date") or top.get("first_air_date") or "")[:4] CACHE[ck] = {"name": (top.get("title") or top.get("name") or "").strip(), - "year": str(top.get("year") or "") or (top.get("first_air_date") or "")[:4], - "id": top.get("id")} + "year": when, "id": top.get("id")} return CACHE[ck] @@ -787,8 +792,13 @@ for r, name, kind in extras: for r in films: parsed, year = title_of(r["base"]) got = tmdb("movie", parsed) - words = eng.norm(got["name"]).split() if got else [] - if not got or not eng.mentions(parsed.split(), words) \ + # The provider's title must be a run of words the FILE itself states. The query + # is only what title_of could salvage from the name, and a short one ("russia") + # lets the top hit be a different work entirely: a 1963 Bond film is not a 1991 + # documentary episode, and Special Edition I is not Special Edition III. + said = eng.norm(os.path.splitext(r["base"])[0]).split() + named = eng.norm(got["name"]).split() if got else [] + if not got or not eng.mentions(named, said) \ or (year != "?" and got["year"] and got["year"] != year): park(r, "long file with no episode evidence, and no movie provider names it") continue diff --git a/tests/media_shows.bats b/tests/media_shows.bats index d5c3b9b..043dc8a 100755 --- a/tests/media_shows.bats +++ b/tests/media_shows.bats @@ -360,6 +360,38 @@ json.dump({"movie:some documentary": [[ "$output" == "$PARK/"* ]] } +@test "a movie answer the file does not itself name stays in the review pile" { + # TMDb's top hit for a truncated query is a NEAR miss, not an answer: "mobile + # suit gundam 00" comes back as Special Edition I while the file is Special + # Edition III, and a one-word query "russia" comes back as a 1963 Bond film. A + # file leaves the TV library only when it states the title the provider names. + printf '8000\t%s\t7200\thevc\t1920x1080\t23.976\t4000000\tyuv420p10le\taac:eng\t0\t0\tmatroska,webm\n' \ + "$LIB/Longform/Some Documentary Special (2015).mkv" > "$PROBED" + python3 -c 'import json, sys +json.dump({"movie:some documentary": + {"name": "Some Documentary Special Part Two", "year": "2015", "id": 55}}, + open(sys.argv[1], "w"))' "$CACHE" + plan + run dst "$LIB/Longform/Some Documentary Special (2015).mkv" + [[ "$output" == "$PARK/"* ]] + run awk -F '\t' -v m="$MOVIES/" '$1 == "move" && index($3, m) == 1 {print $3}' "$OUT" + [ -z "$output" ] +} + +@test "a movie answer from another year is not that film" { + # The year the file states outranks a provider's eagerness: the 2015 special is + # not the 1999 one, however alike their names are. + printf '8000\t%s\t7200\thevc\t1920x1080\t23.976\t4000000\tyuv420p10le\taac:eng\t0\t0\tmatroska,webm\n' \ + "$LIB/Longform/Some Documentary Special (2015).mkv" > "$PROBED" + python3 -c 'import json, sys +json.dump({"movie:some documentary": + {"name": "Some Documentary Special", "year": "1999", "id": 55}}, + open(sys.argv[1], "w"))' "$CACHE" + plan + run dst "$LIB/Longform/Some Documentary Special (2015).mkv" + [[ "$output" == "$PARK/"* ]] +} + @test "the plan creates every folder before the move that needs it" { plan python3 - "$OUT" "$LIB" "$MOVIES" "$PARK" <<'PY'