feat(media): tvdb names the show — titled episode files, id-backed series folders, park outside #102

Merged
dank merged 10 commits from feat/tv-tvdb-naming into main 2026-10-31 12:12:15 -07:00
2 changed files with 39 additions and 5 deletions
Showing only changes of commit 6d03bbc8e7 - Show all commits

No files matched your search

+12 -5
View File
@@ -428,10 +428,13 @@ held = {r["path"]: r for r in rows}
def row_names_the_park(path, name): def row_names_the_park(path, name):
"""The library's own review pile is not a show. When the park sat INSIDE the """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 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 a row like that would rename a real show to the pile — so a row whose series is
series is the hidden top-level folder the file itself sits in is dropped.""" 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] 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): def in_review_pile(path):
@@ -687,10 +690,14 @@ def series_key(name):
def label_of(s): def label_of(s):
"""`<Series> (<year>)`, the form both matchers read.""" """`<Series> (<year>)`, 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, yr = identity(s)
nm = clean(nm) 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): def episode_title(r, s, season, ep):
+27
View File
@@ -93,6 +93,7 @@ setup() {
# The park inside the library: the server indexed the whole pile as ONE show # 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. # named after the folder, so its rows state a series that is not a show.
at "$PARK/Park Show S01E01.mkv" 5000 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 enum census lists every file; the planner reads that instead of walking
# the tree, so the fixture writes the same three columns. # 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" ] = "$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" { @test "a series no authority resolves keeps the provider's name and is reported" {
plan plan
[ "$(dst "$LIB/SNL Pack/Season 19/01 Charles Barkley, Nirvana.avi")" \ [ "$(dst "$LIB/SNL Pack/Season 19/01 Charles Barkley, Nirvana.avi")" \
@@ -241,6 +253,21 @@ PY
[[ "$output" == "$OUTSIDE/"* ]] [[ "$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" { @test "a subtitle follows its episode and keeps its language flag" {
plan plan
[ "$(dst "$SEASON/The.Examples.S01E01.Pilot.1080p.x265.en.srt")" \ [ "$(dst "$SEASON/The.Examples.S01E01.Pilot.1080p.x265.en.srt")" \