docs(media): the TV library's own relayout, with the evidence chain it believes

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.
This commit is contained in:
Frank committed 2026-10-30 23:35:14 -07:00
1 parent 337afac91e
commit 536949c18d
2 files changed
+66 -5

No files matched your search

@@ -1,7 +1,7 @@
--- ---
type: reference type: reference
title: Media Relayout title: Media Relayout
description: "Census, plan and apply the movie library into Jellyfin format, and move TV out of it: the gates, refusals, rollback." description: "Census, plan and apply the movie and TV libraries into Jellyfin format: gates, evidence chain, refusals, rollback."
tags: [dankserver, media-server, jellyfin, plex, procedure] tags: [dankserver, media-server, jellyfin, plex, procedure]
timestamp: 2026-10-30T00:00:00Z timestamp: 2026-10-30T00:00:00Z
--- ---
@@ -12,8 +12,8 @@ The movie library on [[Entropy Volume]] arrived as release folders —
`Some.Movie.2019.1080p.WiKiTaLx.x265-FOO/` with the video inside named after `Some.Movie.2019.1080p.WiKiTaLx.x265-FOO/` with the video inside named after
the release. [[Jellyfin]] matches a movie by folder and file name, so the the release. [[Jellyfin]] matches a movie by folder and file name, so the
release noise cost posters, extras and versions. These scripts turn that release noise cost posters, extras and versions. These scripts turn that
library into Jellyfin's own shape — and move TV back out of it — and nothing library into Jellyfin's own shape — the movies, the TV packs, and the TV that
else in the repo touches a media file. turns up in the movie share — and nothing else in the repo touches a media file.
| Script | Touches media? | Output | | Script | Touches media? | Output |
|---|---|---| |---|---|---|
@@ -21,8 +21,9 @@ else in the repo touches a media file.
| `scripts/media-plan.py` | no — TSVs in, TSVs out | the plan + the exception report | | `scripts/media-plan.py` | no — TSVs in, TSVs out | the plan + the exception report |
| `scripts/media-apply.py` | **renames, never copies**; sweeps only what a run emptied | the moves, plus a journal | | `scripts/media-apply.py` | **renames, never copies**; sweeps only what a run emptied | the moves, plus a journal |
| `scripts/media-tv.py` | no — TSV out | a plan that moves TV into the TV library | | `scripts/media-tv.py` | no — TSV out | a plan that moves TV into the TV library |
| `scripts/media-shows.py` | no — TSV out | a plan that re-files the TV library itself |
The seam is `tests/media_relayout.bats`; the target contract is quoted from The seams are `tests/media_relayout.bats` and `tests/media_shows.bats`; the target contract is quoted from
[jellyfin.org/docs/general/server/media/movies](https://jellyfin.org/docs/general/server/media/movies) [jellyfin.org/docs/general/server/media/movies](https://jellyfin.org/docs/general/server/media/movies)
in `media-plan.py`'s docstring. in `media-plan.py`'s docstring.
@@ -153,6 +154,66 @@ folder it owns, never from the title whose bonus folder it happens to be parked
in — `The Cleveland Show S03E07` inside `Die Hard (1988)/extras/` is not a 1988 in — `The Cleveland Show S03E07` inside `Die Hard (1988)/extras/` is not a 1988
show. A name whose marker is `Part 2` is a film and stays put. show. A name whose marker is `Part 2` is a film and stays put.
## The TV library's own relayout
`/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 same shape, and it never guesses: every series name and every
episode number 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 (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.
# 3. The plan. --tmdb needs TMDB_API_KEY; without it a series no provider dated
# is simply named without a year, which Jellyfin accepts.
python3 scripts/media-shows.py --root /mnt/entropy/TVShows --enum /tmp/tv_enum.tsv \
--titles /tmp/tv_titles.tsv --probed /tmp/tv_probed.tsv \
--out /tmp/tv_plan.tsv --exceptions /tmp/tv_exceptions.txt
# 4. Read the exceptions file FIRST — it is the review, not a log. Then the same
# executor, journal and rollback as the movie relayout above.
scripts/media-apply.py apply --plan /tmp/tv_plan.tsv --root /mnt/entropy \
--journal /mnt/entropy/media-relayout-tv.jsonl --apply
```
Evidence, strongest first — the order the planner believes things:
- the **episode marker the file's own name states** (`S01E02-E03`, `1x04`);
- 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**: identity (the series name and its year), and the
numbering of a file whose own name says nothing;
- 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 a movie provider names it — otherwise it parks.
Two refusals carry the design, and a dry run on the real library found both:
- **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 row that still insists on season 1989
parks its file instead of creating `Season 1989`.
The provider's name is copied verbatim, warts included: Jellyfin carries a Series
named `Black Clover 001` (92 episodes, no year) because it matched a pack off a
filename. Renaming a show is Jellyfin's work — a folder rename never touches its
provider ids — so the mover keeps the name it is handed and strips only the pack
index a release leads with (`4a. Seed DESTINY` → `Seed DESTINY`).
## Laws ## Laws
- **A moved file is a 404 in [[Plex]] until its section is rescanned.** The - **A moved file is a 404 in [[Plex]] until its section is rescanned.** The
+1 -1
View File
@@ -52,7 +52,7 @@ is a locator: one row, one clause, no numbers.
| [[Jellyfin]] | Jellyfin on Dankserver: quadlet unit, overlay metadata view, provider keys, ports, access, transcode status. | | [[Jellyfin]] | Jellyfin on Dankserver: quadlet unit, overlay metadata view, provider keys, ports, access, transcode status. |
| [[mariadb]] | The MariaDB backend for RomM: quadlet unit, network alias, env-file secret handling, and the backup path. | | [[mariadb]] | The MariaDB backend for RomM: quadlet unit, network alias, env-file secret handling, and the backup path. |
| [[Media Metadata View]] | The SSD-backed overlay the two media servers read and write: shape, guards, commands, laws, rollback. | | [[Media Metadata View]] | The SSD-backed overlay the two media servers read and write: shape, guards, commands, laws, rollback. |
| [[Media Relayout]] | Census, plan and apply the movie library into Jellyfin format, and move TV out of it: the gates, refusals, rollback. | | [[Media Relayout]] | Census, plan and apply the movie and TV libraries into Jellyfin format: gates, evidence chain, refusals, rollback. |
| [[Plex]] | Plex Media Server is a **containerized media server on Dankserver**: a podman | | [[Plex]] | Plex Media Server is a **containerized media server on Dankserver**: a podman |
| [[redis]] | The Redis cache and task queue for RomM: quadlet unit, network alias topology, and the backup path. | | [[redis]] | The Redis cache and task queue for RomM: quadlet unit, network alias topology, and the backup path. |
| [[RomM]] | RomM (ROM Manager) is a self-hosted ROM library manager and player. | | [[RomM]] | RomM (ROM Manager) is a self-hosted ROM library manager and player. |