Commit Graph
19 Commits
Author SHA1 Message Date
timothy 64decd492e fix(496): give music videos a per-library server identity; itemId diff + soft trash
Music videos carried no server identity, so JellyfinMusicVideoLibraryScanner had to
reconcile by a (LibraryPathId, path) diff and HARD-delete the remainder. A file served
by two libraries with overlapping local paths is a single row owned by whichever library
scanned it first, so that owner's sweep destroyed a row another library still served —
taking collection membership and playout references with it, irreversibly.

This is #494's deferred "option 2":

- New JellyfinMusicVideo : MusicVideo (ItemId/Etag), mirroring JellyfinMovie — TPT table,
  varchar(36), ItemId index. Dual-provider migration Add_JellyfinMusicVideo.
- New IMediaServerMusicVideoRepository + JellyfinMusicVideoRepository: itemId-keyed
  existing-set/lookup and Flag{Normal,Unavailable,FileNotFound} seams, all scoped per
  library via LibraryPath.LibraryId.
- New MediaServerMusicVideoLibraryScanner base; JellyfinMusicVideoLibraryScanner folds
  onto it and keeps the #177/#488/#497/#500 metadata-reconcile logic verbatim.
- The sweep now soft-trashes (FileNotFound) instead of deleting, so removal is reversible
  and EmptyTrash-governed. DeleteEmptyArtists consequently no longer fires from a sweep.
- Pre-identity rows are ADOPTED in place: the identity row is inserted against the same
  MediaItem id, scoped to the scanned library's own LibraryPath, so collection membership
  survives and a local/second-library row is never hijacked.
- AddMusicVideo normalizes Path/PathHash to the path-REPLACED local path; the projection
  fills them from the server-reported path, which would break every later PathHash lookup.

Docs: scan.musicvideo-reconciliation relocated to docs/decisions/archive/scan.md as
superseded; new active record scan.musicvideo-server-identity.

fixes #496
2026-07-25 17:20:53 +02:00
timothy 7cc12881de docs(484): correct the counted-enumeration counts on the api client interfaces
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 16s
PR Gates / Docs update reminder (pull_request) Successful in 16s
PR Gates / decisions lifecycle (pull_request) Successful in 21s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m7s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 9s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 13s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 17m26s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 22m17s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Review follow-up. The comments still said three (Jellyfin) / two (Emby)
library-level enumerations after the nested season and episode ones were
counted, which understates the reach of the very safety property this
branch establishes -- a future auditor reading them would conclude the
nested sweeps are unprotected.
2026-07-25 16:36:35 +02:00
timothy 6f4497e1ce fix(484): guard the nested TV season and episode sweeps against projection failures
Review finding 1 (blocking). ScanSeasons' FlagFileNotFoundSeasons and ScanEpisodes'
FlagFileNotFoundEpisodes had no guard at all — neither #477's nor #484's — so ProjectToSeason /
ProjectToEpisode returning Failed() was computed and discarded.

#477 scoped those out because "the blast radius is one show's seasons / one season's episodes",
which holds for a per-parent EMPTY fetch but not for a projection failure: that is systematic by
construction. One bad code path fires on every parent, so every season enumerates zero episodes,
existing.Except([]) is the whole episode library, and EmptyTrashHandler deletes it permanently.

Threads the counter into GetSeasonLibraryItems / GetEpisodeLibraryItems(WithoutPeople) for
Jellyfin and Emby using the same optional-trailing-param shape, and guards both sweeps with
MediaServerReconciliationGuard.ShouldFlagMissingDescendants — the same class and the same private
failure predicate as ShouldFlagMissing, deliberately WITHOUT #477's empty-fetch branch so
per-parent empty behaviour (and #476's cascade, which depends on it) is unchanged.

Also from the review:
- finding 3: tests now pin the same-instance JOIN at every level (movie, show, season, episode,
  music video) by driving the real ScanLibrary entry point and recording the failure from inside
  the enumeration, so a refactor handing the api client a fresh counter goes red.
- finding 4: the missing-library Failed() branch is documented as defensive and unreachable.
- finding 2: the mass-Skip residual (Emby's response-shape-dependent MediaSources guard, Plex's
  pre-projection filter) is stated as a known limitation in the decision record.
- finding 5: the log-contract change (only the #484 message when both refusals apply) is noted.

fixes #484
2026-07-25 16:36:35 +02:00
timothy e3645a2840 feat(484): suppress the media-server sweep on projection failures; reject the ratio threshold
Extends MediaServerReconciliationGuard (#477) with a second deterministic refusal: when the
enumeration that produced the incoming set silently dropped items whose projection THREW, the
file-not-found sweep is refused. A dropped item the server did return is indistinguishable
from a deletion at the reconcile step, so a projection regression could otherwise mass-flag a
healthy library FileNotFound (which EmptyTrash then deletes permanently).

Deliberate guard-clause skips (STRM files, virtual items, unsupported types) are explicitly NOT
failures and never suppress a sweep — counting them would permanently disable reconciliation for
any library holding a single STRM file.

The ratio / missing-fraction threshold is REJECTED, not deferred: it is a two-sided heuristic
with no tunable default and no telemetry, and the failure it approximates is exactly observable
via the projection-failure count (a genuine bulk deletion produces zero failures).

Seam is deliberately narrow — the private ProjectTo* contract inside each api client changed from
Option<T> to MediaServerProjectionResult<T> (projected/skipped/failed), the paged helper counts
IsFailure in one place, and the scanner reads it through an optional trailing
MediaServerProjectionFailureCounter on only the five library-level methods that feed a sweep.
The counter is per-enumeration state created by the scanner, never a field on an api client.

fixes #484
2026-07-25 16:36:35 +02:00
timothy 5560790bcb feat(jellyfin): sync music video libraries refs #42
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 3m53s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 5m4s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
2026-06-30 19:33:06 +02:00
Jason DoveandGitHub 794d209941 use latest authorization method with jellyfin api (#2821)
* use latest authorization method with jellyfin api

* temp pin dotnet sdk version to 10.0.102

* fix parameter name
2026-02-12 08:29:47 -06:00
Jason DoveandGitHub 474e647d6d more jellyfin performance improvements (#2747)
* fix slow db and api logging so it also works in scanner project

* don't request people from jellyfin by default
2025-12-31 22:28:15 -06:00
d0af507bef add ability to deep scan just a single tv show for Plex, Emby, and Jellyfin (#2318)
* add ability to deep scan just a single tv show for Plex, Emby, and Jellyfin

Including "/api/libraries/{id:int}/scan-show" REST API endpoint to
trigger.

* restrict plex search results to the intended library

* restrict scanning to media server libraries that are marked to sync with etv

* fix previous commit

* also guard library scan api

* add scan buttons to show ui

* scan single plex show by id

* scan jellyfin and emby single shows by id

* update changelog

---------

Co-authored-by: Jeff Slutter <MrMustard@gmail.com>
Co-authored-by: Jason Dove <1695733+jasongdove@users.noreply.github.com>
2025-08-14 16:07:56 +00:00
Jason DoveandGitHub 7e30444857 dependencies and code cleanup (#2117)
* fix validation in new form layout

* pin mediatr to last oss version

* update dependencies

* cleanup code in core

* cleanup code in ffmpeg

* cleanup code in infra

* cleanup code in scanner

* cleanup code in application

* cleanup main code

* cleanup test code

* solution-wide code cleanup
2025-07-06 15:56:17 +00:00
Jason DoveandGitHub da1cfab5f4 remove jellyfin admin user id requirement (#2025) 2025-06-09 13:33:47 +00:00
Jason DoveandGitHub f41fa669be update media server scanning and paging (#1770)
* update media server scanning and paging

* remove unused types
2024-07-02 13:12:09 -05:00
Jason DoveandGitHub b751f1054b direct stream content from jellyfin if needed (#1167)
* redirect to jellyfin stream as needed

* get jellyfin playback info

* sync chapters from jellyfin

* update changelog

* cleanup
2023-02-19 10:37:48 -06:00
Jason DoveandGitHub 18e66a92ad add paging to media server show and collection calls (#827)
* add paging to media server show library calls

* add paging to media server season and episode library calls

* formatting

* add paging to media server collection calls

* add paging to media server collection item calls

* update changelog
2022-05-31 05:56:48 -05:00
Jason Dove c9789458b9 page media server movie libraries 2022-05-28 20:41:22 -05:00
Jason DoveandGitHub 404ea49e35 jellyfin and emby path infos (#771)
* start adding jellyfin path info; fix some scanning bugs

* sync jellyfin libraries before scanning to ensure latest path infos

* code cleanup

* support emby path infos

* fix periodic scanning of emby and jellyfin libraries

* bug fixes
2022-04-29 15:07:17 -05:00
Jason DoveandGitHub d67251bfa0 jellyfin and emby collection sync (#752)
* sync jellyfin and emby collections

* update changelog
2022-04-22 13:33:35 -05:00
Jason DoveandGitHub c02b83d0d6 code cleanup (#743)
* update tools

* run code cleanup

* update dependencies
2022-04-19 17:47:18 -05:00
Jason DoveandGitHub 1ab98578ab refactor namespaces and imports (#670)
* re-namespace

* optimize usings

* more usings

* more of the same

* more implicit/global usings

* cleanup all usings

* minor fixes
2022-03-03 15:36:07 -06:00
Jason DoveandGitHub 4d86250630 add jellyfin media source (#185)
* wip

* start to add jellyfin tables to db

* code cleanup

* finish adding jellyfin media source

* sync jellyfin libraries

* display list of jellyfin libraries

* toggle jellyfin library sync

* edit jellyfin path replacements

* noop jellyfin scanners

* get jellyfin admin user id on startup

* implement jellyfin disconnect

* add jellyfin libraries to list; start to query jellyfin library items

* code cleanup

* start to project jellyfin movies

* save new jellyfin movies to db

* basic jellyfin movie update

* load jellyfin actor artwork

* load jellyfin movie poster and fan art

* more jellyfin artwork fixes, sync audio streams

* jellyfin playback sort of works

* skip jellyfin movies that are inaccessible

* use ffprobe for jellyfin movie statistics

* code cleanup

* store jellyfin operating system

* more jellyfin movie updates

* update jellyfin movie poster and fan art

* add jellyfin tv types

* sync jellyfin shows

* sync jellyfin seasons

* sync jellyfin episodes

* remove missing jellyfin television items

* delete empty jellyfin seasons and shows

* fix jellyfin updates

* fix indexing jellyfin movie and show languages
2021-05-15 13:14:17 -05:00