Compare commits

..
Author SHA1 Message Date
Frank 03e0d84380 docs(audiobookshelf): the phone app's connection facts and where its token lives
The page told an agent how to sign in from a browser and nothing about the phone.
Measured 2026-11-03 on the deployed service:

- Server Address `http://192.168.0.235:8000`, user `frank`, plain HTTP, no base-path
  prefix; `/healthcheck` and `/ping` answer 200 from a second host on the LAN and
  `iptables -S INPUT` has no rule for 8000, so no phone-side setup is needed.
  `download: true` on the root account is what enables offline listening.
- `POST /login` puts the bearer at `user.token`, not top level. Reading
  `response.token` yields "", the client sends `Authorization: Bearer ` with nothing
  after it, and every `/api/*` answers 401 as though the password were wrong — which
  it was not. That is the failure I hit inside five minutes of scripting this.
- The LAN-only law gains its consequence for clients: the Anubis + Authentik chain
  serves browsers, so guarding a vhost does not make the phone app work remotely.
  Remote listening wants a VPN into the LAN; the LAN address is then still correct.

bats: 822 seams green.
2026-11-02 19:09:23 -08:00
dank 988c4899d4 Merge pull request 'docs(media): point plan/journal/park paths at .media-campaign and NeedsProcessing' (#125) from docs/media-artifact-paths into main 2026-11-02 18:42:34 -08:00
Frank 77e9973ff5 docs(media): point plan/journal/park paths at .media-campaign and NeedsProcessing
The campaign-artifact home moved (42bc482) but the copy-paste surfaces still
named the old ones, and a tool's usage block is exactly what an agent runs:

- scripts/media-shows.py, media-tv.py, media-tvdupes.py: usage examples wrote
  plans and journals at the array root. media-apply.py defaults its journal to
  <root>/media-relayout.jsonl, so any run copied from those lines re-creates
  the root-level clutter the vault now forbids. Each block now names
  /mnt/entropy/.media-campaign/ and says why --journal is explicit.
- media-shows.py: --park named the retired media-park; the pile is
  /mnt/entropy/NeedsProcessing/TVShows.
- Jellyfin.md: library-coverage law named /data/media-park; names the pile by
  its current path and records when it moved.
- Media TV Relayout.md: the runnable --park path named media-park.

bats: 822 seams green.
2026-11-02 18:02:48 -08:00
Frank 625263dd48 Merge remote-tracking branch 'origin/main' into feat/jellyfin-provider-keys 2026-11-02 17:44:49 -08:00
Frank 42bc482ed8 docs(media): campaign artifacts live in /mnt/entropy/.media-campaign
The array root carried 48 planning artifacts (journals, plans, run logs)
accumulated by the relayout campaigns since 2026-11-02. They sit beside the
libraries now, which is how a media server ends up indexing a journal.

Moved all 48 (219.8 MiB) into the dot-directory `/mnt/entropy/.media-campaign`
with a journaled `media-apply` run (plan, apply, verify — 48 verified at their
targets, 0 root files left). Jellyfin and Plex ignore dot-directories, and the
overlay view exposes the array root to both servers, so the same pollution
existed on the view side.

Every vault page that named a root-level journal path is rewritten to the new
home (26 occurrences across 6 pages), and the law is stated once, in
[[Media Relayout]], which owns the tool chain: `media-apply.py` defaults its
journal to `media-relayout.jsonl` beside `--root`, so a run that omits
`--journal` recreates exactly the root-level log this now forbids.
2026-11-02 17:44:49 -08:00
dank aea24353ad Merge pull request 'docs(jellyfin): the switches live under LibraryOptions; a pinned art pass is not a provider census' (#124) from feat/jellyfin-provider-keys into main 2026-11-02 17:39:21 -08:00
10 changed files with 93 additions and 48 deletions

No files matched your search

+10 -4
View File
@@ -78,10 +78,16 @@ one journal is one undo:
python3 scripts/media-shows.py --root /mnt/entropy/TVShows \ python3 scripts/media-shows.py --root /mnt/entropy/TVShows \
--enum /tmp/tv-enum.tsv --titles /tmp/jellyfin-tv.tsv \ --enum /tmp/tv-enum.tsv --titles /tmp/jellyfin-tv.tsv \
--tvdb --tmdb --out /mnt/entropy/media-tv-plan.tsv \ --tvdb --tmdb --out /mnt/entropy/.media-campaign/media-tv-plan.tsv \
--park /mnt/entropy/media-park/TVShows --park /mnt/entropy/NeedsProcessing/TVShows
python3 scripts/media-apply.py apply --plan /mnt/entropy/media-tv-plan.tsv \ python3 scripts/media-apply.py apply \
--root /mnt/entropy --journal /mnt/entropy/media-tv-relayout.jsonl --plan /mnt/entropy/.media-campaign/media-tv-plan.tsv \
--root /mnt/entropy --journal /mnt/entropy/.media-campaign/media-tv-relayout.jsonl
Every plan, journal and log lives in /mnt/entropy/.media-campaign/ — the array
root holds libraries only. media-apply.py defaults its journal to
<root>/media-relayout.jsonl, so pass --journal explicitly or the run puts the
log back on the root.
""" """
import argparse import argparse
import collections import collections
+13 -6
View File
@@ -17,13 +17,20 @@ the executor's own path guard still has teeth, and give it its own journal —
one journal is one undo, and a TV move must not be welded to a movie relayout: one journal is one undo, and a TV move must not be welded to a movie relayout:
python3 scripts/media-tv.py --root /mnt/entropy/Movies \ python3 scripts/media-tv.py --root /mnt/entropy/Movies \
--library /mnt/entropy/TVShows --out /mnt/entropy/media-tv-plan.tsv --library /mnt/entropy/TVShows \
python3 scripts/media-apply.py apply --plan /mnt/entropy/media-tv-plan.tsv \ --out /mnt/entropy/.media-campaign/media-tv-plan.tsv
--root /mnt/entropy --journal /mnt/entropy/media-relayout-tv.jsonl python3 scripts/media-apply.py apply \
python3 scripts/media-apply.py verify --plan /mnt/entropy/media-tv-plan.tsv \ --plan /mnt/entropy/.media-campaign/media-tv-plan.tsv \
--root /mnt/entropy --journal /mnt/entropy/media-relayout-tv.jsonl --root /mnt/entropy --journal /mnt/entropy/.media-campaign/media-relayout-tv.jsonl
python3 scripts/media-apply.py verify \
--plan /mnt/entropy/.media-campaign/media-tv-plan.tsv \
--root /mnt/entropy --journal /mnt/entropy/.media-campaign/media-relayout-tv.jsonl
python3 scripts/media-apply.py sweep --root /mnt/entropy \ python3 scripts/media-apply.py sweep --root /mnt/entropy \
--journal /mnt/entropy/media-relayout-tv.jsonl --apply --journal /mnt/entropy/.media-campaign/media-relayout-tv.jsonl --apply
Plans and journals live in `/mnt/entropy/.media-campaign/`, never loose on the
array root: `media-apply.py` defaults its journal to `<root>/media-relayout.jsonl`,
so `--journal` is always explicit here.
Guards, and why each one is here: Guards, and why each one is here:
+8 -6
View File
@@ -16,14 +16,16 @@ the journal and frees no space until the hold is purged.
python3 scripts/media-tvdupes.py --library /mnt/entropy/TVShows \ python3 scripts/media-tvdupes.py --library /mnt/entropy/TVShows \
--hold /mnt/entropy/.AGENTTRASH/20261102-tv-duplicates \ --hold /mnt/entropy/.AGENTTRASH/20261102-tv-duplicates \
--out /mnt/entropy/tv-dupes-plan.tsv --out /mnt/entropy/.media-campaign/tv-dupes-plan.tsv
python3 scripts/media-apply.py apply --plan /mnt/entropy/tv-dupes-plan.tsv \ python3 scripts/media-apply.py apply \
--root /mnt/entropy --journal /mnt/entropy/tv-dupes.jsonl \ --plan /mnt/entropy/.media-campaign/tv-dupes-plan.tsv \
--root /mnt/entropy --journal /mnt/entropy/.media-campaign/tv-dupes.jsonl \
--metadata-view /var/lib/media-meta/view --apply --metadata-view /var/lib/media-meta/view --apply
python3 scripts/media-apply.py verify --plan /mnt/entropy/tv-dupes-plan.tsv \ python3 scripts/media-apply.py verify \
--root /mnt/entropy --journal /mnt/entropy/tv-dupes.jsonl --plan /mnt/entropy/.media-campaign/tv-dupes-plan.tsv \
--root /mnt/entropy --journal /mnt/entropy/.media-campaign/tv-dupes.jsonl
python3 scripts/media-apply.py sweep --root /mnt/entropy \ python3 scripts/media-apply.py sweep --root /mnt/entropy \
--journal /mnt/entropy/tv-dupes.jsonl --apply # empty folders only --journal /mnt/entropy/.media-campaign/tv-dupes.jsonl --apply # empty folders only
scripts/media-view.sh reap # ghost sidecars above scripts/media-view.sh reap # ghost sidecars above
Then both servers have to be told: the plan moves files on the raw array, and no Then both servers have to be told: the plan moves files on the raw array, and no
@@ -36,7 +36,7 @@ manager) — **Internal-only:** LAN address, no vhost, no DNS name.
| Self-healing | `Restart=always`, `RestartSec=5`, `WantedBy=default.target` | | Self-healing | `Restart=always`, `RestartSec=5`, `WantedBy=default.target` |
| Deploy | `./scripts/deploy.sh dankserver --service audiobookshelf` | | Deploy | `./scripts/deploy.sh dankserver --service audiobookshelf` |
| Handler | `deploy_audiobookshelf` in `scripts/deploy-remote.sh` | | Handler | `deploy_audiobookshelf` in `scripts/deploy-remote.sh` |
| Library layout | one book per folder — ABS groups by the first directory holding a media file (see Laws); every move is journalled at `/mnt/entropy/media-relayout-audiobooks.jsonl` | | Library layout | one book per folder — ABS groups by the first directory holding a media file (see Laws); every move is journalled at `/mnt/entropy/.media-campaign/media-relayout-audiobooks.jsonl` |
## Bootstrap ## Bootstrap
@@ -65,6 +65,22 @@ never `/healthcheck`. It also reads files that are genuinely broken and logs
`[AudioFileScanner] Invalid data found when processing input` per file — that is `[AudioFileScanner] Invalid data found when processing input` per file — that is
the file, not the bind: the same line count grows while the total climbs. the file, not the bind: the same line count grows while the total climbs.
## Phone app
The official **Audiobookshelf** app connects with Server Address
`http://192.168.0.235:8000`, username `frank`, and the same password as the web UI —
no base-path prefix, plain HTTP. Verified from a second host 2026-11-03: `/healthcheck`
and `/ping` answer 200 across the LAN and `iptables -S INPUT` carries no rule for 8000,
so a phone on `192.168.0.0/24` connects with no further setup; `download: true` on the
root account is what makes offline listening work. Away from the LAN it cannot connect —
see the LAN-only law, and note that the guard chain is not the remedy there.
**The login token is at `user.token`, not top level.** `POST /login` answers
`{ user, serverSettings, ereaderDevices, Source, userDefaultLibraryId }` and the bearer sits
inside the user object. A client that reads `response.token` gets the empty string, sends
`Authorization: Bearer ` with nothing after it, and every `/api/*` call then answers `401`
as though the password were wrong.
## Laws ## Laws
- **Speak the image's env names, not the README's.** The server reads `PORT`, - **Speak the image's env names, not the README's.** The server reads `PORT`,
@@ -108,7 +124,10 @@ the file, not the bind: the same line count grows while the total climbs.
- **LAN-only until Frank says otherwise.** `0.0.0.0:8000` with no reverse proxy - **LAN-only until Frank says otherwise.** `0.0.0.0:8000` with no reverse proxy
in front. Any public vhost requires the Anubis + Authentik chain first, and the in front. Any public vhost requires the Anubis + Authentik chain first, and the
outpost proxy provider has to exist before the vhost flips (external exposure outpost proxy provider has to exist before the vhost flips (external exposure
policy in `AGENTS.md`). policy in `AGENTS.md`). That chain serves browsers: the phone app is a non-browser
client and cannot solve a challenge, so guarding a vhost does not make remote
listening work — remote listening wants a VPN into the LAN, after which the LAN
address in *Phone app* is correct unchanged.
- **There is no `/setup` route; the creation door is `POST /init`.** `/status` - **There is no `/setup` route; the creation door is `POST /init`.** `/status`
(no `/api` prefix) reports `isInit` = "a root user exists", `POST /init` with (no `/api` prefix) reports `isInit` = "a root user exists", `POST /init` with
`{"newRoot":{"username","password"}}` creates it, `POST /login` returns the `{"newRoot":{"username","password"}}` creates it, `POST /login` returns the
+3 -3
View File
@@ -58,9 +58,9 @@ reads — movie art has never existed beside the media on either layer.
**Library coverage is the first thing to check when a title is missing.** The **Library coverage is the first thing to check when a title is missing.** The
four libraries are `movies → /data/Movies`, `tvshows → /data/TVShows`, four libraries are `movies → /data/Movies`, `tvshows → /data/TVShows`,
`music → /data/Music`, `boxsets → /config/data/collections` `music → /data/Music`, `boxsets → /config/data/collections`
(`GET /Library/VirtualFolders`). `/data/media-park` is in **none** of them, so (`GET /Library/VirtualFolders`). The review pile — `/data/NeedsProcessing`, the
everything filed under it — the `_unsorted` release bundles and the `extras/` former `media-park`, drained into it 2026-11-02 — is in **none** of them, so
folders the relayout wrote there — is invisible to Jellyfin by configuration, everything filed there is invisible to Jellyfin by configuration,
not by fault: a recursive refresh answers 204, burns CPU for minutes and not by fault: a recursive refresh answers 204, burns CPU for minutes and
indexes nothing, because it never looks there. Check coverage before re-scanning. indexes nothing, because it never looks there. Check coverage before re-scanning.
@@ -41,7 +41,7 @@ python3 scripts/media-audiobooks.py --root /mnt/entropy/Audiobooks \
# library too ([[Audiobookshelf]]) — otherwise it keeps rows for folders that # library too ([[Audiobookshelf]]) — otherwise it keeps rows for folders that
# no longer exist. # no longer exist.
scripts/media-apply.py apply --plan /tmp/ab_plan.tsv --root /mnt/entropy \ scripts/media-apply.py apply --plan /tmp/ab_plan.tsv --root /mnt/entropy \
--journal /mnt/entropy/media-relayout-audiobooks.jsonl --apply --journal /mnt/entropy/.media-campaign/media-relayout-audiobooks.jsonl --apply
``` ```
The group is the identity Plex asserts in its own database — **(narrator, book, The group is the identity Plex asserts in its own database — **(narrator, book,
@@ -68,7 +68,7 @@ no chapter row names, and a loose file in a folder holding two books all park un
that unrelated books share, or a chapter number mistagged as the artist (`0.4`). that unrelated books share, or a chapter number mistagged as the artist (`0.4`).
`Various Artists` is not a placeholder — that is a multi-cast read. `Various Artists` is not a placeholder — that is a multi-cast read.
The reviewed plan lives at `/mnt/entropy/media-relayout-audiobooks-plan.tsv`; it The reviewed plan lives at `/mnt/entropy/.media-campaign/media-relayout-audiobooks-plan.tsv`; it
applies on its own journal, behind the music writer, and then refreshes the Plex applies on its own journal, behind the music writer, and then refreshes the Plex
section. Verify it with `--db-kind plex` — Jellyfin holds no rows here, so a section. Verify it with `--db-kind plex` — Jellyfin holds no rows here, so a
Jellyfin pass proves nothing either way. Jellyfin pass proves nothing either way.
@@ -65,8 +65,8 @@ executor's own format; it never moves anything itself.
python3 scripts/media-tvdupes.py --library /mnt/entropy/TVShows \ python3 scripts/media-tvdupes.py --library /mnt/entropy/TVShows \
--hold /mnt/entropy/.AGENTTRASH/<date>-tv-duplicates \ --hold /mnt/entropy/.AGENTTRASH/<date>-tv-duplicates \
--meta /var/lib/media-meta/view/TVShows --witness witness.tsv \ --meta /var/lib/media-meta/view/TVShows --witness witness.tsv \
--ids /mnt/entropy/tv-tvdb-ids.tsv \ --ids /mnt/entropy/.media-campaign/tv-tvdb-ids.tsv \
--out /mnt/entropy/tv-dupes-plan.tsv --json /tmp/tv-groups.json --out /mnt/entropy/.media-campaign/tv-dupes-plan.tsv --json /tmp/tv-groups.json
# then media-apply.py apply / verify / sweep / rollback, exactly as for a film prune # then media-apply.py apply / verify / sweep / rollback, exactly as for a film prune
``` ```
@@ -76,7 +76,7 @@ python3 scripts/media-tvdupes.py --library /mnt/entropy/TVShows \
match, not a lookup: `BaseItems` (`Type like '%TV.Series'`) joined to match, not a lookup: `BaseItems` (`Type like '%TV.Series'`) joined to
`BaseItemProviders` where `ProviderId = 'Tvdb'`, the `/data/TVShows/` prefix `BaseItemProviders` where `ProviderId = 'Tvdb'`, the `/data/TVShows/` prefix
stripped to the bare top-level folder name — 616 folders, none carrying two stripped to the bare top-level folder name — 616 folders, none carrying two
ids, written to `/mnt/entropy/tv-tvdb-ids.tsv`. Two folders with disjoint ids ids, written to `/mnt/entropy/.media-campaign/tv-tvdb-ids.tsv`. Two folders with disjoint ids
are two shows however alike they read — *Avatar: The Last Airbender* (1999) are two shows however alike they read — *Avatar: The Last Airbender* (1999)
beside the 2024 remake, *Millennium* (1996) beside a German thriller Jellyfin beside the 2024 remake, *Millennium* (1996) beside a German thriller Jellyfin
had also titled *Millennium* — and a pair whose identity cannot be proven is had also titled *Millennium* — and a pair whose identity cannot be proven is
@@ -158,12 +158,12 @@ Nothing is deleted. Every doomed copy moves into a dated hold with the same
executor the relayout uses, so any file comes back: executor the relayout uses, so any file comes back:
```bash ```bash
scripts/media-apply.py apply --plan /mnt/entropy/dup-prune-similar-plan.tsv \ scripts/media-apply.py apply --plan /mnt/entropy/.media-campaign/dup-prune-similar-plan.tsv \
--root /mnt/entropy --journal /mnt/entropy/dup-prune-similar.jsonl # dry --root /mnt/entropy --journal /mnt/entropy/.media-campaign/dup-prune-similar.jsonl # dry
scripts/media-apply.py apply --plan … --root /mnt/entropy --journal … --apply scripts/media-apply.py apply --plan … --root /mnt/entropy --journal … --apply
scripts/media-apply.py verify --plan … --journal /mnt/entropy/dup-prune-similar.jsonl scripts/media-apply.py verify --plan … --journal /mnt/entropy/.media-campaign/dup-prune-similar.jsonl
scripts/media-apply.py sweep --journal /mnt/entropy/dup-prune-similar.jsonl --root /mnt/entropy --apply scripts/media-apply.py sweep --journal /mnt/entropy/.media-campaign/dup-prune-similar.jsonl --root /mnt/entropy --apply
scripts/media-apply.py rollback --journal /mnt/entropy/dup-prune-similar.jsonl --root /mnt/entropy --apply scripts/media-apply.py rollback --journal /mnt/entropy/.media-campaign/dup-prune-similar.jsonl --root /mnt/entropy --apply
``` ```
The plan rows are the tool's own format (`action src dst kind`, `mkdir` rows for The plan rows are the tool's own format (`action src dst kind`, `mkdir` rows for
@@ -33,7 +33,7 @@ python3 scripts/media-music.py --root /mnt/entropy/Music --enum /tmp/music_enum.
# 4. Read the exceptions file FIRST — it is the review, not a log. Then the same # 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. # 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 \ scripts/media-apply.py apply --plan /tmp/music_plan.tsv --root /mnt/entropy \
--journal /mnt/entropy/media-relayout-music.jsonl --apply --journal /mnt/entropy/.media-campaign/media-relayout-music.jsonl --apply
``` ```
The group is what the tags assert, not where the files sit: **(album-artist tag, The group is what the tags assert, not where the files sit: **(album-artist tag,
@@ -71,12 +71,23 @@ that would be filed wrong, and the plan is not run until it is understood.
```bash ```bash
scripts/media-apply.py apply --plan /tmp/jf-plan.tsv --root /mnt/entropy/Movies # dry, writes nothing scripts/media-apply.py apply --plan /tmp/jf-plan.tsv --root /mnt/entropy/Movies # dry, writes nothing
scripts/media-apply.py apply --plan /tmp/jf-plan.tsv --root /mnt/entropy/Movies --apply scripts/media-apply.py apply --plan /tmp/jf-plan.tsv --root /mnt/entropy/Movies --apply
scripts/media-apply.py verify --plan /tmp/jf-plan.tsv --journal /mnt/entropy/media-relayout.jsonl scripts/media-apply.py verify --plan /tmp/jf-plan.tsv --journal /mnt/entropy/.media-campaign/media-relayout.jsonl
scripts/media-apply.py sweep --journal /mnt/entropy/media-relayout.jsonl --root /mnt/entropy/Movies # dry scripts/media-apply.py sweep --journal /mnt/entropy/.media-campaign/media-relayout.jsonl --root /mnt/entropy/Movies # dry
scripts/media-apply.py sweep --journal /mnt/entropy/media-relayout.jsonl --root /mnt/entropy/Movies --apply scripts/media-apply.py sweep --journal /mnt/entropy/.media-campaign/media-relayout.jsonl --root /mnt/entropy/Movies --apply
scripts/media-apply.py rollback --journal /mnt/entropy/media-relayout.jsonl --root /mnt/entropy/Movies --apply scripts/media-apply.py rollback --journal /mnt/entropy/.media-campaign/media-relayout.jsonl --root /mnt/entropy/Movies --apply
``` ```
**Every campaign artifact — plan, journal, exceptions file, run log — lives in
`/mnt/entropy/.media-campaign/`.** The array root carries libraries and nothing
else: a root strewn with `*.jsonl` and `*-plan.tsv` gets browsed as content, and
the dot-directory is ignored by both media servers. Relocated 2026-11-02, 48 files,
undo with `media-apply.py rollback --journal /mnt/entropy/.media-campaign/media-campaign-relocate.jsonl --root /mnt/entropy --apply`.
The holds and trash (`.AGENTTRASH`, `.HOLD-<date>-*`) stay at the root on purpose:
the purge machinery is given those paths as `--hold`, and a hold that moves out
from under the tool that made it is a hold nobody can find. Name `--journal`
explicitly on every run — the tool's default is `media-relayout.jsonl` beside
`--root`, so a bare run recreates exactly the root-level log this page forbids.
An apply also leaves the release folders it emptied standing where they were — An apply also leaves the release folders it emptied standing where they were —
hundreds of them at the library root — which is what `sweep` removes, dry-run hundreds of them at the library root — which is what `sweep` removes, dry-run
first, from the same journal. first, from the same journal.
@@ -110,7 +121,7 @@ a read beside a running scan can fail as "database disk image is malformed".
`immutable` stays only the fallback for a database whose `-shm` cannot be read. `immutable` stays only the fallback for a database whose `-shm` cannot be read.
```bash ```bash
scripts/media-verify.py --journal /mnt/entropy/media-relayout-music.jsonl \ scripts/media-verify.py --journal /mnt/entropy/.media-campaign/media-relayout-music.jsonl \
--db-kind jellyfin --ignore-ext jpg,jpeg,png,log,cue,toc,nfo,txt,m3u,accurip --db-kind jellyfin --ignore-ext jpg,jpeg,png,log,cue,toc,nfo,txt,m3u,accurip
``` ```
@@ -150,11 +161,11 @@ by series, and writes a plan in the executor's own format:
```bash ```bash
scripts/media-tv.py --root /mnt/entropy/Movies --library /mnt/entropy/TVShows \ scripts/media-tv.py --root /mnt/entropy/Movies --library /mnt/entropy/TVShows \
--out /mnt/entropy/media-tv-plan.tsv --out /mnt/entropy/.media-campaign/media-tv-plan.tsv
scripts/media-apply.py apply --plan /mnt/entropy/media-tv-plan.tsv --root /mnt/entropy scripts/media-apply.py apply --plan /mnt/entropy/.media-campaign/media-tv-plan.tsv --root /mnt/entropy
scripts/media-apply.py apply --plan /mnt/entropy/media-tv-plan.tsv --root /mnt/entropy \ scripts/media-apply.py apply --plan /mnt/entropy/.media-campaign/media-tv-plan.tsv --root /mnt/entropy \
--journal /mnt/entropy/media-relayout-tv.jsonl --apply --journal /mnt/entropy/.media-campaign/media-relayout-tv.jsonl --apply
scripts/media-apply.py sweep --journal /mnt/entropy/media-relayout-tv.jsonl \ scripts/media-apply.py sweep --journal /mnt/entropy/.media-campaign/media-relayout-tv.jsonl \
--root /mnt/entropy --apply --root /mnt/entropy --apply
``` ```
@@ -51,16 +51,16 @@ python3 scripts/media-census.py probe --root /mnt/entropy/TVShows \
# against scp'd TSVs — which is where the two provider secrets live. # 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 \ python3 scripts/media-shows.py --root /mnt/entropy/TVShows --enum /tmp/tv_enum.tsv \
--titles /tmp/tv_titles.tsv --probed /tmp/tv_probed.tsv \ --titles /tmp/tv_titles.tsv --probed /tmp/tv_probed.tsv \
--tmdb-cache /mnt/entropy/media-tv-names.json --tmdb --tvdb \ --tmdb-cache /mnt/entropy/.media-campaign/media-tv-names.json --tmdb --tvdb \
--park /mnt/entropy/media-park/TVShows \ --park /mnt/entropy/NeedsProcessing/TVShows \
--out /mnt/entropy/media-relayout-shows2-plan.tsv \ --out /mnt/entropy/.media-campaign/media-relayout-shows2-plan.tsv \
--exceptions /mnt/entropy/media-relayout-shows2-exceptions.txt --exceptions /mnt/entropy/.media-campaign/media-relayout-shows2-exceptions.txt
# 4. Read the exceptions file FIRST — it is the review, not a log. Same executor # 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 # 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 # applied pass is its unwind record, so a second pass that reuses the name
# destroys the only map back. # destroys the only map back.
scripts/media-apply.py apply --plan /mnt/entropy/media-relayout-shows2-plan.tsv \ scripts/media-apply.py apply --plan /mnt/entropy/.media-campaign/media-relayout-shows2-plan.tsv \
--root /mnt/entropy --journal /mnt/entropy/media-relayout-shows2.jsonl --apply --root /mnt/entropy --journal /mnt/entropy/.media-campaign/media-relayout-shows2.jsonl --apply
``` ```
Evidence, strongest first — the order the planner believes things: Evidence, strongest first — the order the planner believes things: