From ceb5617a42547db2be12ce68507f5c71b61c531e Mon Sep 17 00:00:00 2001 From: dank Date: Sat, 31 Oct 2026 00:24:50 -0700 Subject: [PATCH] docs(media): the audiobook arm, and the music plan's applied state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One page owns the relayout facts: the new section carries the Plex-only reader, the shape, the four commands, and the two laws a future operator would otherwise re-litigate (never renumber a chapter; the shelf is the narrator). The music section said its plan was NOT applied — it was applied on 2026-10-31, and a stale line in a runbook gets believed, so it now names the journal and the rollback. --- .../Dankserver/Media Relayout.md | 82 ++++++++++++++++--- 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/serverAppConfigs-docs/Dankserver/Media Relayout.md b/serverAppConfigs-docs/Dankserver/Media Relayout.md index dd17018..b7ecc18 100644 --- a/serverAppConfigs-docs/Dankserver/Media Relayout.md +++ b/serverAppConfigs-docs/Dankserver/Media Relayout.md @@ -1,7 +1,7 @@ --- type: reference title: Media Relayout -description: "Census, plan and apply the movie and TV libraries into Jellyfin format: gates, evidence chain, refusals, rollback." +description: "Census, plan and apply the movie, TV, music and audiobook libraries into their media servers' format: gates, evidence chain, refusals, rollback." tags: [dankserver, media-server, jellyfin, plex, procedure] timestamp: 2026-10-30T00:00:00Z --- @@ -12,20 +12,23 @@ The movie library on [[Entropy Volume]] arrived as release folders — `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 release noise cost posters, extras and versions. These scripts turn that -library into Jellyfin's own shape — the movies, the TV packs, the TV that -turns up in the movie share, and the music library — and nothing else in the repo touches a media file. +library into the shape its media server reads — the movies, the TV packs, the TV +that turns up in the movie share, the music library, and the audiobooks, whose +only reader is Plex — and nothing else in the repo touches a media file. | Script | Touches media? | Output | |---|---|---| -| `scripts/media-census.py` | reads headers only | `enum`, `probed`, `plex` TSVs | +| `scripts/media-census.py` | reads headers and the servers' own DBs, never writes | `enum`, `probed`, and one authority TSV per library | | `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-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 | | `scripts/media-music.py` | no — TSV out | a plan that re-files the music library by its tags | +| `scripts/media-audiobooks.py` | no — TSV out | a plan that re-files the audiobooks by the book Plex matched | -The seams are `tests/media_relayout.bats`, `tests/media_shows.bats` and -`tests/media_music.bats`; the target contract is quoted from +The seams are `tests/media_relayout.bats`, `tests/media_shows.bats`, +`tests/media_music.bats` and `tests/media_audiobooks.bats`; the movie and TV +target contracts are quoted from [jellyfin.org/docs/general/server/media/movies](https://jellyfin.org/docs/general/server/media/movies) in `media-plan.py`'s docstring. @@ -292,10 +295,69 @@ What it refuses: rename to `.m4a` would claim it is; **audio no provider row names parks** too, because unmatched audio is a tagging job, not a renaming job. -The 2026-10-30 dry run files 114k tracks and parks 6.8k — mostly sidecars -sitting in a folder that holds two albums, plus audio no provider row names. The -plan is NOT applied: the exceptions file is the review, and it is Frank's to -read. +The plan was applied on 2026-10-31; its journal is +`/mnt/entropy/media-relayout-music.jsonl` and its rollback is that journal run +through `media-apply.py rollback --apply`. Whatever the tags could not name sits +in `Music/_unsorted`, keeping the relative path it had, for a human to review: +the pile is mostly sidecars in a folder holding two albums, plus audio no +provider row names, which is a tagging job and not a renaming job. + +## The audiobook library's own relayout + +`/mnt/entropy/Audiobooks` is the one library here that [[Jellyfin]] does not read +at all — its music scanner has no audiobook concept — while [[Plex]] indexes all +of it as Artist → Album → Track, where the album is the BOOK, that book's single +artist is the NARRATOR, and both were matched out of the files' own tags. +`scripts/media-audiobooks.py` re-files the tree so the property both servers' +book scanners assume is true — + + Audiobooks// ()/ + +Today it is not: the top level is a mix of author-named shelves and collection +packs, and a pack's book is split across `… [Part 1]`, `… [Part 2]` folders three +or four deep, so a scan shows three quarters of a novel. + +```bash +# 1. The authority: Plex's chapter rows for this root (the DB opens read-only). +python3 scripts/media-census.py audiobooks --root /mnt/entropy/Audiobooks \ + --out /tmp/ab_rows.tsv +# 2. What is on disk (stat only). +python3 scripts/media-census.py enumerate --root /mnt/entropy/Audiobooks \ + --out /tmp/ab_enum.tsv +# 3. The plan. +python3 scripts/media-audiobooks.py --root /mnt/entropy/Audiobooks \ + --enum /tmp/ab_enum.tsv --rows /tmp/ab_rows.tsv --out /tmp/ab_plan.tsv \ + --exceptions /tmp/ab_exceptions.txt +# 4. Read the exceptions file FIRST. Then the same executor, journal and rollback +# as above, on its own journal, and refresh the Plex section as in step 5 of +# the movie Procedure — from inside the container, never over the host port. +scripts/media-apply.py apply --plan /tmp/ab_plan.tsv --root /mnt/entropy \ + --journal /mnt/entropy/media-relayout-audiobooks.jsonl --apply +``` + +The group is the identity Plex asserts in its own database — **(narrator, book, +year)** — so one book arriving in four part folders becomes one folder, and a +1965 and a 2020 recording that share a title stay two books. + +Two laws here are the opposite of the music arm's, and both are forced by what +the rows actually hold: + ++- **A chapter is never renumbered.** Most books contain chapters whose index Plex + does not know, so a number written into a name would be invented — and it would + buy nothing, because both servers order playback by the embedded track tag, not + by the file name. A wrong number in a name is the number a human later trusts. + Fix order in the tags: a Plex scan re-reads tags and never renames a file. ++- **The shelf is the narrator, not the author.** Every book has exactly one artist + row and it is the narrator; the author survives only inside some album titles + (`Kryptos by Graham McNeill`) and in no column Plex stores, so deriving it would + mean parsing titles where a ripper happened to write one and inventing it + everywhere else. One rule that always resolves beats a shelf that is half author + and half narrator, and the author stays visible in the folder name Plex shows. + +What it refuses, same list as the music arm with one addition: a video container, +audio no chapter row names, and a loose file in a folder holding two books all +park under `_unsorted` with the path they have today — and so does a chapter whose +row has **no book or no narrator**, because inventing a shelf is filing it wrong. ## Laws