Backends

A backend is an existing open-source media server that owns one kind of media and one folder. SoundStorm installs each one in its own container, sets it up with nobody logging in, keeps the credentials it generated, and drives it through its API - so the person using SoundStorm never sees it.

The backends at a glance

BackendKindImageProvisioned byWhat SoundStorm uses
NavidromeMusicdeluan/navidromeFirst-run admin form while no user existsSubsonic search, streams, covers, albums, lyrics, ReplayGain, transcoding for beats
JellyfinFilms, TVjellyfin/jellyfinIts startup wizard, a plain REST APISearch, playback negotiation, HLS transcoding, subtitles, episodes
AudiobookshelfAudiobooksadvplyr/audiobookshelfRoot user, library, per-member usersSearch, tracks, chapters, per-person listening position
ImmichPhotosimmich-app/*:v3Admin sign-up, API key, external libraryThumbnails, previews, smart search, people, places, per-member accounts
StorytellerRead-along timingpinned by digestA server action on /init, then tokensSentence-level alignment of ebook text to audiobook audio
AudioMuse-AISong analysispinned by digest/api/setup before its first saveMoods, energy, tempo, "sounds like"
SoundStorm itselfEbooks, documents-Nothing to provision; it scans the foldersEPUB and PDF metadata, covers, the reader's resources

Jellyfin is provisioned once and registered as two sources sharing one token - jellyfin (films) and jellyfin-tv (series and episodes) - because they are separate Jellyfin libraries with different scrapers. That is why provision.buildSources returns a slice.

Why these, and why delegate at all

Owning scanning, metadata, artwork and transcoding is years of work to arrive somewhere worse than what exists. So each media type goes to the best open server for it, and the test for whether SoundStorm owns a type itself is: is it self-describing and free of transcoding? EPUB is; video and HEIC photos are not. Navidrome rather than Jellyfin for music because its music model is first-class (multi-value artists, compilations, ReplayGain, a fast scanner). Jellyfin rather than Plex because Plex needs a plex.tv account, which no installer can create - fatal to the zero-keys promise. Emby went closed-source.

A backend is two halves: search and provisioning. A backend a human must configure by hand defeats the point, so both halves or it is not done. Every one of these was set up from a script against a live server before it was accepted.

The adapter interface

Every backend is reached through an adapter in internal/source/<name> that implements source.Source:

type Source interface {
    ID() string                 // "navidrome", "jellyfin-tv" ...
    Kind() media.Kind           // exactly one kind per source
    Search(ctx context.Context, q media.Query) ([]media.Item, error)
    Health(ctx context.Context) error
}

Everything else is an optional interface a handler checks for with a type assertion. A source that cannot do something simply does not implement it, and the feature is absent for that shelf rather than broken. The main ones:

InterfaceWhat it answersImplemented by
Streamer, ArtProviderAn authenticated upstream Target for an item's bytes or artwork, which the server fetches and pipes throughAll
Negotiator, HLSProvider, SubtitleProviderWhether a film needs transcoding, its HLS playlists and segments, WebVTT subtitlesJellyfin
TrackLister, ChapterLister, PositionTracker, AudioLayouterAn audiobook's files on one clock, its chapters, the listener's place, how its files map to chaptersAudiobookshelf
MusicBrowser, MixSource, LyricsSource, SongFileLister, ListenerAlbums and artists, random songs and genres, lyrics, real file paths, FLAC for beat analysisNavidrome
ShowBrowserA show's seasons and episodesJellyfin (TV)
PhotoBrowserPeople, places, a person's photosImmich
BookOpenerA book's internal files for the readerlocalbooks
ItemGetterOne item by id, for favorites, playlists and ContinueAll
FileListerWhich files an item is, relative to its shelf, for deletionMedia backends
RecentLister, InProgressListerNewest first for Home, books in progress for ContinueMost
Rescanner, Starter"Look now" after an upload; work to do before answering at allMost; localbooks

A Target carries headers as well as a URL because backends disagree about where a credential goes: Subsonic in the query string, Jellyfin 12 only in an Authorization header. The proxy just replays both parts. It is never given to a browser.

Normalization at the edge

Only an adapter knows its backend's vocabulary. Everything past it speaks media.Item. That rule absorbs a surprising amount of disagreement, each found against a live server:

Rules every backend lives under

Each backend

NavidromeMusic: Subsonic, real paths, ReplayGain, the listening transcoding. JellyfinFilms and TV: playback negotiation, HLS, subtitles, and an API that moves. AudiobookshelfAudiobooks: per-person accounts and positions, inodes, chapters. ImmichPhotos: external libraries, API keys, an account per member. StorytellerRead-along: forced alignment, two-step books, timing conversion. AudioMuse-AIMoods: hearing every song, nothing leaving the house. Ebooks and documentsNo backend: localbooks, EPUB and PDF metadata, optional OPDS.