Three things the 2026-10-31 five-library relayout taught, written where the next operator reads them: - A verify run must open the servers' databases plain `mode=ro`. Both keep their libraries in WAL mode; `immutable=1` skips the write-ahead log, so a scanner's own committed rows read as missing and the read can fail as "database disk image is malformed". The census page claimed immutable — now it says what the code does. - "No row at the destination" splits into four classes, and only REAL (no row at dst AND the file still at src) is the relayout's own damage. Phantom rows naming a pre-move path are normal behaviour for both servers. And the owning server decides whether a pass can convict: Jellyfin holds no audiobook library, so that journal verifies against Plex or it proves nothing. - A chain of passes rolls back newest journal first, or the tool refuses; the refusals measured that day are the tool working, not failing. The music and audiobook arms moved to their own pages (progressive disclosure; the parent drops 175 lines and stays the owner of the shared gates, the executor, the journal law and the rollback). Stale per-run counts destroyed: episode counts, byte sizes, move tallies for a pass that already happened.
3.2 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | ||||
|---|---|---|---|---|---|---|---|---|
| reference | Media Music Relayout | Re-filing /mnt/entropy/Music by Jellyfin's tag rows: the census, the album key, the refusals. |
|
2026-10-31T00:00:00Z |
Media Music Relayout
/mnt/entropy/Music is rips and discography packs, and Jellyfin has matched and
tagged almost all of it. scripts/media-music.py re-files it into the shape
Jellyfin's music scanner reads —
Music/<album artist>/<Album> (<year>)/NN - <Title>.ext
— and the authority is the tag row of the track, never the name the ripper gave the file. The shared gates, the executor, the journal law and the rollback live in Media Relayout; this page is the music arm only.
# 1. The authority: Jellyfin's audio rows, one per track, with the album and
# artist ids its own database carries (the DB opens read-only).
python3 scripts/media-census.py music --root /mnt/entropy/Music \
--out /tmp/music_rows.tsv
# 2. What is on disk (stat only).
python3 scripts/media-census.py enumerate --root /mnt/entropy/Music \
--out /tmp/music_enum.tsv
# 3. The plan.
python3 scripts/media-music.py --root /mnt/entropy/Music --enum /tmp/music_enum.tsv \
--rows /tmp/music_rows.tsv --out /tmp/music_plan.tsv \
--exceptions /tmp/music_exceptions.txt
# 4. Read the exceptions file FIRST — it is the review, not a log. Then the same
# executor, journal and rollback as [[Media Relayout]] shows, on its own journal.
scripts/media-apply.py apply --plan /tmp/music_plan.tsv --root /mnt/entropy \
--journal /mnt/entropy/media-relayout-music.jsonl --apply
The group is what the tags assert, not where the files sit: (album-artist tag,
album, year). One album arrives as three disc folders and as two copies of one
rip, and one MusicBrainz release group is one album. A compilation is one album
whose tracks name many performers, so it files once under Various Artists:
keying on the performer would file one album once per artist. Two different
album-artist tags on one album name are two albums, which is what Jellyfin's two
album items say.
What it refuses:
+- A sidecar is placed by the audio around it, never by its own name. A
.lrc, .txt or picture that shares a track's stem follows that track and
takes the name the track now has, or Jellyfin cannot find it; anything else
goes to the album its own folder was placed in, or to the folder whose discs
sit one level below it. A folder whose audio belongs to two albums parks its
sidecar rather than guessing.
+- An image album keeps its name. A folder holding one audio file and a
.cue is a whole album in one file; numbering it 01 - … tells Jellyfin to
index it as one track.
+- A video container parks, because Jellyfin will not index it as music and a
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.
ONE writer at a time on this array: two rename passes race, and neither journal tells. Verify the run against Jellyfin's own tables — see Proving the move landed in Media Relayout.