|
|
|
@@ -89,13 +89,16 @@ looks exactly like an unconfigured library.
|
|
|
|
|
| Library | `CollectionType` | `EnableInternetProviders` | `EnableRealtimeMonitor` |
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
| `Movies` | movies | true (fixed 2026-11-02) | **false** |
|
|
|
|
|
| `Shows` | tvshows | **false** | true |
|
|
|
|
|
| `Shows` | tvshows | true (fixed 2026-11-02) | true |
|
|
|
|
|
| `Music` | music | **false** | true |
|
|
|
|
|
| `Collections` | boxsets | **false** | false |
|
|
|
|
|
|
|
|
|
|
`Shows` and `Music` are silent file indexes in the sense above: a refresh answers
|
|
|
|
|
`Music` and `Collections` are silent file indexes in the sense above: a refresh answers
|
|
|
|
|
`204`, stores nothing, logs nothing. Any metadata or artwork work on either one
|
|
|
|
|
starts by flipping the switch, never by firing a scan.
|
|
|
|
|
starts by flipping the switch, never by firing a scan. `Shows` sat in that set
|
|
|
|
|
until 2026-11-02; the door is `POST /Library/VirtualFolders/LibraryOptions` with
|
|
|
|
|
the library's own `ItemId` (below), and `scripts/jellyfin-providers.sh verify` is
|
|
|
|
|
the one command that says which libraries are still in it.
|
|
|
|
|
|
|
|
|
|
**The per-library option door on 12.x is `POST /Library/VirtualFolders/LibraryOptions`**
|
|
|
|
|
— `GET` there answers `405`, which is how the route announces itself. The 10.x doors
|
|
|
|
@@ -119,6 +122,33 @@ store on a build where the automatic image pass will not run at all — it is ho
|
|
|
|
|
the Movies library got its art: 2,515 of 2,564 films carry a primary now, and the
|
|
|
|
|
rest have none in TMDb (shorts like `Astartes`, files whose names match nothing).
|
|
|
|
|
|
|
|
|
|
**The Download door's exact wire shape:** the list response carries the address in
|
|
|
|
|
`Url` — there is no `ImageUrl` field — and the POST takes camelCase query params,
|
|
|
|
|
`?type=Primary&imageUrl=<Url>`, answering `204`; the stored tag then appears in
|
|
|
|
|
`ImageTags`. PascalCase params, or an `imageUrl` left empty, answer `500`, which
|
|
|
|
|
reads as a server fault and is a field-name fault.
|
|
|
|
|
|
|
|
|
|
**`RemoteImages` answers empty for an item with no `ProviderIds`** — no provider id,
|
|
|
|
|
nothing to look up. Art coverage is capped by identification, not by the image door:
|
|
|
|
|
the items a campaign returns nothing for are the unidentified ones, and no image
|
|
|
|
|
retry will change that.
|
|
|
|
|
|
|
|
|
|
**A `Download` campaign over a library that is already dressed does nothing in
|
|
|
|
|
seconds, and that is the measurement proving identification is the only lever
|
|
|
|
|
left.** Run over all 753 series and 2,805 seasons of `Shows` on 2026-11-02 it
|
|
|
|
|
classified 3,558 items in 20 s: 2,908 already carried a primary, 624 have no
|
|
|
|
|
provider id at all, 26 have an id and no poster in TMDb — zero downloads. The
|
|
|
|
|
artless remainder is not an image-door problem: it is the unnamed long tail (clip
|
|
|
|
|
collections, `(Warhammer TV)`-shaped directories), and the lever is folder names
|
|
|
|
|
or an identify pass, never another retry loop.
|
|
|
|
|
|
|
|
|
|
**Series and season art is fetched; episode stills are not, deliberately.** Every
|
|
|
|
|
episode query is a fresh provider round-trip — nothing caches per episode — measured
|
|
|
|
|
at ~11 s per item beside ~2 s per series on the same host, with one hit in five in a
|
|
|
|
|
sample. Across 42k episodes that is days of provider traffic to dress a fifth of them,
|
|
|
|
|
and clients display the series and season art anyway. If episode stills are ever
|
|
|
|
|
ordered, the loop is the same door and its log makes the pass resumable.
|
|
|
|
|
|
|
|
|
|
**API shape on 12.x:** `/Items` and `/Items/{id}` need `UserId` when
|
|
|
|
|
authenticated by API key — without it the answer is `400 Error processing
|
|
|
|
|
request`, which reads exactly like an authentication failure. `MetadataRefreshMode`
|
|
|
|
@@ -232,9 +262,17 @@ scripts/jellyfin-providers.sh verify # plugin + key LENGTHS, never key bytes
|
|
|
|
|
- **The homelab TVDB key is a licensed key** (its JWT claims say
|
|
|
|
|
`community_supported: false`), so it needs **no PIN** — the optional
|
|
|
|
|
`TVDB_PIN=` line exists only for a subscriber-shaped key.
|
|
|
|
|
- **TVDB is installed and keyed but is not a metadata agent**: TMDb still
|
|
|
|
|
identifies everything. Enabling TVDB as a provider would re-fetch show
|
|
|
|
|
metadata across the library, so it is a deliberate, separate change.
|
|
|
|
|
- **TVDB is installed but is not a metadata agent**: TMDb still identifies
|
|
|
|
|
everything. Enabling TVDB as a provider would re-fetch show metadata across
|
|
|
|
|
the library, so it is a deliberate, separate change.
|
|
|
|
|
- **A plugin owns its own config file, under its own root, and omits null
|
|
|
|
|
settings** — so the element an installer wants to fill may simply not exist,
|
|
|
|
|
and its root is `<PluginConfiguration>`, not `<Configuration>`. TVDB's file had
|
|
|
|
|
no `<ApiKey>` element at all, which is why `install` refused with
|
|
|
|
|
`no place to write <ApiKey>` from October until the anchor fix: the secret sat
|
|
|
|
|
valid in `provider-keys` the whole time, and `verify`'s `MISSING key` was this
|
|
|
|
|
fault, not a missing key. The writer now anchors on the file's own root and
|
|
|
|
|
still refuses by name when a file has none.
|
|
|
|
|
- **The deployer places the keys before it restarts jellyfin**
|
|
|
|
|
(`ensure_provider_keys` in `scripts/deploy-remote.sh`) and warns instead of
|
|
|
|
|
failing when `provider-keys` is absent, so a host without it still deploys.
|
|
|
|
@@ -284,12 +322,34 @@ anything. The fix is `systemctl --user restart jellyfin.service`, one POST after
|
|
|
|
|
health answers, and then believe `ScheduledTasks` State at 130 % CPU, not the
|
|
|
|
|
response code.
|
|
|
|
|
|
|
|
|
|
**A scan that must see a new folder has to be recursive.** `POST /Library/Refresh`
|
|
|
|
|
is the incremental pass, and inotify does not cross a rootless podman bind, so
|
|
|
|
|
folders created on the host stay invisible to it. The move is
|
|
|
|
|
`POST /Items/{id}/Refresh?Recursive=true` (204) per library, the id from
|
|
|
|
|
`GET /Library/VirtualFolders` — its field is `ItemId`, not `Id` — and that same
|
|
|
|
|
record's `RefreshStatus` is the progress read.
|
|
|
|
|
**The door that scans is `POST /Library/Refresh` (204), and `ScheduledTasks` is
|
|
|
|
|
the progress read.** A task that has never completed answers
|
|
|
|
|
`LastTaskResult=None, EndTimeUtc=None`, and that pair is what separates "never
|
|
|
|
|
ran" from "finished a moment ago" — the difference between a door that is dead
|
|
|
|
|
and one that merely answered `204`.
|
|
|
|
|
|
|
|
|
|
**The per-library `POST /Items/{id}/Refresh` queues nothing, and the provider
|
|
|
|
|
switch is not the reason.** Measured twice on 2026-11-02 against `Shows`: before
|
|
|
|
|
the flip and again after it, the call answered `204`, the journal wrote zero lines
|
|
|
|
|
over the following ten minutes, and `Scan Media Library` kept its never-run
|
|
|
|
|
signature; a service restart changes nothing. `POST /Library/Refresh` does run —
|
|
|
|
|
`204`, then `Running` inside 15 s at 33 % CPU — but it walks every library, so on
|
|
|
|
|
this build a library-scoped metadata pass has no working door and the global one
|
|
|
|
|
is the only lever.
|
|
|
|
|
|
|
|
|
|
**That endpoint takes its parameters from one source.** The documented query form
|
|
|
|
|
(empty body) and the JSON body form (no query) each parse; supplying both at once
|
|
|
|
|
answers `400` with an empty body whatever the values, and `ValidationFailure` is
|
|
|
|
|
rejected in the body while the query form accepts it.
|
|
|
|
|
|
|
|
|
|
**`RefreshStatus` on the `GET /Library/VirtualFolders` record is not a progress
|
|
|
|
|
read.** It stayed `Idle` through a scan that was verifiably running at 75 %. The
|
|
|
|
|
record's useful field is `ItemId`, and the live library set is still the entries
|
|
|
|
|
whose `Locations` are non-empty.
|
|
|
|
|
|
|
|
|
|
`inotify` does not cross a rootless podman bind, so folders created on the host
|
|
|
|
|
stay invisible until a pass like this runs; every pass that writes into a library
|
|
|
|
|
ends by firing one.
|
|
|
|
|
|
|
|
|
|
**`Movies` has no watcher at all.** `EnableRealtimeMonitor` is per library (table in
|
|
|
|
|
*Data layout*) and it is false for `Movies` and `Collections`. Nothing notices a
|
|
|
|
|