Jellyfin music videos imported with no media streams — channels 402/403 fail to play #924

Closed
opened 2026-10-21 22:16:36 +02:00 by timothy · 5 comments
Owner

Symptom

Channels 402 (Hip Hop & Soul) and 403 (Retro TV Music) won't play. Every HLS session start fails with:

System.InvalidOperationException: Sequence contains no matching element
   at ErsatzTV.Core.FFmpeg.FFmpegStreamSelector.SelectVideoStream(MediaVersion version) in FFmpegStreamSelector.cs:line 42
   at ErsatzTV.Core.FFmpeg.FFmpegLibraryProcessService.ForPlayoutItem(...)
   at ...GetPlayoutItemProcessByChannelNumberHandler.GetProcess(...) line 440

Other channels (e.g. 100) are fine.

Evidence (prod DB on jazz, 2026-10-21)

  • Library 8 "Music Videos" (jellyfin://e2c00f297a5f80af390f52f72e782147): 1520/1520 music-video MediaVersion rows have zero MediaStream rows of any kind, Width=0, Height=0, DateUpdated=0001-01-01. Duration is populated.
  • Movies: 0 missing. Episodes: 4 missing.
  • The 2026-08-30 backup (~/downloadswarm/ersatztv-backups/20260830T152422Z) shows the same 1520/0, so the problem has existed since at least then. It was not caused by today's 18:08 UTC scan.

Root cause (code)

  • JellyfinApiClient.ProjectToMusicVideo builds the version with Streams = [] (line ~749). The movie projection does the same.
  • MediaServerMovieLibraryScanner.UpdateStatistics handles this: when the head version has no streams, it calls GetMediaServerStatistics, which fetches PlaybackInfo (and ServerReturnsStatisticsWithMetadata covers the other case).
  • MediaServerMusicVideoLibraryScanner.UpdateStatistics (line ~318) just writes incoming.GetHeadVersion(), which is the empty projection, with no statistics fetch. Music videos therefore never get streams. The scanner was introduced in #496 (2026-07-25).

Fix direction

  • Port the movie scanner's statistics path (GetMediaServerStatistics / full metadata+statistics when Streams.Count == 0) into the music-video scanner, then deep-scan library 8.
  • Optionally, make SelectVideoStream fail with a clear error (or skip the item) instead of a bare First() throw.

Discovered from server-management while debugging "can't watch Live TV".

Done-when

  • A media-server music-video scan populates MediaStream rows and Width/Height on the head MediaVersion when the projection carries no streams (statistics fetched as the movie scanner does), for both new and updated items
  • The other media-server scanners (other-video, TV episodes) are checked for the same gap; any gap found is fixed or filed
  • NUnit tests pin the music-video statistics path and red when the fix alone is reverted
  • FFmpegStreamSelector.SelectVideoStream on a version with no video stream fails with a clear, actionable error naming the media item, not a bare Sequence contains no matching element; pinned by a test
  • Local gate green (dotnet build + ErsatzTV.Tests, ErsatzTV.Core.Tests, ErsatzTV.Scanner.Tests, ErsatzTV.FFmpeg.Tests, ErsatzTV.Architecture.Tests; BOM + format check)
  • Follow-up for the prod deep-scan of library 8 handed to media-management (channel operation, not run from this repo)
  • Adversarial review passed
## Symptom Channels **402 (Hip Hop & Soul)** and **403 (Retro TV Music)** won't play. Every HLS session start fails with: ``` System.InvalidOperationException: Sequence contains no matching element at ErsatzTV.Core.FFmpeg.FFmpegStreamSelector.SelectVideoStream(MediaVersion version) in FFmpegStreamSelector.cs:line 42 at ErsatzTV.Core.FFmpeg.FFmpegLibraryProcessService.ForPlayoutItem(...) at ...GetPlayoutItemProcessByChannelNumberHandler.GetProcess(...) line 440 ``` Other channels (e.g. 100) are fine. ## Evidence (prod DB on jazz, 2026-10-21) - Library 8 "Music Videos" (`jellyfin://e2c00f297a5f80af390f52f72e782147`): **1520/1520** music-video `MediaVersion` rows have **zero `MediaStream` rows** of any kind, `Width=0`, `Height=0`, `DateUpdated=0001-01-01`. Duration is populated. - Movies: 0 missing. Episodes: 4 missing. - The 2026-08-30 backup (`~/downloadswarm/ersatztv-backups/20260830T152422Z`) shows the same 1520/0, so the problem has existed since at least then. It was not caused by today's 18:08 UTC scan. ## Root cause (code) - `JellyfinApiClient.ProjectToMusicVideo` builds the version with `Streams = []` (line ~749). The movie projection does the same. - `MediaServerMovieLibraryScanner.UpdateStatistics` handles this: when the head version has no streams, it calls `GetMediaServerStatistics`, which fetches PlaybackInfo (and `ServerReturnsStatisticsWithMetadata` covers the other case). - `MediaServerMusicVideoLibraryScanner.UpdateStatistics` (line ~318) just writes `incoming.GetHeadVersion()`, which is the empty projection, with no statistics fetch. Music videos therefore never get streams. The scanner was introduced in #496 (2026-07-25). ## Fix direction - Port the movie scanner's statistics path (`GetMediaServerStatistics` / full metadata+statistics when `Streams.Count == 0`) into the music-video scanner, then deep-scan library 8. - Optionally, make `SelectVideoStream` fail with a clear error (or skip the item) instead of a bare `First()` throw. Discovered from server-management while debugging "can't watch Live TV". ## Done-when - [x] A media-server music-video scan populates `MediaStream` rows and `Width`/`Height` on the head `MediaVersion` when the projection carries no streams (statistics fetched as the movie scanner does), for both new and updated items - [x] The other media-server scanners (other-video, TV episodes) are checked for the same gap; any gap found is fixed or filed - [x] NUnit tests pin the music-video statistics path and red when the fix alone is reverted - [x] `FFmpegStreamSelector.SelectVideoStream` on a version with no video stream fails with a clear, actionable error naming the media item, not a bare `Sequence contains no matching element`; pinned by a test - [x] Local gate green (dotnet build + ErsatzTV.Tests, ErsatzTV.Core.Tests, ErsatzTV.Scanner.Tests, ErsatzTV.FFmpeg.Tests, ErsatzTV.Architecture.Tests; BOM + format check) - [x] Follow-up for the prod deep-scan of library 8 handed to media-management (channel operation, not run from this repo) - [x] Adversarial review passed
timothy added the in-progress label 2026-10-21 22:18:11 +02:00
Author
Owner

Claiming: branch 924-musicvideo-stream-stats, worktree /Users/timothy/orca/workspaces/ersatztv/wt-924 (orchestrated session 99d1dedb). Four-way check clean 2026-10-21: no open PR referencing #924, no remote branch matching *924*, no prior claim comment.

Claiming: branch `924-musicvideo-stream-stats`, worktree `/Users/timothy/orca/workspaces/ersatztv/wt-924` (orchestrated session 99d1dedb). Four-way check clean 2026-10-21: no open PR referencing #924, no remote branch matching `*924*`, no prior claim comment.
Author
Owner

Correction to the issue body: on prod the Jellyfin "Music Videos" ErsatzTV library is id 16, not 8. There is no Library row with Id 8; /api/v1/media-sources lists Jellyfin "Music Videos" as library 16. The count is unchanged at 1520/1520 MusicVideo MediaVersions with zero MediaStream rows (measured by media-management on 2026-10-21 against image sha256:c724d0ef…). The post-deploy repair is POST /api/v1/libraries/16/scan?deep=true. Any reference to "library 8" in the branch or closing record should read 16.

Correction to the issue body: on prod the Jellyfin "Music Videos" ErsatzTV library is **id 16**, not 8. There is no Library row with Id 8; `/api/v1/media-sources` lists Jellyfin "Music Videos" as library 16. The count is unchanged at 1520/1520 MusicVideo MediaVersions with zero MediaStream rows (measured by media-management on 2026-10-21 against image `sha256:c724d0ef…`). The post-deploy repair is `POST /api/v1/libraries/16/scan?deep=true`. Any reference to "library 8" in the branch or closing record should read 16.
Author
Owner

Closing record

PR: http://192.168.1.95:3000/timothy/ersatztv/pulls/928 (head 9d3989140f). Not closed here; the merge closes it.

What changed: music-video scanner now reads statistics from the server (Jellyfin playback info) under the movie scanner's refresh condition and writes nothing when the server cannot answer; SelectVideoStream names the version, file and remediation. Decision record scan.mediaserver-statistics-from-server.
Other scanners: other-video (Plex only, ServerReturnsStatisticsWithMetadata) and TV (Jellyfin/Emby override, Plex with metadata) have no gap.
Follow-ups: prod deep-scan of library 16 -> timothy/media-management#88; etag-on-failed-statistics residual -> ersatztv#927.

Done-when evidence

  • Music-video scan populates streams + width/height (new and updated): dotnet test ErsatzTV.Tests --filter JellyfinMusicVideoLibraryScannerTests (ErsatzTV.Tests 2142 passed, 0 failed).
  • Other scanners checked: commit ec862ce95 message ("Other-video ... and TV ... have no such gap") and the decision record.
  • Tests red when fix reverted: tests in ErsatzTV.Tests/Integration/JellyfinMusicVideoLibraryScannerTests.cs; clause sweep recorded in commit f90d9ad24.
  • SelectVideoStream clear error: dotnet test ErsatzTV.Core.Tests --filter FFmpegStreamSelectorTests (719 passed, 0 failed).
  • Local gate: dotnet build 0 errors; Tests 2142/Core 719/Scanner 1504/FFmpeg 83/Architecture 7 all 0 failed; BOM check (od -A n -t x1 -N 3) shows 75 73 69 on all 5 touched .cs; dotnet format whitespace --verify-no-changes --include clean; check-doc-narrative 0 warnings.
  • Prod deep-scan follow-up handed off: timothy/media-management#88.
  • Adversarial review: round 1, 2 lenses, 0 blocking / 0 should-fix / 5 nit, clean (listed in PR #928 body under REVIEW HISTORY).
## Closing record PR: http://192.168.1.95:3000/timothy/ersatztv/pulls/928 (head 9d3989140f895e50f967599245b6676b3e65187b). Not closed here; the merge closes it. **What changed:** music-video scanner now reads statistics from the server (Jellyfin playback info) under the movie scanner's refresh condition and writes nothing when the server cannot answer; SelectVideoStream names the version, file and remediation. Decision record `scan.mediaserver-statistics-from-server`. **Other scanners:** other-video (Plex only, ServerReturnsStatisticsWithMetadata) and TV (Jellyfin/Emby override, Plex with metadata) have no gap. **Follow-ups:** prod deep-scan of library 16 -> timothy/media-management#88; etag-on-failed-statistics residual -> ersatztv#927. ### Done-when evidence - Music-video scan populates streams + width/height (new and updated): `dotnet test ErsatzTV.Tests --filter JellyfinMusicVideoLibraryScannerTests` (ErsatzTV.Tests 2142 passed, 0 failed). - Other scanners checked: commit ec862ce95 message ("Other-video ... and TV ... have no such gap") and the decision record. - Tests red when fix reverted: tests in ErsatzTV.Tests/Integration/JellyfinMusicVideoLibraryScannerTests.cs; clause sweep recorded in commit f90d9ad24. - SelectVideoStream clear error: `dotnet test ErsatzTV.Core.Tests --filter FFmpegStreamSelectorTests` (719 passed, 0 failed). - Local gate: dotnet build 0 errors; Tests 2142/Core 719/Scanner 1504/FFmpeg 83/Architecture 7 all 0 failed; BOM check (`od -A n -t x1 -N 3`) shows 75 73 69 on all 5 touched .cs; `dotnet format whitespace --verify-no-changes --include` clean; check-doc-narrative 0 warnings. - Prod deep-scan follow-up handed off: timothy/media-management#88. - Adversarial review: round 1, 2 lenses, 0 blocking / 0 should-fix / 5 nit, clean (listed in PR #928 body under REVIEW HISTORY).
timothy removed the in-progress label 2026-10-22 08:24:43 +02:00
Author
Owner

Correction to the issue body: the prod "Music Videos" library is Library.Id 16, not 8. The 8 was the LibraryPath.Id (path jellyfin://e2c00f297a5f80af390f52f72e782147), which the diagnostic query printed under an Id column. Use library 16 for the post-fix deep scan (POST /api/v1/libraries/16/scan?deep=true). As of 2026-10-27, library 16 holds 1419 active music videos plus 101 dedup removals (1520 rows total, matching the original count).

Correction to the issue body: the prod "Music Videos" library is **Library.Id 16**, not 8. The 8 was the `LibraryPath.Id` (path `jellyfin://e2c00f297a5f80af390f52f72e782147`), which the diagnostic query printed under an `Id` column. Use library 16 for the post-fix deep scan (`POST /api/v1/libraries/16/scan?deep=true`). As of 2026-10-27, library 16 holds 1419 active music videos plus 101 dedup removals (1520 rows total, matching the original count).
Author
Owner

Live outcome: v26.16.0 on prod, 2026-10-27

  • Deployed via Komodo stack jazz-media (server-management): the running ersatztv image is sha256:6d698162a3cb5194f110b36ae5b02666472afb8187965bb6f45a2d8e04789bc0 = :26.16.0, on .NET 10.0.14. The pre-deploy backup and migration smoke passed.
  • Deep scan POST /api/v1/libraries/16/scan?deep=true (media-management, 19:18Z). Library 16 State=0 music-video versions with zero MediaStream rows: 1419 → 0, and none lack a video stream. The only stream-less rows left are the 101 soft-trashed duplicates (State=1), as expected.
  • Artists: 0 State=0 music videos with no artist or "Unknown Artist" (was 767), after media-management re-tagged the files and ErsatzTV rescanned.
  • Playback: channels 402 and 403 each serve a 1920x1080 avc1.4D4028 HLS variant whose first segment returns 200 video/mp2t (~1.1 MB). Re-measured independently from ersatztv after the media-management report.
## Live outcome: v26.16.0 on prod, 2026-10-27 - **Deployed** via Komodo stack `jazz-media` (server-management): the running `ersatztv` image is `sha256:6d698162a3cb5194f110b36ae5b02666472afb8187965bb6f45a2d8e04789bc0` = `:26.16.0`, on .NET 10.0.14. The pre-deploy backup and migration smoke passed. - **Deep scan** `POST /api/v1/libraries/16/scan?deep=true` (media-management, 19:18Z). Library 16 State=0 music-video versions with zero `MediaStream` rows: **1419 → 0**, and none lack a video stream. The only stream-less rows left are the 101 soft-trashed duplicates (State=1), as expected. - **Artists:** 0 State=0 music videos with no artist or "Unknown Artist" (was 767), after media-management re-tagged the files and ErsatzTV rescanned. - **Playback:** channels 402 and 403 each serve a 1920x1080 `avc1.4D4028` HLS variant whose first segment returns `200 video/mp2t` (~1.1 MB). Re-measured independently from ersatztv after the media-management report.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: timothy/ersatztv#924