feat(media): plan a TV relayout from the numbering Jellyfin already has #99

Merged
dank merged 4 commits from feat/media-tv-relayout into main 2026-10-30 23:38:36 -07:00
Owner

Plan a TV relayout from the numbering Jellyfin already has

/mnt/entropy/TVShows is scene packs, not a library: release folders with
seasons buried several levels down, subtitles under Subs/<release-name>/, and
bonus material in nine spellings of "Extras". Jellyfin has already matched and
numbered most of it — 47,676 of its 58,551 files carry a matched, numbered
row
. This branch makes that work the authority for re-filing the library, and
refuses to guess where it is absent.

What lands

  • scripts/media-census.py jellyfin — export the matched rows (file series year season episode extra tmdb) from the live database, opened read-only.
  • scripts/media-census.py enumerate|probe — what is on disk, and the runtimes
    the film arm needs.
  • scripts/media-shows.py — the planner: plan TSV in the executor's own format
    plus an exceptions file that is the review, not a log.
  • scripts/media-apply.py is unchanged and does the moves: journal first,
    rollback and sweep afterwards. Nothing in this branch touches the library.
  • serverAppConfigs-docs/Dankserver/Media Relayout.md gains the section that
    owns the contract and the evidence chain.

Evidence, strongest first

  1. the episode marker the file's own name states (S01E02-E03, 1x04);
  2. 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;
  3. the provider row, for identity (series name and year) and for numbering a
    file whose own name says nothing;
  4. a bonus folder anywhere above the file, or Jellyfin's ExtraType;
  5. runtime over SHORT_MAX → film-shaped, and it leaves for --movies only
    when a movie provider names it.

Two refusals, both earned on the real library

  • Folders resolve a series; they never invent one. An early run turned
    …/03. Extra/01. NCOP/NCOP 01.mkv into a root-level series called 01 NCOP.
    Folder text may now only match a registered name as a token run —
    (CBB) Tengen Toppa Gurren Lagann - The Complete Series (BD 1080p) names
    Tengen Toppa Gurren Lagann — and a bonus subfolder with no registered name
    in any ancestor parks.
  • 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 in the database, and one Gundam row says season 2004. The folder and
    the file's own leading number win; a row that still insists on season 1989
    parks its file instead of creating Season 1989.

Also fixed on the way: a sidecar now searches every ancestor folder for the video
it names (the Subs/<release-name>/ layout was losing 792 subtitles), and the
shared bonus-folder test no longer reads a bare index as a title — name_words
drops "extras" as release junk, so 01. Extras reduced to 01 and "matched"
the 01 of NCOP 01, cancelling the bonus evidence in the engine's own guard.

Verification

  • tests/media_shows.bats: 19 behaviors, each one output-level — where a file
    lands, what parks, that a destination is never claimed twice, that re-planning
    a library the plan already satisfies moves nothing.
  • ./tests/run.sh: 684 tests green.
  • Dry run against the real library (read-only, nice -n 19): 48,757 moves,
    9,680 parked, 5,799 folders, 39.02 TiB "touched"
    — renames on one filesystem,
    so no data moves and no space is needed. The run-time census is still
    finishing, so the film arm has not had its durations yet: the 1,664 parked
    long files will become films (or stay parked) on the next plan.

What this does NOT do

Nothing is applied. scripts/media-apply.py --apply against the library is the
next step and belongs after Frank has read the exceptions report —
/tmp/tv_exceptions.txt, currently: 2,646 files that are not media or have
nothing beside them to belong to, 1,940 bonus files inside packs no provider
ever matched, 1,664 files no authority numbers, and ~450 files whose only row is
numbered nonsense (season 1989, season 0).

Rollback is the journal: media-apply.py rollback --journal /mnt/entropy/media-relayout-tv.jsonl --apply, per rename.


cwd: /home/blank/.omp/wt/wt-20261030-163847-6d1fc1f · session: 01a18f2e-18b1-7573-bcd5-ac99636f8bcb

# Plan a TV relayout from the numbering Jellyfin already has `/mnt/entropy/TVShows` is scene packs, not a library: release folders with seasons buried several levels down, subtitles under `Subs/<release-name>/`, and bonus material in nine spellings of "Extras". Jellyfin has already matched and numbered most of it — **47,676 of its 58,551 files carry a matched, numbered row**. This branch makes that work the authority for re-filing the library, and refuses to guess where it is absent. ## What lands - `scripts/media-census.py jellyfin` — export the matched rows (`file series year season episode extra tmdb`) from the live database, opened read-only. - `scripts/media-census.py enumerate|probe` — what is on disk, and the runtimes the film arm needs. - `scripts/media-shows.py` — the planner: plan TSV in the executor's own format plus an exceptions file that is the review, not a log. - `scripts/media-apply.py` is unchanged and does the moves: journal first, `rollback` and `sweep` afterwards. Nothing in this branch touches the library. - `serverAppConfigs-docs/Dankserver/Media Relayout.md` gains the section that owns the contract and the evidence chain. ## Evidence, strongest first 1. the episode marker the file's own name states (`S01E02-E03`, `1x04`); 2. 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; 3. the provider row, for identity (series name and year) and for numbering a file whose own name says nothing; 4. a bonus folder anywhere above the file, or Jellyfin's `ExtraType`; 5. runtime over `SHORT_MAX` → film-shaped, and it leaves for `--movies` only when a movie provider names it. ## Two refusals, both earned on the real library - **Folders resolve a series; they never invent one.** An early run turned `…/03. Extra/01. NCOP/NCOP 01.mkv` into a root-level series called `01 NCOP`. Folder text may now only match a registered name as a token run — `(CBB) Tengen Toppa Gurren Lagann - The Complete Series (BD 1080p)` names *Tengen Toppa Gurren Lagann* — and a bonus subfolder with no registered name in any ancestor parks. - **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 in the database, and one Gundam row says season 2004. The folder and the file's own leading number win; a row that still insists on season 1989 parks its file instead of creating `Season 1989`. Also fixed on the way: a sidecar now searches every ancestor folder for the video it names (the `Subs/<release-name>/` layout was losing 792 subtitles), and the shared bonus-folder test no longer reads a bare index as a title — `name_words` drops "extras" as release junk, so `01. Extras` reduced to `01` and "matched" the `01` of `NCOP 01`, cancelling the bonus evidence in the engine's own guard. ## Verification - `tests/media_shows.bats`: 19 behaviors, each one output-level — where a file lands, what parks, that a destination is never claimed twice, that re-planning a library the plan already satisfies moves nothing. - `./tests/run.sh`: 684 tests green. - Dry run against the real library (read-only, `nice -n 19`): **48,757 moves, 9,680 parked, 5,799 folders, 39.02 TiB "touched"** — renames on one filesystem, so no data moves and no space is needed. The run-time census is still finishing, so the film arm has not had its durations yet: the 1,664 parked long files will become films (or stay parked) on the next plan. ## What this does NOT do Nothing is applied. `scripts/media-apply.py --apply` against the library is the next step and belongs after Frank has read the exceptions report — `/tmp/tv_exceptions.txt`, currently: 2,646 files that are not media or have nothing beside them to belong to, 1,940 bonus files inside packs no provider ever matched, 1,664 files no authority numbers, and ~450 files whose only row is numbered nonsense (season 1989, season 0). Rollback is the journal: `media-apply.py rollback --journal /mnt/entropy/media-relayout-tv.jsonl --apply`, per rename. --- cwd: /home/blank/.omp/wt/wt-20261030-163847-6d1fc1f · session: 01a18f2e-18b1-7573-bcd5-ac99636f8bcb
dank added 4 commits 2026-10-30 23:37:07 -07:00
The Movies relayout planned from Plex; the TV library has no Plex rows and
58k files whose names lie about which episode they hold. Jellyfin has already
matched and numbered 47,676 of them, so the planner treats that numbering as
the authority and the file name as the fallback: an episode number the name
states, or a provider row, or nothing - and "nothing" parks the file under
_unsorted with a reason instead of inventing a number.

  - media-census.py jellyfin: one TSV row per matched episode, its container
    path mapped back to the host, series name/year/TMDb id from the Series row
    (the episode row carries none of them). Both TSV writers now end lines with
    \n so a shell consumer sees the last field clean.
  - media-shows.py: the plan. Series folders take the provider's own spelling;
    one file claiming two episodes keeps both numbers; two files claiming one
    episode - the bigger keeps the standard name, the other parks; bonus
    material files under the series' extras folder, never as an episode; a long
    file inside the TV library leaves for Movies only when TMDb answers with a
    matching title, otherwise it parks. Re-running the plan on a library it
    already filed moves nothing.
  - tests/media_shows.bats: 15 behaviors, including the executor preflight and
    the re-plan stability law.
The first run against the real library died in the anchor lookup: a subtitle
whose own name carries the media extension matches BOTH prefix forms of its
video, and the "keep the longest" comparison read the destination slot of the
tuple instead of the length slot. The fixture only ever matched one prefix, so
the seam was green over a crash.

The tail the sidecar keeps is now stripped of that container word: Jellyfin and
Plex both read "Show S01E01.forced.srt", not "Show S01E01.mkv.forced.srt", and
an nfo still carries the media extension because that one is constructed, not
parsed.
A dry run of the TV planner against the real 58k-file library produced a root
series called "01 NCOP" out of a bonus subfolder, copied Jellyfin's stale
numbering (every file under SNL 16-20/Season 16..19 carries season 16 episode
20; one Gundam row says season 2004), and lost subtitles a scene pack had
buried in Subs/<release-name>/.

- Folders now RESOLVE a series and never invent one: a folder's words match a
  registered name as a token run ("(CBB) Tengen Toppa Gurren Lagann - The
  Complete Series (BD 1080p)" names Tengen Toppa Gurren Lagann), and folder
  words alone name nothing, so an unknown pack's bonus folder parks instead of
  opening a new show at the library root.
- Numbering priority: the marker the file's own name states, then the Season NN
  folder plus the number the name leads with inside it, then the provider row.
  A row that still insists on season 1989 parks its file rather than creating
  Season 1989.
- The provider row keeps identity: the registered name and its year survive a
  file whose own marker numbers it, and the pack index a release leads with
  ("4a. Seed DESTINY") is folded out of the folder name.
- A sidecar searches every ancestor folder for the video it names, not just the
  parent, so Subs/<release-name>/<release>.jpn.srt follows its episode.
- The shared bonus-folder test no longer reads an index as a title: name_words
  drops "extras" as release junk, so "01. Extras" reduced to "01" and matched
  the "01" of "NCOP 01", cancelling the bonus evidence. Only words can name a
  film.
One page owns relayout: the census commands, the order the planner believes
things in, the two refusals a dry run on the real library proved necessary, the
apply and rollback path, and the standing wart that a provider name can itself
be scene junk ("Black Clover 001", 92 episodes) — renaming that is Jellyfin's
job, not the mover's.
dank added 3 commits 2026-10-30 23:37:07 -07:00
The first run against the real library died in the anchor lookup: a subtitle
whose own name carries the media extension matches BOTH prefix forms of its
video, and the "keep the longest" comparison read the destination slot of the
tuple instead of the length slot. The fixture only ever matched one prefix, so
the seam was green over a crash.

The tail the sidecar keeps is now stripped of that container word: Jellyfin and
Plex both read "Show S01E01.forced.srt", not "Show S01E01.mkv.forced.srt", and
an nfo still carries the media extension because that one is constructed, not
parsed.
A dry run of the TV planner against the real 58k-file library produced a root
series called "01 NCOP" out of a bonus subfolder, copied Jellyfin's stale
numbering (every file under SNL 16-20/Season 16..19 carries season 16 episode
20; one Gundam row says season 2004), and lost subtitles a scene pack had
buried in Subs/<release-name>/.

- Folders now RESOLVE a series and never invent one: a folder's words match a
  registered name as a token run ("(CBB) Tengen Toppa Gurren Lagann - The
  Complete Series (BD 1080p)" names Tengen Toppa Gurren Lagann), and folder
  words alone name nothing, so an unknown pack's bonus folder parks instead of
  opening a new show at the library root.
- Numbering priority: the marker the file's own name states, then the Season NN
  folder plus the number the name leads with inside it, then the provider row.
  A row that still insists on season 1989 parks its file rather than creating
  Season 1989.
- The provider row keeps identity: the registered name and its year survive a
  file whose own marker numbers it, and the pack index a release leads with
  ("4a. Seed DESTINY") is folded out of the folder name.
- A sidecar searches every ancestor folder for the video it names, not just the
  parent, so Subs/<release-name>/<release>.jpn.srt follows its episode.
- The shared bonus-folder test no longer reads an index as a title: name_words
  drops "extras" as release junk, so "01. Extras" reduced to "01" and matched
  the "01" of "NCOP 01", cancelling the bonus evidence. Only words can name a
  film.
One page owns relayout: the census commands, the order the planner believes
things in, the two refusals a dry run on the real library proved necessary, the
apply and rollback path, and the standing wart that a provider name can itself
be scene junk ("Black Clover 001", 92 episodes) — renaming that is Jellyfin's
job, not the mover's.
dank merged commit 82f6ec55c2 into main 2026-10-30 23:38:36 -07:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dank/serverAppConfigs#99