--- 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**: () [tvdbid-NNNN]/Season NN/ () - SxxEyy - .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 tvdb id 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 - ` 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>