c633b89ad970d5b40fad82f859fff4bbf7b02dc4
Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Successful in 9s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 1m12s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 51s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 1m25s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 7s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m56s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 15m37s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 19m3s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
JellyfinMusicVideoLibraryScanner.UpdateMetadata copied only scalar fields for an EXISTING music video, so genres/tags/studios/artists edited in Jellyfin never reached ErsatzTV — the update path silently dropped every child collection (only the Add path ever persisted them). Root cause is inherited from upstream: unlike the movie/episode Jellyfin path, which reconciles collections inside the tracked repository GetOrAdd, MusicVideoRepository.GetOrAdd is AsNoTracking and the scanner never reconciled the collections itself. Fix mirrors PlexMovieLibraryScanner.UpdateMetadata's remove-stale + add-new idiom, reconciling exactly the collections that BOTH the Add path persists AND GetOrAdd eager-loads: Genres, Tags, Studios, Artists. Guids (add-persisted but not eager-loaded — would duplicate) and Directors (eager-loaded but not add-persisted for music videos) are deliberately out of scope. Movie/Episode paths do NOT have this gap (they reconcile in the tracked repo GetOrAdd), so no separate fix is needed there. Test is an interaction test (substituted repos, canned existing item) verifying the exact reconcile calls; proven non-vacuous. The real-DB double-scan approach can't drive this: the in-memory harness shares one SQLite connection across contexts and mid-scan GetOrAdd's First()-nav path predicate mis-resolves once the existing item carries metadata children — a harness-only quirk (prod uses per-context pooled connections). fixes #497 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ci(406): disable swap on the capped mysql; fix peak-step placement and failure mode [decisions-edit]
fix(409): report never-scanned LastScan as null for API/MCP parity (migration + read coercion) (#459)
fix(409): report never-scanned LastScan as null for API/MCP parity (migration + read coercion) (#459)
Merge pull request 'chore(deps): update dependency humanizer.core to 3.0.10' (#328) from renovate/humanizer-monorepo into main
ErsatzTV
ErsatzTV lets you transform your media library into a personalized, live TV experience - complete with EPG, channel scheduling, and seamless streaming to all your devices. Rediscover your content, your way.
How It Works
- Install ErsatzTV: Download and set up the server on your system.
- Add Your Media: Connect your media libraries and collections.
- Create Channels: Design and schedule your own live channels.
- Stream Anywhere: Watch on any device with IPTV and EPG support.
Key Features
- Custom channels: Create and schedule your own live TV channels.
- IPTV & EPG: Stream with IPTV and Electronic Program Guide support.
- Hardware Transcoding: High-performance streaming with hardware acceleration (NVENC, QSV, VAAPI, AMF, VideoToolbox)
- Media Server Integration: Connect Plex, Jellyfin, Emby and more.
- Music & Subtitles: Mix music videos and enjoy subtitle support.
- Open Source: Free, open, and community-driven project.
Documentation
Documentation is available at ersatztv.org.
License
This project is inspired by pseudotv-plex and the dizquetv fork and is released under the zlib license.
