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
12 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | |||||
|---|---|---|---|---|---|---|---|---|---|
| reference | Media TV Relayout | Re-file TVShows from scene packs into the shape Jellyfin reads: authority order, refusals, the two-series split test. |
|
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.
# 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 —S03E2019andS04E47370355are 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 NNfolder it sits in and the number its name leads with inside it:Season 19/01 Charles Barkley, Nirvana.aviis 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_MAXmakes it film-shaped, and it leaves for--moviesonly 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 whattitle_ofsalvaged 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.mkvinto a root series called01 NCOP. - A scraped row does not outrank the filesystem. Jellyfin numbers
multi-season packs badly: every file under
SNL 16-20/Season 16..19carries 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 00IS 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
S05E01in 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=titlenames the series each file belongs to. Measured 2026-11-02 onCOW and CHICKEN … Complete Animated TV Series, a bundle whose name pairs Cow and Chicken with I Am Weasel: 103 of 104 files carry aCow 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
Sxxmatches theSeason NNfolder holding them, per-season counts equal, and no duplicateSxxEyykey 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