fix(924): the no-video-stream error allows for a file that really has none, and the record names library 16
review-verdict/h10 Review-verdict: MERGEABLE @ 304040d (base: main)
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
PR Gates / Docs update reminder (pull_request) Successful in 13s
PR Gates / decisions lifecycle (pull_request) Successful in 16s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 10s
Review verdict / Set review-verdict status (pull_request_target) Successful in 7s
PR Gates / Script lint and tests (ruff + pytest) (pull_request) Successful in 19m44s
Build ErsatzTV Image / CI toolchain image resolves (pull_request) Successful in 5s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m6s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 7m19s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 7s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 40s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m5s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped

Review round 1 nits:
- SelectVideoStream's message stated "its media statistics have not been
  read" as fact. A file that genuinely has no video stream (an audio-only
  local file, or a Jellyfin item whose playback info has no video, where
  ProjectToMediaVersion returns None) would send an operator round a
  deep-scan loop that can never succeed. The message now covers both cases.
- scan.mediaserver-statistics-from-server said "library 8". Prod's Jellyfin
  "Music Videos" library is id 16; no Library row has Id 8 (measured by
  media-management, 2026-10-21).
- The ersatztv skill's symptom note named "images before the #924 fix"
  instead of the release that carries it (v26.16.0).

Refs #924
Decisions-Edit: yes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: 99d1dedb-f027-4e05-baab-2111f3a7d951
This commit is contained in:
2026-10-21 23:21:50 +02:00
co-authored by Claude Opus 5.5
parent 9d3989140f
commit 304040de4d
3 changed files with 7 additions and 6 deletions
+3 -3
View File
@@ -472,11 +472,11 @@ docker start ersatztv
### Common Mistakes (check every time)
- **Playout not building**: Three things must all be correct: (1) `ProgramScheduleOneItem` row exists for the schedule item, (2) `PlaybackOrder=3` (Shuffle), (3) `ScheduleKind=1` on Playout. Missing any one results in 0 playout items — this is the most common issue.
- **Collection queries fail**: `CollectionItem` has no `Id` column — use `rowid` for counting. Content lookup goes through `MediaVersion.MovieId` → `MediaFile.Path` (not a generic MediaItemId join).
- **Channel won't start — "has no video stream on record"** (images before the ersatztv#924 fix log a bare
- **Channel won't start — "has no video stream on record"** (images before v26.16.0, which carries the ersatztv#924 fix, log a bare
`Sequence contains no matching element` at `FFmpegStreamSelector.SelectVideoStream`): the playing item's
`MediaVersion` has no `MediaStream` rows. Deep-scan its library — `POST /api/v1/libraries/{id}/scan?deep=true`;
a regular scan skips items whose server etag is unchanged and cannot heal them. Jellyfin music videos scanned
before that fix were all stored without streams (`scan.mediaserver-statistics-from-server`).
a regular scan skips items whose server etag is unchanged and cannot heal them. If the file itself has no video
stream, no scan heals it. Jellyfin music videos scanned before that fix were all stored without streams (`scan.mediaserver-statistics-from-server`).
- **Channel logos forgotten**: After creating a channel, add an Artwork row (ArtworkKind=2) + logo file, then run `fix_logos.py` to push to Jellyfin. Without this, the channel shows no logo in the EPG.
- **Playout reset required**: After any schedule/collection change, run `POST /api/v1/channels/{id}/playout/reset`. Wait 5-10s for the playout to build before verifying item count.
+3 -2
View File
@@ -58,9 +58,10 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector
return Task.FromException<MediaStream>(
new InvalidOperationException(
$"Media version {version.Id} ({path}) has no video stream on record, so it cannot be played. " +
"Its media statistics have not been read: rescan the item's library to read them (for a " +
"If its media statistics were never read, rescan the item's library to read them (for a " +
"Jellyfin/Emby/Plex library, a deep scan, since a regular scan skips items the server reports " +
"unchanged)."));
"unchanged). If the file itself has no video stream, a rescan cannot help: remove it from " +
"video channels."));
}
public async Task<Option<MediaStream>> SelectAudioStream(
@@ -14,7 +14,7 @@ The Jellyfin and Emby library-items projections build every movie, episode and m
`Streams = []` and no dimensions; only the per-item playback info carries them. The movie and TV scanners fetch
statistics when the item is new, changed, deep-scanned or has no stored streams. The music-video scanner wrote
the projection's version instead, so every Jellyfin music video was stored without streams and
`FFmpegStreamSelector.SelectVideoStream` threw on play. Measured on prod 2026-10-21 (#924): library 8, 1520 of
`FFmpegStreamSelector.SelectVideoStream` threw on play. Measured on prod 2026-10-21 (#924): the Jellyfin "Music Videos" library (id 16), 1520 of
1520 music-video versions with zero `MediaStream` rows (movies 0, episodes 4).
- **Writing the projection is worse than writing nothing.** `IMetadataRepository.UpdateStatistics` reconciles