Commit Graph
6 Commits
Author SHA1 Message Date
vicrodh 75b8eee1c6 feat(media): add Jellyfin Quick Connect and harden remote connections
Add cancellable Quick Connect pairing and shared connection guards for Jellyfin and Subsonic. Bound connection attempts, validate library access before saving credentials, and report per-provider connection and synchronization status.

cx
2026-09-09 17:12:45 -06:00
vicrodh e7135a424a feat(media-servers): capture the recording MBID and ISRC from Jellyfin and Subsonic
Jellyfin requests Fields=ProviderIds on both listing passes and maps
ProviderIds.MusicBrainzTrack (the recording id) into JellyfinTrack;
Subsonic reads OpenSubsonic musicBrainzId and the first isrc. Both land
in two additive remote_cache_tracks columns (isrc, recording_mbid), kept
across upserts with COALESCE so a known id is never overwritten by a
server that stops sending it, and travel to the queue through
qbz-source's remote constructor.

cc
2026-08-28 20:31:05 -06:00
vicrodh d244213a64 fix(library): harden cross-source catalog data
Preserve per-disc and collection artwork, multi-value genres and remote metadata across Local, Plex, Jellyfin and Subsonic.

Apply source, format and quality filters before pagination, reject invalid CUE rows, and keep Qt library surfaces aligned with the selected physical version.

cx
2026-08-25 18:26:06 -06:00
vicrodh 8fff37a8ef feat(library): hydrate Jellyfin quality in background
Publish bounded essential metadata pages through source generations, preserving delta sync and authorizing prune only after a complete full pass.

Hydrate MediaSources in persistent prioritized batches with cancellation epochs, startup resume, bounded retries, and coalesced catalog catch-up.

cx
2026-08-23 14:24:16 -06:00
vicrodh 910f7aec4d fix(jellyfin): one session per test binary — a repeat auth revokes the token
Reported as a flake: four live tests passed and one failed Unauthorized,
while other tests doing the same call succeeded. It is not a flake and
not the server being slow.

Jellyfin keys the SESSION on DeviceId, and a second AuthenticateByName
under the same DeviceId REPLACES the first — the earlier token starts
answering 401. Measured directly against 10.11.11 rather than inferred:

  same DeviceId:      auth -> T1, auth -> T2;  T1 = 401, T2 = 200
  different DeviceId: auth -> A,  auth -> B;   A  = 200, B  = 200

Five tests authenticating in parallel under one DeviceId were therefore
invalidating each other, and whichever finished first was left holding a
dead token. Running with --test-threads=1 hid it, which is why the first
run looked green.

The tests now share one lazily-initialised session, which is also what
the app must do. Two production obligations follow and are now written
on `auth_header` where somebody will read them: the DeviceId must be
stable PER INSTALL (persisted like local_plex::client_id — a constant
would make two QBZ installs log each other out, a fresh id per launch
drops the previous run's token and litters the server's devices page),
and the app must authenticate once and hold the token rather than
concurrently.

Verified in the parallel configuration the report used: 10 unit + 5 live,
three consecutive runs, 5/5 each time.

cc
2026-08-20 07:34:41 -06:00
vicrodh e578d8a54a feat(jellyfin): the protocol crate, verified against a live server
Stage 5's first half. Sibling of `qbz-plex` and deliberately shaped like
it, so `qbz-source`'s JellyfinSource will read like PlexSource:
frontend-agnostic, HTTP + DTOs, no cache schema and no settings store.

Three measured facts shape the file, and each one is a comment because
each one is a decision somebody could otherwise undo:

- `?static=true` is bit-perfect. Verified by md5 against the file on the
  server's own disk, not inferred from a content type. `is_direct_
  response` encodes the tell a transcode leaves — chunked with no
  Content-Length — so a server-side policy silently resampling a hi-res
  track is caught rather than played.
- Quality costs 45.8 s per 4924 tracks and there is no cheaper path.
  `Fields=MediaStreams` trims 29% of the bytes and saves nothing: the
  cost is server-side media-info hydration. The scan will own that
  knowingly, which is why `track_count` exists to give a progress
  denominator.
- Cover urls need no credentials, so unlike a Plex thumb they are STABLE
  and their cache key can be the url itself.

`stream_url` / `image_url` are free functions rather than methods. Two
reasons and both are load-bearing: `Source::artwork_token` may not touch
the network by contract, and a `reqwest::Client` cannot even be
CONSTRUCTED without a rustls provider — which a unit test does not have.

The mapper picks the audio stream BY TYPE, not by position: an embedded
cover rides in the same MediaStreams array as an `mjpeg` with BitDepth 8,
and taking [0] would badge a 24-bit FLAC as 8-bit. A lossy row keeps an
absent bit depth rather than defaulting to 16, which would badge every
MP3 as CD quality.

10 unit tests, plus 5 LIVE tests that skip unless QBZ_JELLYFIN_URL names
a server. The unit tests prove the JSON shape is understood; only the
live ones can prove the endpoint still exists, the token still opens it,
and the stream is still direct. Against Jellyfin 10.11.11: 5/5 — 500-row
page with 221 real bit depths of 4924 total, a Range request answered
206 with a `fLaC` bitstream, a tokenless cover, and a future-dated delta
returning zero.

cc
2026-08-20 07:15:20 -06:00