Media Relayout.md crossed the OKF 500-line ceiling with the 2026-11-02 TV laws, and the ceiling was right: the TV arm reads a different authority and refuses for different reasons, like the music and audiobook arms that already have pages. The arm moves verbatim to Media TV Relayout.md and gains two laws from the TVShows pass: a folder naming two series is not evidence of two series (ffprobe's embedded title tag answers per file), and right numbering with a wrong name means rename the folder only. The executor page keeps the executor laws and gains the hold-outside---root/--force law; Jellyfin.md gains the settle read (ScheduledTasks State, since /Library/ScanStatus is a 404 on 12.4). Index regenerated with gen-vault-index.py. cwd: /home/blank/.omp/wt/wt-20261030-163847-6d1fc1f · session: 01a18f2e-18b1-7573-bcd5-ac99636f8bcb
202 lines
12 KiB
Markdown
202 lines
12 KiB
Markdown
---
|
|
type: reference
|
|
title: Media TV Relayout
|
|
description: "Re-file TVShows from scene packs into the shape Jellyfin reads: authority order, refusals, the two-series split test."
|
|
tags: [dankserver, media-server, jellyfin, plex, procedure]
|
|
timestamp: 2026-11-02T11:00:00Z
|
|
---
|
|
|
|
# Media TV Relayout
|
|
|
|
The `/mnt/entropy/TVShows` share arrived as scene packs — release folders whose
|
|
names are the release, not the show. This arm re-files it into the shape
|
|
[[Jellyfin]] reads, with tvdb as the name authority. The executor, the journal,
|
|
the sweep and the rollback belong to [[Media Relayout]]; this page owns the TV
|
|
authority order, the refusals, and the test that decides a split.
|
|
|
|
`/mnt/entropy/TVShows` is scene packs rather than a library, and Jellyfin has
|
|
already matched and numbered most of it. `scripts/media-shows.py` re-files that
|
|
library into the shape Jellyfin reads, with **tvdb as the name authority**:
|
|
|
|
<Series> (<year>) [tvdbid-NNNN]/Season NN/
|
|
<Series> (<year>) - SxxEyy - <Episode Title>.ext
|
|
|
|
It never guesses: every series name, episode number and title it writes traces to
|
|
an authority, and what has none is parked for a human to read.
|
|
|
|
```bash
|
|
# 1. The authority: Jellyfin's own matched rows — each carries the series' TMDb
|
|
# and Tvdb ids and the title the row holds (the DB opens read-only).
|
|
python3 scripts/media-census.py jellyfin --root /mnt/entropy/TVShows \
|
|
--out /tmp/tv_titles.tsv
|
|
# 2. What is on disk (stat only), and the runtimes the film arm needs.
|
|
python3 scripts/media-census.py enumerate --root /mnt/entropy/TVShows \
|
|
--out /tmp/tv_enum.tsv
|
|
python3 scripts/media-census.py probe --root /mnt/entropy/TVShows \
|
|
--enum /tmp/tv_enum.tsv --jobs 6 --out /tmp/tv_probed.tsv
|
|
# The probe is the slow step: ffprobe reads both ends of every file. So re-use
|
|
# the last one — a duration belongs to the file, not to its name: re-key the
|
|
# previous census through that pass's own journal (`src` -> `dst`) instead of
|
|
# seeking the array again. It is also the only column the film arm reads, and
|
|
# only for a file with no marker and no provider row (4,794 of 58,551 today,
|
|
# all of them covered by a re-key that costs seconds, not an hour).
|
|
# 3. The plan. --tmdb needs TMDB_API_KEY — the v3 key in ~/.config/secrets/tmdb,
|
|
# which travels as the `api_key` query parameter. Only a v4 request token is
|
|
# accepted as a Bearer header, and the wrong shape answers 401 on EVERY call,
|
|
# which the summary line flags as NOT provider-complete. --tvdb reads its
|
|
# `api_key=` line from ~/.config/secrets/tvdb, never argv. Both write the SAME
|
|
# cache file, so the plan is reproducible offline afterwards: run it once to
|
|
# fill the cache, and quote the second run.
|
|
# Paths are only strings to a dry run, so this step can run on the workstation
|
|
# against scp'd TSVs — which is where the two provider secrets live.
|
|
python3 scripts/media-shows.py --root /mnt/entropy/TVShows --enum /tmp/tv_enum.tsv \
|
|
--titles /tmp/tv_titles.tsv --probed /tmp/tv_probed.tsv \
|
|
--tmdb-cache /mnt/entropy/media-tv-names.json --tmdb --tvdb \
|
|
--park /mnt/entropy/media-park/TVShows \
|
|
--out /mnt/entropy/media-relayout-shows2-plan.tsv \
|
|
--exceptions /mnt/entropy/media-relayout-shows2-exceptions.txt
|
|
# 4. Read the exceptions file FIRST — it is the review, not a log. Same executor
|
|
# as above, but on this arm's OWN journal, NAMED PER PASS: the journal of an
|
|
# applied pass is its unwind record, so a second pass that reuses the name
|
|
# destroys the only map back.
|
|
scripts/media-apply.py apply --plan /mnt/entropy/media-relayout-shows2-plan.tsv \
|
|
--root /mnt/entropy --journal /mnt/entropy/media-relayout-shows2.jsonl --apply
|
|
```
|
|
|
|
Evidence, strongest first — the order the planner believes things:
|
|
|
|
- the **episode marker the file's own name states** (`S01E02-E03`, `1x04`); a digit
|
|
run longer than three at that position is NOT a number — `S03E2019` and
|
|
`S04E47370355` are one box set's airdate fragment and pack index — so such a file
|
|
is renumbered by tvdb's title or parks, and is never truncated down to fit;
|
|
- the **`Season NN` folder it sits in and the number its name leads with inside
|
|
it**: `Season 19/01 Charles Barkley, Nirvana.avi` is S19E01;
|
|
- the **provider row**: which episode the file IS, and the numbering of a file
|
|
whose own name says nothing;
|
|
- the **row's tvdb id, else its TMDb id carried through TMDb's external ids, else
|
|
an exact tvdb name search**: that id names the SERIES FOLDER and supplies the
|
|
episode title, because a name the server read off a folder is not a show name.
|
|
The folder CARRIES the id: `[tvdbid-NNNN]` is what Jellyfin's Tvdb agent and
|
|
Plex's scanner both read, so a show whose title is ambiguous resolves by number
|
|
instead of by a human guessing which folder is which. The id rides the folder
|
|
only — a file name gets 180 characters and the episode title needs the room.
|
|
- a **bonus folder** anywhere above the file, or Jellyfin's `ExtraType` → the
|
|
series' canonical extra folder;
|
|
- a runtime over `SHORT_MAX` makes it film-shaped, and it leaves for `--movies` only
|
|
when the movie provider's title is a run of words the FILE itself states and its
|
|
year agrees with the file's: the query is only what `title_of` salvaged from a
|
|
release name, and the first hit for a short one is a near miss — a 1963 Bond film
|
|
for the word "russia", *Special Edition I* for a file saying *Edition III*. Else it
|
|
parks.
|
|
|
|
Four refusals carry the design: what the planner will NOT do is the point of it.
|
|
|
|
- **Folders resolve a series; they never invent one.** A folder's words may
|
|
resolve a file to a series a provider has already named — `(CBB) Tengen Toppa
|
|
Gurren Lagann - The Complete Series (BD 1080p)` names the registered *Tengen
|
|
Toppa Gurren Lagann* — but a bonus subfolder is not a show. An early run made
|
|
`…/03. Extra/01. NCOP/NCOP 01.mkv` into a root series called `01 NCOP`.
|
|
- **A scraped row does not outrank the filesystem.** Jellyfin numbers
|
|
multi-season packs badly: every file under `SNL 16-20/Season 16..19` carries
|
|
season 16 episode 20, and one Gundam row says season 2004. The folder, then
|
|
the file's own leading number, win.
|
|
- **A season that is a YEAR is the date matcher, not a season.** Jellyfin files
|
|
specials by airdate, so a row can arrive saying `season 2002`; that is not a
|
|
season and it never becomes a folder. The one authority on such a row is tvdb's
|
|
episode list read backwards: the title the row carries names the (season,
|
|
episode) tvdb gives it. A title tvdb gives to two episodes names nothing, and
|
|
the file parks with the reason it always carried. `Season 00` IS a legal
|
|
season — specials live there — so a row that really says 0 files there instead
|
|
of parking.
|
|
- **A season tvdb's own list contradicts gets no tvdb title.** 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: the slot its number points at is a
|
|
different episode, and dressing it in that episode's title is a confident lie.
|
|
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. 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
|
|
one Series item per box set — `24 (2001) Season 1`, `Modern Family S05` — and
|
|
copying those names verbatim launders folder junk into the contract. The planner
|
|
resolves each series to its tvdb id and writes tvdb's name and year; the merged
|
|
identity is also what turns a box set split across five release folders into ONE
|
|
folder, and what makes two files claiming one episode MEET in the duplicate rule.
|
|
A series no id resolves keeps the provider's words and is listed under *no tvdb
|
|
identity* in the exceptions — that list, not a rename, is the review.
|
|
|
|
**The review pile lives outside the library.** Files parked inside
|
|
`/mnt/entropy/TVShows` were indexed as one show named `_unsorted`, and that
|
|
show's rows then looked like evidence. So a row whose series is the hidden
|
|
top-level folder the file itself sits in is dropped — and the folder is matched
|
|
with its leading underscore stripped, because the server *displays* `_unsorted` as
|
|
`unsorted`: compare the spelled names and the pile survives the relayout as a
|
|
visible series folder that real files get renamed into. `--park` outside the root
|
|
makes the next plan re-file the pile instead of skipping it. Inside a hidden
|
|
top-level folder a file's own words may MATCH a registered show, never invent one.
|
|
|
|
**The operator's id map is the door for a name tvdb cannot be searched by.**
|
|
`scripts/media-shows.py` reads `scripts/media-shows-tvdb-ids.tsv` by default: one
|
|
line per pack folder, `folder <TAB> tvdb id <TAB> note`. The file exists because
|
|
tvdb titles a record in the show's own language and the English name rides in
|
|
*aliases* the name search never reads: `ブラッククローバー` (331753) answers to no
|
|
spelling of "Black Clover", and the bare alias `One Piece` belongs to 392276, the
|
|
2023 live action, not to the 1999 record (81797) the pack here is. The map
|
|
outranks a provider id — a box set's provider id is the server's own guess at a
|
|
folder name — and it merges the release folders that are one show. Everything it
|
|
touched is written to the exceptions: every folder it named, every row that
|
|
matched no folder, every disagreement with a provider id, every id tvdb never
|
|
answered. A line whose id is not a number stops the plan, because a typo renames
|
|
a whole pack to the wrong show. Only folders the search CANNOT reach belong in
|
|
it; a row for a folder the search would resolve hides a live lookup behind a
|
|
number nobody re-checks.
|
|
|
|
`scripts/media-shows-tvdb.py` makes the sheet the map is built from. It reads a
|
|
plan's `mkdir` rows, asks tvdb about every destination folder carrying no
|
|
`[tvdbid-`, and writes one row per folder with the verdict that says how much was
|
|
guessed to get there: `EXACT` (tvdb's own spelling), `PUNCT` (equal only after
|
|
release junk is stripped from the folder's words), `NEAR` (tvdb answered
|
|
something else — the best hit is printed for a human and nothing applies it),
|
|
`NONE`. Acceptance never moved across the passes that built this: a hit counts
|
|
only when tvdb's name equals the query with every non-alphanumeric dropped from
|
|
both sides, and the record's year still has to fall inside any year span the
|
|
folder names. That gate is what caught three confident picks — Fairy Tail's 2024
|
|
sequel (410031), the 2026 Hokuto no Ken remake (465216) and `Mugyutto! Black
|
|
Clover` (378762) offered for Black Clover. It is why the map is approved by hand
|
|
and the sheet is never fed to the planner.
|
|
|
|
## Laws of this arm
|
|
|
|
- **A folder that names two series is not evidence of two series — the files
|
|
answer.** `ffprobe -show_entries format_tag=title` names the series each file
|
|
belongs to. Measured 2026-11-02 on `COW and CHICKEN … Complete Animated TV
|
|
Series`, a bundle whose name pairs *Cow and Chicken* with *I Am Weasel*: 103
|
|
of 104 files carry a `Cow and Chicken - SxxEyy - <title>` tag and none carries
|
|
an *I Am Weasel* one. The 7-minute duration class is a half-episode segment,
|
|
not a second show, and a file holding both segments needs a remux, which this
|
|
executor will never do. Split only when the tags (or a provider episode list)
|
|
name both series per file; everything else is a rename.
|
|
- **When the numbering is right and only the name is wrong, rename the folder
|
|
and nothing else.** Tags whose `Sxx` matches the `Season NN` folder holding
|
|
them, per-season counts equal, and no duplicate `SxxEyy` key anywhere prove
|
|
the layout is already correct. Renumbering files under that evidence buys
|
|
nothing and can silently mislabel an episode.
|
|
|
|
## See Also
|
|
|
|
[[Media Relayout]] — the executor, the journal, the sweep and the rollback this
|
|
arm shares. [[Jellyfin]] — the reader of the resulting shape. [[Plex]] — the
|
|
second reader. [[Media Metadata View]] — where a rename's sidecars strand, or
|
|
do not.
|
|
|
|
## Citations
|
|
|
|
- Jellyfin Shows documentation — series, season and episode folder/file naming:
|
|
<https://jellyfin.org/docs/general/server/media/shows>
|