feat(media): the folder carries its tvdb id; specials and year-seasons resolve #105
No files matched your search
+34
-14
@@ -404,20 +404,37 @@ def tvdb_search(name):
|
||||
return None
|
||||
|
||||
|
||||
# Tvdb's default episode query answers AT MOST this many rows and states no
|
||||
# total: One Piece (1999), Crayon Shin-chan (1992) and Detective Conan (1996)
|
||||
# each answer exactly 500 while holding over a thousand. A list that long bounds
|
||||
# nothing above itself, so it must never be read as "the series holds 500".
|
||||
EPISODE_PAGE_CAP = 500
|
||||
|
||||
|
||||
def tvdb_episodes(sid):
|
||||
"""`season/episode` -> title for one series, in the one call tvdb answers."""
|
||||
"""`season/episode` -> title for one series, in the one call tvdb answers.
|
||||
Whether that call came back a cut-off page is recorded in the cache beside
|
||||
it, so a warm cache answers the same question a cold one did."""
|
||||
ck = f"tvdb:episodes:{sid}"
|
||||
if ck in CACHE:
|
||||
return CACHE[ck]
|
||||
data = tvdb(f"/series/{sid}/episodes/default")
|
||||
if not data:
|
||||
return None
|
||||
CACHE[ck] = {f"{e.get('seasonNumber')}/{e.get('number')}":
|
||||
(e.get("name") or "").strip()
|
||||
for e in data.get("episodes") or []}
|
||||
if ck not in CACHE:
|
||||
data = tvdb(f"/series/{sid}/episodes/default")
|
||||
if not data:
|
||||
return None
|
||||
rows = data.get("episodes") or []
|
||||
CACHE[ck] = {f"{e.get('seasonNumber')}/{e.get('number')}":
|
||||
(e.get("name") or "").strip() for e in rows}
|
||||
CACHE[f"tvdb:capped:{sid}"] = len(rows) >= EPISODE_PAGE_CAP
|
||||
return CACHE[ck]
|
||||
|
||||
|
||||
def tvdb_list_capped(sid):
|
||||
"""True when tvdb's episode answer for this series is a page, not a list."""
|
||||
rows = tvdb_episodes(sid)
|
||||
if not rows:
|
||||
return False
|
||||
return CACHE.get(f"tvdb:capped:{sid}", len(rows) >= EPISODE_PAGE_CAP)
|
||||
|
||||
|
||||
|
||||
# tvdb's titles read backwards, for the rows that carry a title and no numbering.
|
||||
_TITLE_IDX = {}
|
||||
@@ -757,14 +774,17 @@ def label_of(s):
|
||||
|
||||
def contradicted_seasons(s):
|
||||
"""Seasons whose numbering tvdb's own episode list contradicts — the one place
|
||||
a tvdb title can be a confident lie. An anime pack numbered by its release group
|
||||
books S05E01 in a season tvdb does not hold, or books more episodes there than
|
||||
tvdb lists, and the slot its number points at is a DIFFERENT episode. Books <=
|
||||
tvdb's count stays plausible: a partial pack is the normal shape."""
|
||||
a tvdb title can be a confident lie. An anime pack numbered by its release group
|
||||
books S05E01 in a season tvdb does not hold, or books more episodes there than
|
||||
tvdb lists, and the slot its number points at is a DIFFERENT episode. Books <=
|
||||
tvdb's count stays plausible: a partial pack is the normal shape. A list the
|
||||
authority cut off at its page limit (EPISODE_PAGE_CAP) contradicts nothing —
|
||||
it says nothing about the episodes it never sent."""
|
||||
if "contradicted" in s:
|
||||
return s["contradicted"]
|
||||
bad = set()
|
||||
eps = tvdb_episodes(s["tvdb"]) if s["tvdb"] else None
|
||||
eps = None if not s["tvdb"] or tvdb_list_capped(s["tvdb"]) \
|
||||
else tvdb_episodes(s["tvdb"])
|
||||
if eps:
|
||||
have = collections.Counter()
|
||||
for k in eps:
|
||||
|
||||
@@ -275,7 +275,11 @@ Four refusals carry the design: what the planner will NOT do is the point of it.
|
||||
The file still renames to the standard shape; the title does not ride, and the
|
||||
series is listed under *seasons whose numbering tvdb contradicts* in the
|
||||
exceptions. A season holding FEWER files than tvdb lists stays plausible — a
|
||||
partial pack is the normal shape.
|
||||
partial pack is the normal shape. Neither reading holds for a list tvdb
|
||||
cut off at its own page limit: the default episode query answers at most
|
||||
500 rows and states no total, and One Piece, Crayon Shin-chan and
|
||||
Detective Conan each answer exactly 500 while holding over a thousand —
|
||||
a page bounds nothing above itself, so it contradicts nothing.
|
||||
|
||||
**A provider name is evidence only when an id backs it.** Jellyfin names a Series
|
||||
after the folder it scanned, so a library filed by release folders ends up with
|
||||
|
||||
@@ -539,3 +539,30 @@ PY
|
||||
[ "$(dst "$LIB/Gate Pack/Season 05/two S05E01.mkv")" = "$want" ]
|
||||
grep -A6 -i "numbering tvdb contradicts" "$EXC" | grep -q "Anime Gate"
|
||||
}
|
||||
|
||||
# Tvdb's default episode query answers at most 500 rows and states no total, so a
|
||||
# list that long is a PAGE, not a list: One Piece (1999), Crayon Shin-chan (1992)
|
||||
# and Detective Conan (1996) each answer exactly 500 while holding over a thousand.
|
||||
# Read as the whole truth, that page contradicts every long-running show's pack
|
||||
# and strips the tvdb title from all of it.
|
||||
@test "an episode list the authority paginates never contradicts the pack" {
|
||||
python3 - "$CACHE" <<'PY'
|
||||
import json, sys
|
||||
json.dump({"tvdb:series:4242": {"name": "Long Run", "year": "1999"},
|
||||
"tvdb:episodes:4242": {f"1/{n}": f"Episode {n}"
|
||||
for n in range(1, 501)}},
|
||||
open(sys.argv[1], "w"))
|
||||
PY
|
||||
for n in $(seq 1 501); do
|
||||
ep=$(printf '%02d' "$n")
|
||||
file "$LIB/Long Pack/Season 01/long $ep.mkv" 1000
|
||||
title_row "$LIB/Long Pack/Season 01/long $ep.mkv" \
|
||||
"Long Run" 1999 1 "$n" "" "" "" "4242"
|
||||
done
|
||||
plan
|
||||
want="$LIB/Long Run (1999) [tvdbid-4242]/Season 01"
|
||||
want+="/Long Run (1999) - S01E01 - Episode 1.mkv"
|
||||
[ "$(dst "$LIB/Long Pack/Season 01/long 01.mkv")" = "$want" ]
|
||||
run grep -i "numbering tvdb contradicts" "$EXC"
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
Reference in new issue
Block a user