jellyfin-tools/jellyfin-year-review/docs/sources.md
jellyfin-year-review/docs/sources.mdRaw
1# Source investigation and supported contracts
2
3Verified 2026-10-17 against primary sources, plus synthetic representative fixtures. No production Jellyfin/Jellystat installation was accessed or changed. These are source-verified API/schema contracts, not a claim that every release was integration-tested against a live installation.
4
5- [Jellyfin current release v12.2](https://github.com/jellyfin/jellyfin/releases/tag/v12.2). Authentication follows the official [UserController](https://github.com/jellyfin/jellyfin/blob/v12.2/Jellyfin.Api/Controllers/UserController.cs) (`POST /Users/AuthenticateByName`, `GET /Users/Me`, `GET /Users`) and [SessionController](https://github.com/jellyfin/jellyfin/blob/v12.2/Jellyfin.Api/Controllers/SessionController.cs) (`POST /Sessions/Logout`). Inventory uses `/Items` with recursive movie/episode/series selection, genres and Primary image tags. A server configured with custom authentication providers must still accept the standard Jellyfin username/password API. No OIDC or account linking is used. API keys need permission to list users and all inventory; fresh user login in the default online mode needs reachable Jellyfin. Offline account export and local validation are described separately below.
6- [Jellystat 1.1.12](https://github.com/CyferShepard/Jellystat/releases/tag/1.1.12) raw activity contract; source also inspected at main commit [`e0a38eeeff4c4bbaba60abad5f76e8a54415d1a8`](https://github.com/CyferShepard/Jellystat/tree/e0a38eeeff4c4bbaba60abad5f76e8a54415d1a8). [ActivityMonitor](https://github.com/CyferShepard/Jellystat/blob/1.1.12/backend/tasks/ActivityMonitor.js) accumulates seconds while unpaused, finalizes rows, and can merge resumed records into existing rows. Its stop/merge timestamp updates explain why `ActivityDateInserted - PlaybackDuration` is not a reliable historical watch interval. [Raw activity mapping](https://github.com/CyferShepard/Jellystat/blob/1.1.12/backend/models/jf_playback_activity.js) supplies ID, user, movie/series ID, episode ID, duration and timestamp. `EpisodeId` takes precedence over `NowPlayingItemId` when present.
7- Jellystat's [API routes](https://github.com/CyferShepard/Jellystat/blob/1.1.12/backend/routes/api.js) and [Swagger contract](https://github.com/CyferShepard/Jellystat/blob/1.1.12/backend/swagger.json) expose history endpoints, but `/api/getHistory` groups records and replaces durations with sums. Pagination during live collection is also not an immutable snapshot. This implementation deliberately uses a native raw-table export in a repeatable-read transaction instead. It needs read-only SELECT access to the existing Jellystat PostgreSQL instance, no Jellystat API token or new database.
8- [Jellystat's plugin import migration](https://github.com/CyferShepard/Jellystat/blob/1.1.12/backend/migrations/101_ji_insert_playback_plugin_data_to_activity_table.js) marks Playback Reporting imports with `imported=true`. Such rows are explicitly excluded/rejected. The task explicitly prohibited Playback Reporting despite later inherited language recommending it, so no plugin schema, plugin connection, or plugin database reader is supported. This project does not claim compatibility with Playback Reporting schemas. Review generation does not query Jellyfin SQLite. The separate, explicitly selected offline authentication exporter reads only the 12.2 account/status schema described below.
9- [PostgreSQL repeatable-read transaction semantics](https://www.postgresql.org/docs/current/transaction-iso.html#XACT-REPEATABLE-READ) support the single-table read-only export. [SQLite online backup](https://www.sqlite.org/backup.html) and [WAL behavior](https://www.sqlite.org/wal.html) explain why copying a live database file alone is unsafe. SQLite snapshots are archival guidance only, not a playback input for this app.
10- [SvelteKit 3 static adapter](https://svelte.dev/docs/kit/adapter-static), [SPA fallback](https://svelte.dev/docs/kit/single-page-apps) and [v3 migration](https://svelte.dev/docs/kit/migrating-to-sveltekit-3): use Vite-hosted SvelteKit configuration, adapter fallback `index.html`, `ssr=false`, a rest route for deep links, and the current `$app/tsconfig` reference. No production Node process. Exact dependency versions come from official npm package dist-tags and are locked.
11- [Go stable downloads](https://go.dev/dl/) and [Go rooted filesystem APIs](https://pkg.go.dev/os#OpenRoot): Go 1.27.2, static build with embedded `time/tzdata` for scratch deployment.
12
13The representative fixture covers native finalized row fields, numeric-string duration, episode precedence, null/unknown metadata, and duplicate records. It deliberately does not turn a raw Jellystat aggregate row into an exact canonical session. If an installed version has a different schema or mixed-server history, export only the configured Jellyfin server's native rows using a reviewed query. The provided default assumes one Jellyfin server per Jellystat database; cross-server identity collisions are outside the supported contract.
14
15
16## Jellyfin 12.2 offline account export
17
18The offline exporter and verifier follow the exact `v12.2` sources: [User entity](https://github.com/jellyfin/jellyfin/blob/v12.2/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/User.cs), [SQLite model snapshot](https://github.com/jellyfin/jellyfin/blob/v12.2/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Migrations/JellyfinDbModelSnapshot.cs), [PermissionKind](https://github.com/jellyfin/jellyfin/blob/v12.2/src/Jellyfin.Database/Jellyfin.Database.Implementations/Enums/PermissionKind.cs) (disabled=2, remote access=4), [DefaultAuthenticationProvider](https://github.com/jellyfin/jellyfin/blob/v12.2/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs), [PasswordHash encoding](https://github.com/jellyfin/jellyfin/blob/v12.2/MediaBrowser.Model/Cryptography/PasswordHash.cs), [CryptographyProvider](https://github.com/jellyfin/jellyfin/blob/v12.2/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs), and [cryptography constants](https://github.com/jellyfin/jellyfin/blob/v12.2/MediaBrowser.Model/Cryptography/Constants.cs). Current default is salted PBKDF2-SHA512, 210,000 iterations, 64-byte output; legacy `PBKDF2` means SHA-1 with 32-byte output. The PHC-like format encodes salt/hash as hex, not base64. Only the current built-in provider ID is accepted for export. Unlike Jellyfin itself, offline export omits passwordless accounts and does not migrate hashes.
19
20Python's [read-only URI connections](https://docs.python.org/3/library/sqlite3.html#how-to-work-with-sqlite-uris) and SQLite's [read-transaction snapshot isolation](https://www.sqlite.org/isolation.html) support a consistent read-only export, including committed WAL content. Source access uses one transaction rather than a raw live `.db` copy. Serving uses Go's standard-library [crypto/pbkdf2](https://pkg.go.dev/crypto/pbkdf2) and requires no database driver or Python runtime.
21
22## Jellyfin artwork
23
24Posters use Jellyfin 12.2's [`GET /Items/{itemId}/Images/Primary`](https://github.com/jellyfin/jellyfin/blob/v12.2/Jellyfin.Api/Controllers/ImageController.cs) with `format=Jpg`, maximum dimensions 500×750, quality 85 and the inventory’s Primary image tag. The [ImageFormat enum](https://github.com/jellyfin/jellyfin/blob/v12.2/MediaBrowser.Model/Drawing/ImageFormat.cs) and [BaseItemDto image metadata](https://github.com/jellyfin/jellyfin/blob/v12.2/MediaBrowser.Model/Dto/BaseItemDto.cs) define that format and cache tag. Movie and parent-series IDs select the Jellyfin artwork first. Requests retain the configured Jellyfin base path and use the operator API key in an HTTP header only. Successful JPEG responses are cached privately and copied to the immutable dataset under SHA-256 filenames; the frontend uses `/art/<hash>.jpg` exclusively. Source failures, missing images or invalid JPEGs use placeholders. Redirects are rejected, request deadlines and three-attempt retry limits apply, and 429/5xx backoff is capped at three seconds per wait.
25
26
27## TVmaze fallback artwork
28
29The [TVmaze public API](https://www.tvmaze.com/api) provides a generation-only fallback for series with unavailable Jellyfin artwork. It requires no API key. TVmaze does not provide a movie catalog, so movies retain placeholders if Jellyfin cannot provide a poster. Matching first tries `/lookup/shows` with Jellyfin TVDB/IMDb provider IDs (including the documented redirect); otherwise `/search/shows` and up to three `/shows/:id/akas` requests must identify one exact normalized title or alias, matching the premiere year when known. Ambiguous results are rejected rather than guessing.
30
31Posters are downloaded from TVmaze's image CDN, validated and converted to local JPEGs. Successful fallback posters and their attribution are cached, while failed searches/downloads are retried on the next generation. Jellyfin is still tried first every time. API requests are spaced at least 500 ms apart; HTTP 429/5xx responses receive bounded retries and backoff. Only title/provider metadata is sent to TVmaze, using a separate client without Jellyfin credentials. The browser serves the published `/art/` file and never calls either API.
32
33TVmaze API data is licensed under [CC BY-SA](https://creativecommons.org/licenses/by-sa/4.0/). Each fallback poster retains a `posterSource` link to its TVmaze show page, displayed as a TVmaze credit. Retain that attribution and the applicable license when redistributing the exported TVmaze material.