The three folder image providers #110 did not name #152

Closed
opened 2026-08-16 17:03:55 +02:00 by Claude · 1 comment
Collaborator

#110 named seven of upstream's dynamic image providers and closed them. There
are ten. The three it did not name all subclass BaseFolderImageProvider
(Emby.Server.Implementations/Images/) and all do the same thing: take the
first child's Primary picture and copy it whole, which is the
CreateSingleImage path the box set already goes through here.

  • MusicAlbumImageProvider — the one that matters. An album with no cover
    of its own is given the first track's, which together with the
    AudioImageProvider #110 landed is the whole chain that gives a music library
    artwork: the track's cover art comes out of the file, and the album copies it.
    Without this an album stays blank while its tracks do not.
  • FolderImageProvider — a plain folder, with two exclusions of its own: a
    PhotoAlbum or a MusicAlbum is somebody else's, and a folder that is a top
    parent gets none.
  • PhotoAlbumImageProvider — the same for a photo album.

The child chosen is not the first row: the query is
Parent = item, Recursive = true, ImageTypes = [Primary], OrderBy = (IsFolder asc, SortName asc), Limit = 1, so a folder sorts before a file and the name
decides between equals.

One behaviour to port deliberately rather than discover: HasChangedByDate
returns false for a MusicAlbum, so an album's copied cover is never
considered stale by the file's date the way every other one is.

Where to start

jellyfin_app::scan::by_name::copied and LINKED are the shape — the box set
already copies a picture whole and files it as poster<ext> under the item's
own metadata directory. What differs is the source query (children rather than
linked children) and the filename: ImageSaver.GetStandardSavePath writes
folder<ext> and not poster<ext> for a MusicAlbum, a MusicArtist, a
PhotoAlbum and a Person.

How to know it is done

diff-db on BaseItemImageInfos over a music library both servers scanned, and
the files compared on bytes — a copy is a copy, so this is one of the few
picture paths where byte equality is available rather than approximate.

It cannot be measured against the fixture as it stands. Registration
disables every image fetcher per type, so upstream draws none of these either;
and nothing in the fixture media carries cover art for an album to copy. Both
are the same precondition #110 left behind — see reference/README.md.

House rules

Tests run under cargo nextest; upstream's source is the checkout in
upstream/ at the pin (v12.0-rc5, 6d501ba4), not the clone beside the
repository. Generated artifacts are regenerated, never edited. A comment says
what the code does, not how it got there. ARCHITECTURE.md decides which crate
a thing goes in; DECISIONS.md says why a rule exists.

#110 named seven of upstream's dynamic image providers and closed them. There are ten. The three it did not name all subclass `BaseFolderImageProvider` (`Emby.Server.Implementations/Images/`) and all do the same thing: take the first child's `Primary` picture and copy it whole, which is the `CreateSingleImage` path the box set already goes through here. - **`MusicAlbumImageProvider`** — the one that matters. An album with no cover of its own is given the first track's, which together with the `AudioImageProvider` #110 landed is the whole chain that gives a music library artwork: the track's cover art comes out of the file, and the album copies it. Without this an album stays blank while its tracks do not. - **`FolderImageProvider`** — a plain folder, with two exclusions of its own: a `PhotoAlbum` or a `MusicAlbum` is somebody else's, and a folder that is a top parent gets none. - **`PhotoAlbumImageProvider`** — the same for a photo album. The child chosen is not the first row: the query is `Parent = item, Recursive = true, ImageTypes = [Primary], OrderBy = (IsFolder asc, SortName asc), Limit = 1`, so a folder sorts before a file and the name decides between equals. One behaviour to port deliberately rather than discover: `HasChangedByDate` returns `false` for a `MusicAlbum`, so an album's copied cover is never considered stale by the file's date the way every other one is. ## Where to start `jellyfin_app::scan::by_name::copied` and `LINKED` are the shape — the box set already copies a picture whole and files it as `poster<ext>` under the item's own metadata directory. What differs is the source query (children rather than linked children) and the filename: `ImageSaver.GetStandardSavePath` writes `folder<ext>` and not `poster<ext>` for a `MusicAlbum`, a `MusicArtist`, a `PhotoAlbum` and a `Person`. ## How to know it is done `diff-db` on `BaseItemImageInfos` over a music library both servers scanned, and the files compared on bytes — a copy is a copy, so this is one of the few picture paths where byte equality is available rather than approximate. **It cannot be measured against the fixture as it stands.** Registration disables every image fetcher per type, so upstream draws none of these either; and nothing in the fixture media carries cover art for an album to copy. Both are the same precondition #110 left behind — see `reference/README.md`. ### House rules Tests run under `cargo nextest`; upstream's source is the checkout in `upstream/` **at the pin** (`v12.0-rc5`, `6d501ba4`), not the clone beside the repository. Generated artifacts are regenerated, never edited. A comment says what the code does, not how it got there. `ARCHITECTURE.md` decides which crate a thing goes in; `DECISIONS.md` says why a rule exists.
Author
Collaborator

Landed in 9a2f8fd.

Both providers are the CreateSingleImage copy the box set already takes, so
what went in is the source query and the filename, not new mechanism:
rows_to_illustrate_from_inside and by_name::copied_as.

What the port settles

Only two of the three. PhotoAlbumImageProvider stays absent because
nothing here files a photo album — Kind has no such variant.

Neither reaches a series, a season or a box set. This was the thing worth
checking before writing anything. BaseFolderImageProvider<T> is an
ICustomMetadataProvider<T>, IMetadataProvider<TItemType> is invariant, and
ProviderManager.RefreshSingleItem picks the metadata service by exact type
first (CanRefreshPrimary) — so FolderImageProvider, typed Folder, is
offered only to FolderMetadataService, which claims a plain Folder. A
Series gets SeriesMetadataService and never sees it. The issue's framing —
"a plain folder, with two exclusions of its own" — is right, and the exclusions
for PhotoAlbum/MusicAlbum inside FolderImageProvider.Supports are
unreachable for the same reason.

The directory a library points at is passed over. IsTopParent is true for
a folder whose parent is the AggregateFolder, which is exactly the physical
directory a library covers, so upstream draws nothing for it.

Only the first candidate. The base query is Parent = item, Recursive = true, ImageTypes = [Primary], OrderBy = (IsFolder asc, SortName asc), Limit = 1, and CreateSingleImage then filters that one for a local file with an
extension. So a row whose first candidate has no extension gets nothing — it
does not fall through to the next, which is the opposite of the box set,
where the whole child list is handed over and filtered afterwards. Both are
reproduced as they are.

The album's exclusion is the reason it is there. Limit = 1 runs before
.Where(i => i is not Folder), so the filter only bites when the single
candidate is a folder — which happens when no file below the album carries a
Primary. Upstream's comment says why: a folder can have been given a collage,
and copying that onto the record would be the album wearing another row's
picture.

Where it runs

Directly after the extraction the previous commit landed and before the by-name
pictures. That order is load-bearing in both directions: the cover an album
copies is the one AudioImageProvider just took out of its first track, and a
music genre's collage is drawn from what both leave behind.

What is not measured

The same wall #110 left: the fixture disables every image fetcher per library
and per type, so upstream copies nothing there either, and no track in the
fixture media carries cover art for an album to copy. This is measurable
against a pair on a library provisioned with the fetchers on — see
reference/README.md and the fixture half of #153.

HasChangedByDate returning false for a MusicAlbum is not ported and does
not need to be: it governs re-drawing, and this server leaves a row that
already carries a picture alone. The note beside that check says so.

Landed in `9a2f8fd`. Both providers are the `CreateSingleImage` copy the box set already takes, so what went in is the source query and the filename, not new mechanism: `rows_to_illustrate_from_inside` and `by_name::copied_as`. ## What the port settles **Only two of the three.** `PhotoAlbumImageProvider` stays absent because nothing here files a photo album — `Kind` has no such variant. **Neither reaches a series, a season or a box set.** This was the thing worth checking before writing anything. `BaseFolderImageProvider<T>` is an `ICustomMetadataProvider<T>`, `IMetadataProvider<TItemType>` is invariant, and `ProviderManager.RefreshSingleItem` picks the metadata service by exact type first (`CanRefreshPrimary`) — so `FolderImageProvider`, typed `Folder`, is offered only to `FolderMetadataService`, which claims a plain `Folder`. A `Series` gets `SeriesMetadataService` and never sees it. The issue's framing — "a plain folder, with two exclusions of its own" — is right, and the exclusions for `PhotoAlbum`/`MusicAlbum` inside `FolderImageProvider.Supports` are unreachable for the same reason. **The directory a library points at is passed over.** `IsTopParent` is true for a folder whose parent is the `AggregateFolder`, which is exactly the physical directory a library covers, so upstream draws nothing for it. **Only the first candidate.** The base query is `Parent = item, Recursive = true, ImageTypes = [Primary], OrderBy = (IsFolder asc, SortName asc), Limit = 1`, and `CreateSingleImage` then filters that one for a local file with an extension. So a row whose first candidate has no extension gets nothing — it does *not* fall through to the next, which is the opposite of the box set, where the whole child list is handed over and filtered afterwards. Both are reproduced as they are. **The album's exclusion is the reason it is there.** `Limit = 1` runs before `.Where(i => i is not Folder)`, so the filter only bites when the single candidate is a folder — which happens when no file below the album carries a `Primary`. Upstream's comment says why: a folder can have been given a collage, and copying that onto the record would be the album wearing another row's picture. ## Where it runs Directly after the extraction the previous commit landed and before the by-name pictures. That order is load-bearing in both directions: the cover an album copies is the one `AudioImageProvider` just took out of its first track, and a music genre's collage is drawn from what both leave behind. ## What is not measured The same wall #110 left: the fixture disables every image fetcher per library and per type, so upstream copies nothing there either, and no track in the fixture media carries cover art for an album to copy. This is measurable against a pair on a library provisioned with the fetchers on — see `reference/README.md` and the fixture half of #153. `HasChangedByDate` returning `false` for a `MusicAlbum` is not ported and does not need to be: it governs re-drawing, and this server leaves a row that already carries a picture alone. The note beside that check says so.
Sign in to join this conversation.
No description provided.