No harness run has ever exercised a trickplay route #148

Closed
opened 2026-08-16 15:13:37 +02:00 by Claude · 3 comments
Collaborator

cargo xtask reference provision never sets EnableTrickplayImageExtraction.
There is no such element in any of the three options.xml, and
SELECT COUNT(*) FROM TrickplayInfos is 0 in both snapshots. Two consequences,
found while landing #99 and #140.

The columns #99 landed are diffed against nothing. TrickplayInfos is empty
on both sides of every pair run, so the seven columns a client indexes with —
width, height, tile counts, thumbnail count, interval and Bandwidth — agree by
being absent. Bandwidth in particular is a pure function of the encoded tile's
byte length, and its only coverage anywhere is
jellyfin_core::image::sheet_bandwidth's unit cases and the committed answer key
in crates/jellyfin-images/tests/sheet.rs.

Two scenario requests refuse for a reason they do not name.
crates/jellyfin-harness/scenarios/refusals.json:168-172 and :188-192 —
trickplay-tiles-at-a-width-nothing-was-cut-at and
trickplay-tile-past-the-last-sheet — both refuse because the item has no
sheets at all, not because the width is one nothing was cut at or the index is
past the last sheet. They agree between the servers and they cannot fail for the
reason they are named after. They pre-date #99; #99 is what made them visibly
wrong.

Where to start

xtask/src/reference/ is where provision decides the library options. Setting
EnableTrickplayImageExtraction and ExtractTrickplayImagesDuringLibraryScan
makes a bootstrap produce sheets, at which point both scenarios refuse for the
reason they name and TrickplayInfos becomes a table with rows on both sides.

The cost is a snapshot rebuild, which redraws the account and the server ids that
scenarios and config.toml both name — so this wants to happen alongside the
next rebuild rather than on its own.

How to know it is done

TrickplayInfos is non-empty in the snapshot, diff-db reports it as agreeing
over a pair rather than as absent, and each of the two refusal requests fails if
the sheet it names is removed from the reasoning.

House rules

Tests run under cargo nextest; upstream's source is the checkout in
upstream/ at the pin. Generated artifacts are regenerated, never edited.
crates/jellyfin-harness/README.md says what a diff means and what an ignore
entry has to carry. A scenario request that cannot fail is not a check.

`cargo xtask reference provision` never sets `EnableTrickplayImageExtraction`. There is no such element in any of the three `options.xml`, and `SELECT COUNT(*) FROM TrickplayInfos` is `0` in both snapshots. Two consequences, found while landing #99 and #140. **The columns #99 landed are diffed against nothing.** `TrickplayInfos` is empty on both sides of every pair run, so the seven columns a client indexes with — width, height, tile counts, thumbnail count, interval and `Bandwidth` — agree by being absent. `Bandwidth` in particular is a pure function of the encoded tile's byte length, and its only coverage anywhere is `jellyfin_core::image::sheet_bandwidth`'s unit cases and the committed answer key in `crates/jellyfin-images/tests/sheet.rs`. **Two scenario requests refuse for a reason they do not name.** `crates/jellyfin-harness/scenarios/refusals.json:168-172` and `:188-192` — `trickplay-tiles-at-a-width-nothing-was-cut-at` and `trickplay-tile-past-the-last-sheet` — both refuse because the item has *no* sheets at all, not because the width is one nothing was cut at or the index is past the last sheet. They agree between the servers and they cannot fail for the reason they are named after. They pre-date #99; #99 is what made them visibly wrong. ## Where to start `xtask/src/reference/` is where `provision` decides the library options. Setting `EnableTrickplayImageExtraction` and `ExtractTrickplayImagesDuringLibraryScan` makes a bootstrap produce sheets, at which point both scenarios refuse for the reason they name and `TrickplayInfos` becomes a table with rows on both sides. The cost is a snapshot rebuild, which redraws the account and the server ids that scenarios and `config.toml` both name — so this wants to happen alongside the next rebuild rather than on its own. ## How to know it is done `TrickplayInfos` is non-empty in the snapshot, `diff-db` reports it as agreeing over a pair rather than as absent, and each of the two refusal requests fails if the sheet it names is removed from the reasoning. ### House rules Tests run under `cargo nextest`; upstream's source is the checkout in `upstream/` **at the pin**. Generated artifacts are regenerated, never edited. `crates/jellyfin-harness/README.md` says what a diff means and what an ignore entry has to carry. A scenario request that cannot fail is not a check.
Author
Collaborator

Two things for whoever grows the fixture, both of them from batch D and both of
the same shape as the trickplay gap this issue already names.

The snapshot has no picture of any kind. xtask reference provision
registers every library with empty ImageFetchers per type, which disables the
three in-box extractors — Screen Grabber, Embedded Image Extractor and the
audio one — along with the remote fetchers. So select count(*) from BaseItemImageInfos is 0 on the default snapshot, and neither server draws a
library tile, a genre collage or a poster there. That is deliberate and worth
keeping: a scan has to be a function of the files. But it means everything
batch D landed is measurable against a pair only on a library provisioned
with those fetchers on — register_enriched already demonstrates the
mechanism.

Nothing in the media carries a picture to extract. ffprobe over the
fixture's mp3, flac and mkv finds audio and video streams and no attached_pic
and no attachments. So even with the fetchers on, only the screen grabber has
anything to do. Measuring the other two takes a track muxed with cover art and
a container muxed with an attached picture — make-sample-media.sh is where
they would go, and the image records a digest of it, so xtask reference build
rebuilds when it changes.

Both are noted in reference/README.md beside the trickplay note. They cost
nothing extra if they ride along with the rebuild this issue already needs.

Two things for whoever grows the fixture, both of them from batch D and both of the same shape as the trickplay gap this issue already names. **The snapshot has no picture of any kind.** `xtask reference provision` registers every library with empty `ImageFetchers` per type, which disables the three in-box extractors — `Screen Grabber`, `Embedded Image Extractor` and the audio one — along with the remote fetchers. So `select count(*) from BaseItemImageInfos` is 0 on the default snapshot, and neither server draws a library tile, a genre collage or a poster there. That is deliberate and worth keeping: a scan has to be a function of the files. But it means everything batch D landed is measurable against a pair only on a library provisioned *with* those fetchers on — `register_enriched` already demonstrates the mechanism. **Nothing in the media carries a picture to extract.** `ffprobe` over the fixture's mp3, flac and mkv finds audio and video streams and no `attached_pic` and no attachments. So even with the fetchers on, only the screen grabber has anything to do. Measuring the other two takes a track muxed with cover art and a container muxed with an attached picture — `make-sample-media.sh` is where they would go, and the image records a digest of it, so `xtask reference build` rebuilds when it changes. Both are noted in `reference/README.md` beside the trickplay note. They cost nothing extra if they ride along with the rebuild this issue already needs.
Author
Collaborator

The provisioning half is landed in 9d3fd58 and 611fa42. The snapshot is not
rebuilt, so this stays open
, and the reason is below rather than "no time".

What landed

provision registers a new library with EnableTrickplayImageExtraction and
ExtractTrickplayImagesDuringLibraryScan. Both exist on upstream's
LibraryOptions and both are needed — the second is what the scan path reads,
the first is what RefreshTrickplayDataAsync and the RefreshTrickplayImages
task read. The empty ImageFetchers lists do not disable them:
TrickplayProvider is an ICustomMetadataProvider, which
CanRefreshMetadataForCache does not filter by that list.

It also asks for TrickplayOptions.ScanBehavior: Blocking. Upstream's default
is NonBlocking and TrickplayProvider.FetchInternal then fire-and-forgets
RefreshTrickplayDataAsync, while xtask's scan() waits only for the
RefreshLibrary task to go idle — so a snapshot cut after a provision would
have carried a row for Night Signal on a fast host and none on a slow one.
The fixture would have stopped being a function of the files, which is the
property reference/README.md opens by asserting.

Not on the enriched library: its clips are five seconds against a ten-second
interval, and upstream cuts no sheets for a file shorter than one.

Why the rebuild is not in this change

Three things, and the third is the one that matters most:

  1. It replaces reference/snapshots, which the main checkout and every worktree
    copy from. A reference instance was live on :8096 throughout.
  2. Done as a fresh provision it redraws the account and server ids, and twelve
    committed scenarios name the old account id — over a hundred occurrences.
    reference/README.md already says to restore-and-rescan instead.
  3. A rebuild is not only a rebuild. This server writes no sheets — it reads,
    serves and deletes them — so a snapshot carrying upstream's makes
    TrickplayInfos a table upstream has rows in and ours has none, which
    diff-db reports until an exemption says why. And the two refusal requests
    named after a width nothing was cut at and an index past the last sheet start
    being answered by upstream rather than refused — which is the point, and
    also two scenario requests to rewrite.

All three are now written down in reference/README.md beside the trickplay
note, so whoever does the rebuild meets them before rather than after.

On the two extras in the comment above

I drafted generators for the cover art and the attached picture and dropped
them. The audio cover probes cleanly as attached_pic, but the video attachment
came through as a video stream rather than an attachment — unlike the font
already in Test Pattern S02E02.mkv — and I cannot say what upstream's embedded
image extractor makes of either without a pair to run it against. Committing
them would be the same adds-only smell in a different place. They are still
worth doing with the rebuild; make-sample-media.sh is where.

The provisioning half is landed in 9d3fd58 and 611fa42. **The snapshot is not rebuilt, so this stays open**, and the reason is below rather than "no time". ## What landed `provision` registers a new library with `EnableTrickplayImageExtraction` and `ExtractTrickplayImagesDuringLibraryScan`. Both exist on upstream's `LibraryOptions` and both are needed — the second is what the scan path reads, the first is what `RefreshTrickplayDataAsync` and the `RefreshTrickplayImages` task read. The empty `ImageFetchers` lists do not disable them: `TrickplayProvider` is an `ICustomMetadataProvider`, which `CanRefreshMetadataForCache` does not filter by that list. It also asks for `TrickplayOptions.ScanBehavior: Blocking`. Upstream's default is `NonBlocking` and `TrickplayProvider.FetchInternal` then fire-and-forgets `RefreshTrickplayDataAsync`, while `xtask`'s `scan()` waits only for the `RefreshLibrary` task to go idle — so a snapshot cut after a provision would have carried a row for *Night Signal* on a fast host and none on a slow one. The fixture would have stopped being a function of the files, which is the property `reference/README.md` opens by asserting. Not on the enriched library: its clips are five seconds against a ten-second interval, and upstream cuts no sheets for a file shorter than one. ## Why the rebuild is not in this change Three things, and the third is the one that matters most: 1. It replaces `reference/snapshots`, which the main checkout and every worktree copy from. A reference instance was live on `:8096` throughout. 2. Done as a fresh provision it redraws the account and server ids, and twelve committed scenarios name the old account id — over a hundred occurrences. `reference/README.md` already says to restore-and-rescan instead. 3. **A rebuild is not only a rebuild.** This server writes no sheets — it reads, serves and deletes them — so a snapshot carrying upstream's makes `TrickplayInfos` a table upstream has rows in and ours has none, which `diff-db` reports until an exemption says why. And the two refusal requests named after a width nothing was cut at and an index past the last sheet start being *answered* by upstream rather than refused — which is the point, and also two scenario requests to rewrite. All three are now written down in `reference/README.md` beside the trickplay note, so whoever does the rebuild meets them before rather than after. ## On the two extras in the comment above I drafted generators for the cover art and the attached picture and dropped them. The audio cover probes cleanly as `attached_pic`, but the video attachment came through as a video stream rather than an attachment — unlike the font already in `Test Pattern S02E02.mkv` — and I cannot say what upstream's embedded image extractor makes of either without a pair to run it against. Committing them would be the same adds-only smell in a different place. They are still worth doing with the rebuild; `make-sample-media.sh` is where.
Author
Collaborator

Done. The provisioning half had already landed in 9d3fd58 — what was left is
that every snapshot on disk was cut before it, so TrickplayInfos was still
empty on both sides of every run.

Rebuilt in 113caba. The default snapshot now carries two sets of sheets:

ItemId                                 Width Height Tile ThumbnailCount Interval Bandwidth
6AF10317-BB89-65DE-8A75-9CE1186806A0   320   180    10x10  9            10000    888
A3B8431C-9390-59C6-7D97-5B5E4251268E   320   180    10x10  2            10000    407

The two requests now refuse for the reason they are named after. Measured
against the reference at the pin, on Night Signal, which has sheets at 320:

/Trickplay/320/tiles.m3u8   200 application/vnd.apple.mpegurl
/Trickplay/999/tiles.m3u8   404      — a width nothing was cut at
/Trickplay/320/0.jpg        200 image/jpeg
/Trickplay/320/9.jpg        404      — a sheet past the last

Before, all four were 404 for the same wrong reason. refusals.json replays
over the pair with no trickplay difference — 156 responses, 2 differences,
neither of them a trickplay route.

Two things the rebuild dragged in, both recorded here so the next one is not a
surprise:

  • The library on the host had to be re-laid. reference/var/media was from
    a build this tree no longer records, so its dates disagreed with the ones the
    container bakes, and scan_ingest's date comparison failed on 12 of 20 rows
    the moment the snapshot moved. Deleting the directory and letting up copy
    it again from the image put the two back in step; the suite is green.
  • A rebuild redraws the accounts, and fourteen scenarios name the
    jellyfin account's id. They are re-pointed in the same commit.

One difference the run turned up that is not this issue and not trickplay:
GET /MusicGenres/InstantMix naming no id answers 400 on both servers, with
a JSON body upstream and a plain-text one here. It is on a route absent.rs
answers for an excluded feature, and it is unrelated to anything changed here.

Done. The provisioning half had already landed in `9d3fd58` — what was left is that every snapshot on disk was cut before it, so `TrickplayInfos` was still empty on both sides of every run. Rebuilt in `113caba`. The `default` snapshot now carries two sets of sheets: ``` ItemId Width Height Tile ThumbnailCount Interval Bandwidth 6AF10317-BB89-65DE-8A75-9CE1186806A0 320 180 10x10 9 10000 888 A3B8431C-9390-59C6-7D97-5B5E4251268E 320 180 10x10 2 10000 407 ``` **The two requests now refuse for the reason they are named after.** Measured against the reference at the pin, on *Night Signal*, which has sheets at 320: ``` /Trickplay/320/tiles.m3u8 200 application/vnd.apple.mpegurl /Trickplay/999/tiles.m3u8 404 — a width nothing was cut at /Trickplay/320/0.jpg 200 image/jpeg /Trickplay/320/9.jpg 404 — a sheet past the last ``` Before, all four were `404` for the same wrong reason. `refusals.json` replays over the pair with **no trickplay difference** — 156 responses, 2 differences, neither of them a trickplay route. Two things the rebuild dragged in, both recorded here so the next one is not a surprise: - **The library on the host had to be re-laid.** `reference/var/media` was from a build this tree no longer records, so its dates disagreed with the ones the container bakes, and `scan_ingest`'s date comparison failed on 12 of 20 rows the moment the snapshot moved. Deleting the directory and letting `up` copy it again from the image put the two back in step; the suite is green. - **A rebuild redraws the accounts**, and fourteen scenarios name the `jellyfin` account's id. They are re-pointed in the same commit. One difference the run turned up that is not this issue and not trickplay: `GET /MusicGenres/InstantMix` naming no id answers `400` on both servers, with a JSON body upstream and a plain-text one here. It is on a route `absent.rs` answers for an excluded feature, and it is unrelated to anything changed here.
Sign in to join this conversation.
No description provided.