Jellyfin (films and TV)

Jellyfin identifies films and series in library/movies and library/tv, keeps their metadata and artwork, and - the reason it is here - transcodes video a browser cannot play. SoundStorm decides nothing about video formats itself; it asks Jellyfin and does what it is told.

Why Jellyfin

Asked and answered rather than assumed. Plex needs a plex.tv account, so it cannot be provisioned without a human logging into a cloud service - fatal to the zero-keys promise. Emby went closed-source. Everything else in the space is too thin to bet on. And video fails the rule for what SoundStorm owns: it is not self-describing and does need transcoding. The honest cost is API churn (Jellyfin 12 broke two documented auth methods) and a 2.5 GB image.

Provisioning

Jellyfin's startup wizard is a plain REST API - /Startup/Configuration, /Startup/User, /Startup/RemoteAccess, /Startup/Complete - and it stops accepting calls once setup completes, which makes driving it safe. SoundStorm then signs in (/Users/AuthenticateByName) and creates two libraries with /Library/VirtualFolders: films on /media/movies and shows on /media/tv, both mounted read-only.

Films and series are separate Jellyfin libraries with different collection types and scrapers, so one Jellyfin is registered as two sources sharing one token: jellyfin (Movie) and jellyfin-tv (Series, Episode). The house shares one Jellyfin account, which is why film and episode positions are kept by SoundStorm per person rather than in Jellyfin.

Jellyfin accepts connections while it is still loading and answers 503. Provisioning treats that, like any 5xx or dial error, as "wait" - only a 401 or 403 means the credentials are wrong.

The calls SoundStorm makes

CallUsed for
/ItemsSearch and browse; IncludeItemTypes=Series to list shows; SortBy=SortName only when browsing
/Items/{id}/PlaybackInfoNegotiating playback with a device profile
/videos/{id}/master.m3u8 and childrenHLS transcodes, proxied under /api/hls/
/Videos/{item}/{source}/Subtitles/{n}/Stream.vttSubtitles as WebVTT
DELETE /Videos/ActiveEncodingsStopping a transcode when the viewer leaves
/Shows/{id}/EpisodesA show's page, season by season
/Items/{id}/Images/PrimaryPosters, resized by Jellyfin
POST /Library/Refresh"Look now" after an upload (every library; there is no per-library trigger without its id)

Playback is negotiated, never assumed

Whether a file needs transcoding depends on container, video codec, audio codec, profile and level. Jellyfin knows all five; SoundStorm knows none. So StreamTarget POSTs a device profile to PlaybackInfo and follows the answer. The profile errs conservative on purpose - h264/aac in mp4, VPx/AV1 in webm - because claiming a codec the browser cannot decode is the silent failure: Jellyfin hands over the original and the video element shows nothing, with no error anywhere. Claiming too little only costs an unnecessary transcode.

Verified against live servers

Keeping the admin token in

One account serves two sources, so every Jellyfin target checks the item comes back from a query restricted to its own types (cached ten minutes, since every HLS segment asks) - otherwise a member refused films could play one through the TV source. HLS paths must match the shapes Jellyfin's own playlists use; a doubly-encoded .. once reached the admin API. And some options make Jellyfin write URLs carrying the admin token into the master playlist - HLS subtitles, subtitles in the manifest, trickplay tiles - so every subtitle key is stripped and enableTrickplay=false is forced.

Deleted files

Jellyfin removes items for deleted files on its next validation - unless the library folder is empty. Then it logs that the folder is "inaccessible or empty, skipping" and changes nothing, because it cannot tell "everything was deleted" from "the drive did not mount". So every deleted film stayed searchable forever. The folders' README.txt placeholders are what keep them never empty, and EnsurePlaceholders writes them back before every scan. The adapter also sends IsMissing=false, so episodes Jellyfin manufactures for gaps in a series never appear.

Version

The image follows latest; everything above was checked against 12.1.0. Jellyfin's API has moved under SoundStorm before, so re-verify authentication and the HLS query keys when it changes.