* fix(jellycompat): resolve item duration probed-first with runtime fallback
Jellyfin-protocol clients received no runtime at all for items whose catalog
runtime is 0. RunTimeTicks is omitempty, so a zero value is dropped from the
JSON entirely rather than sent as 0, and strict clients (Infuse) abandon
playback on those items. On the production deployment 5,245 movies have
media_items.runtime = 0 while 5,239 of them have a correct probed
media_files.duration.
Resolve duration at read time the way /api/v1 already does: probed file
duration first, catalog runtime as the fallback. The item row is deliberately
not backfilled — one item can have several versions of different lengths, so
per-file data does not belong there.
- scanner: FirstDurationsByContentIDs / FirstDurationsByEpisodeIDs, batched
lookups using the same "first live file with duration > 0, ordered by id"
rule as the v1 API's contentDurationSeconds. The episode_id IS NULL guard on
the content-id query is load-bearing: every episode file carries its series'
content_id, so without it a series row would report an episode's duration.
- catalog: optional batchDurationFetcher extension on DetailService, following
the existing extraFileFetcher pattern so test fakes need no changes.
Nil-receiver safe and fail-soft — a failed lookup logs and degrades to the
catalog runtime rather than failing the page.
- jellycompat: DurationSeconds on upstreamListItem/upstreamEpisode, a shared
runtimeTicks resolver, and fillListItemDurations wired into the nine page
producers. Fixes the three sites that had no fallback (itemFromList,
episodeFromUpstream, HandleSearchHints); the detail and PlaybackInfo paths
were already correct.
This is additive within the v1 rules — it populates a field that was
previously omitted. No field is renamed, removed, retyped, or repurposed.
* fix(jellycompat): avoid duplicate duration lookups
---------
Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com>