Files
qbz/crates/qbz-jellyfin/Cargo.toml
T
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

25 lines
1000 B
TOML

[package]
name = "qbz-jellyfin"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "Jellyfin integration for QBZ: HTTP client, JSON metadata, bit-perfect stream URLs (frontend-agnostic)"
[dependencies]
# HTTP client for the Jellyfin server API.
reqwest = { workspace = true, features = ["json"] }
# Jellyfin speaks JSON end to end (Plex speaks XML — that is the whole
# difference between this crate's parsing and `qbz-plex`'s).
serde = { workspace = true }
serde_json = { workspace = true }
[dev-dependencies]
tokio = { workspace = true }
# reqwest is built with `rustls-tls-webpki-roots-no-provider`, so a
# `reqwest::Client` cannot be CONSTRUCTED until a process-level CryptoProvider
# is installed. The app does that in `qbz_app::install_crypto_provider`; a test
# binary has no app, so the live harness installs it itself (same shape as
# `qbz-offline-cache/tests/purchase_download_harness.rs`).
rustls = { version = "0.23", features = ["aws_lc_rs"] }