Self-hosted · sits on top of your own Jellyfin
Your library,
put back in order.
jellystructure watches a Jellyfin library and does the part nobody enjoys — matching the right title, the right poster, the right episode number, the right subtitle — so what you press play on actually looks right.
What it actually does
The tedious half of running a media library, automated.
Jellyfin still does all the streaming — jellystructure just keeps what's behind it accurate, and stays out of the way once it is.
Matching that locks in place
Finds the right movie or show, pulls artwork and cast & crew, and remembers any correction you make — a re-scan never quietly overwrites it.
Episode numbers that hold up
Works out season and episode numbers even when the filename doesn't say, and repairs the ones that drift out of sync over time.
Skip intro, skip credits
Detects the intro and end-credits span in every episode of a season and publishes it back to Jellyfin, so any client can skip it.
Files the way people expect
Multi-episode files, several audio tracks, several subtitle languages — shown the way a person would describe them, not the way the filesystem stores them.
Plugs into the rest of the stack
Talks to Radarr, Sonarr, Bazarr and Seerr, so requests, downloads and subtitles land in the same well-kept library automatically.
A proper admin panel
A dashboard for the whole library — search, filters, drift warnings, and a raw editing surface for the rare case automation gets it wrong.
How the pieces fit
Three layers, each doing one job.
Jellyfin
Stores and streams the media. Untouched — jellystructure never replaces it, only tidies what it shows.
jellystructure
Continuously scans the library, fixes metadata and artwork, and detects segments — running quietly in the background.
Ravilo
The TV and phone app your household actually opens.
Run it
Three containers, one file.
The whole stack as a docker-compose.yml: Jellyfin, jellystructure and the Ravilo web app. Both of our images are on GitHub; 1.52 is the latest release.
services:
jellyfin:
image: jellyfin/jellyfin:latest
user: "1000:1000"
ports:
- "8096:8096"
volumes:
- ./jellyfin:/config
- ./jellyfin-cache:/cache
- /mnt/media:/media
restart: unless-stopped
jellystructure:
image: ghcr.io/jeggy/jellystructure:1.52
user: "1000:1000"
ports:
- "9505:9505"
volumes:
- ./jellystructure:/config
- /mnt/media:/media
environment:
# where the Ravilo web app is served from, so its browser calls are allowed
CORS_ALLOWED_ORIGINS: "localhost:8080"
restart: unless-stopped
ravilo-web:
image: ghcr.io/jeggy/ravilo-web:1.52
ports:
- "8080:8080"
environment:
# the jellystructure address as a browser reaches it
DEFAULT_SERVER_URL: "http://localhost:9505"
# plain http on a home network; drop this once it sits behind https
CSP_ALLOW_HTTP_CONNECT: "1"
restart: unless-stopped
- Media in one place. Mount your files at the same path in both containers (here
/mnt/media→/media) so the two agree on every filename, and make sure the1000:1000user owns them — jellystructure writes artwork and NFO files next to your media. - Set up Jellyfin at
http://localhost:8096: create the admin account and add the/medialibrary. - Open jellystructure at
http://localhost:9505. The first screen asks for the Jellyfin address — usehttp://jellyfin:8096, the container name — a Jellyfin API token and a TMDB API key. Sign in with the Jellyfin admin account; there is no separate login. - Watch at
http://localhost:8080, or install Ravilo on the TV and point it at the same jellystructure address.
Swap localhost for the machine's name or address when you open it from another device, in all three places. Needs Jellyfin 12 or later.