Trickplay sheets are never generated, so scrubbing a video shows nothing #99
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
/Videos/{id}/Trickplay/{width}/tiles.m3u8and the.jpgroute beside it are answered and serve whateverTrickplayInfosholds (crates/jellyfin-db/src/items/children.rsandcrates/jellyfin-db/src/items/repository.rs) — and nothing in the workspace ever inserts a row. Both routes therefore serve a database somebody else filled, which is what M7 said and is not an install.Upstream has three pieces:
TrickplayProvider(a custom metadata provider that fires on refresh),TrickplayImagesTask(keyRefreshTrickplayImages, daily), andTrickplayMoveImagesTask(keyMoveTrickplayImages), all inMediaBrowser.Providers/Trickplay/. It decodes at an interval, tiles the frames into sheets, and records width, height, tile counts and interval per resolution inTrickplayInfos.This is the most visible missing artefact for a viewer: hovering the scrub bar in jellyfin-web shows a preview against upstream and nothing here. The generation is ffmpeg work the encoding crate is already built to drive, and the row shape is fixed by the schema.
Note the interaction with #92 and with the scheduler: this is expensive per file and has to be resumable, which is why upstream makes it a task rather than part of a scan.
Where to start
The read side is done and empty:
crates/jellyfin-http/src/routes/streaming/trickplay.rsserves the playlist and the sheets, and
crates/jellyfin-db/src/items/children.rsandcrates/jellyfin-db/src/items/repository.rsreads
TrickplayInfos. Nothing inserts.Upstream is three files in
MediaBrowser.Providers/Trickplay/:TrickplayProvider(fires on refresh),
TrickplayImagesTask(keyRefreshTrickplayImages, daily)and
TrickplayMoveImagesTask(keyMoveTrickplayImages). Take the interval,the widths, the tile grid and the "where do the sheets live" rules from there —
each of them is a number a client reads back out of
TrickplayInfos, soinventing one produces a sheet no player can index into.
The decode-and-tile work belongs with the image code (
crates/jellyfin-images,which already carries the resampler ported for the blurhash) driven by
crates/jellyfin-ffmpeg.How to know it is done
library here, and
diff-dbTrickplayInfos: same widths, heights, tile countsand interval per resolution.
compares the pictures a provider won.
preview. That is the thing a viewer notices and no diff will tell you.
House rules
CLAUDE.mdis the short version and these are the parts that bite: tests rununder
cargo nextest; upstream's source is the checkout inupstream/at thepin (
v12.0-rc5,6d501ba4) and not the clone beside the repository;generated files are regenerated rather than edited; a comment says what the code
does, not how it got there; and
Dnreferences stay out of code and comments.ARCHITECTURE.mddecides which crate a thing goes in and which dependency edgesare illegal,
DECISIONS.mdsays why a rule exists.The reference pair is
cargo xtask reference up(andreference provision,which is idempotent); a run is
cargo run -p jellyfin-harness -- replay --upstream URL --ours URL --scenario FILE --out DIR,with
diff-dbfor the two databases.crates/jellyfin-harness/README.mdsayswhat a diff means and what an ignore entry has to carry — every entry needs a
reason and an expiry milestone, and an adds-only history is the smell the risk
table names.
Not implemented, and here is the measured reason plus a split. Everything below was measured by running upstream's
RefreshTrickplayImageson the fixture.Bandwidthis a pure function of the encoded tile's byte length. A one-byte difference in the JPEG changes the row. Upstream encodes the tile through SkiaSharp at quality 90, so matching that row means a libjpeg-turbo-faithful JPEG encoder —crates/jellyfin-images/src/jpeg.rsis the comparable decoder and runs to 1,804 lines, andcollage.rsalready records this project's position that a drawn picture is not upstream's bytes.Something never measured before was confirmed on the way: our existing trickplay read path serves upstream's real sheets correctly — same playlist with the api key masked, and a byte-identical tile, on the
<data>/data/trickplay/<xx>/<hyphenated guid>/<width> - <tw>x<th>/<n>.jpgconvention.The split
GetInputArgumentandGetVideoProcessingFilterParam, both already ported), tiling, theTrickplayInfoswrite,RefreshTrickplayImages, andExtractTrickplayImagesDuringLibraryScan. Everything matches exceptBandwidth, which needs an ignore entry expiring on (2). This issue.jellyfin-images— what makes a tile byte-identical and retires that entry. Filed separately; it is the large one.MoveTrickplayImagesandDiscoverExistingTrickplayAsync— moving sheets between the media and data directories, and cataloguing user-placed ones with upstream's interval-snapping arithmetic. Independent of both.Doing (1) with an argued ignore entry is the honest path: a client gets working scrub previews, and the one row that cannot match says why and when it will.
Done, on
main. Split (1) of the comment above landed; split (2) landed too andis #140, so there is no
Bandwidthignore entry — it was opened in7f31074and deleted in
3cd112d, anddb-ignore-list.jsonis byte-for-byte unchangedacross the batch.
What landed
crates/jellyfin-ffmpeg/src/intervals.rs—ExtractVideoImagesOnIntervalAcceleratedon its software path, through the already-ported
input_argumentandvideo_processing_filter_param, with thesetptsin front of the rate filter.crates/jellyfin-images/src/trickplay.rs—SkiaEncoder.CreateTrickplayTile:the grid, the black padding of a short sheet, and the refusal when a frame is
not the width asked for.
crates/jellyfin-server/src/artefacts.rs— the task body: page the videos ofevery library that asks, the
CanGenerateTrickplayguards, cut, tile, file,prune the widths nothing is configured at, and forget the sheets of a library
that stopped asking.
Trickplayconfig section on upstream's defaults,LibraryConfig::extract_trickplay_images,ItemWriter::save_trickplay/forget_trickplay, andKind::TrickplayImages.The numbers
The command line builds character for character against what upstream logged:
The row, from a real run of each server over a
cp -acopy of thedefaultsnapshot — upstream in its own container from the pinned image on
:8099, ourson
:8098:The sheet is
cmp-identical at 110,967 bytes, at<data>/data/trickplay/6a/6af10317-…/320 - 10x10/0.jpgon both. Over HTTP theplaylist matches with the key masked and
/Videos/{id}/Trickplay/320/0.jpgreturns the same bytes from each. The task id
64f5f44cd30dc273cb9890205473bbccfalls out ofGuid::hashed("MediaBrowser.Providers.Trickplay.TrickplayImagesTask")unchangedand is asserted in the route test.
What the review round changed
the sibling chapter-image sweep already did and the way upstream does twice.
at a time. Upstream throws on the same condition; without it a remux or an
MPEG-TS capture yields ~130k JPEGs for a 90-minute film and a row claiming
ThumbnailCount=130000.(
TrickplayManager.cs:360-366). Measured: generate at[320], move theconfiguration to
[480], re-run — the database holds320|180|9|888and480|270|9|1554, both folders survive, and the 320 tile still answers200where it used to 404.
IsShortcutandIsPlaceHolderguards added, and the runtime guard moved ontothe raw interval where upstream has it, before the clamp to 1000.
Intervals::cuttakes aCancellationand races it against the deadline, soa cancelled task no longer waits up to an hour on one file;
stderris keptand logged on a non-zero exit.
Two guards were deliberately left out, both because they cannot fire here:
SourceTypes = [Library]is dead upstream at this pin — eight callers setInternalItemsQuery.SourceTypesand nothing reads it; there is no reference inBaseItemRepository.csorBaseItemRepository.TranslateQuery.csand noSourceTypecolumn onBaseItemEntity.IsCompleteMedianeeds a live-TVrecordings manager this server does not have, and its channel branch needs
channel items nothing files. If live TV lands, this is one of the places that
grows a guard.
Filed forward
MoveTrickplayImagesandDiscoverExistingTrickplayAsync, split (3)above, untouched.
by any harness run and two refusal scenarios refuse for a reason they do not
name.