Libraries and media layout
A library is a named collection of one type (MOVIE, SHOW, MUSIC, BOOK, COMIC or
PODCAST). A directory is a path on disk attached to a library; a library can span several
directories, even across nodes. The scanner decides what a file is from its path, so the
on-disk layout matters.
Configuring libraries and directories
Indexed properties, from disk/src/main/resources/disk.yaml (as env vars:
APP_ISTER_DISK_LIBRARIES_0_NAME etc.):
app:
ister:
disk:
libraries:
- name: shows
type: SHOW
- name: books
type: BOOK
directories:
- name: disk1
path: /disk1
library: shows
- name: disk2
path: /disk2
library: shows
Directory names must be unique across the whole cluster — they name the per-directory work
queues (Multi-node). Write paths without a trailing slash and keep them
stable: the path is stored verbatim in the database and compared as a string prefix, so changing
/disk1 to /disk1/ later counts as a path change. At startup this config is applied
asymmetrically: a directory's path is updated in the database, but a library is only
created — changing libraries[n].type after the first start silently does nothing (delete the
library and re-create it instead). And if a directory name is already claimed by another node,
startup aborts (Multi-node).
Expected layout per type
This is the short version; chapter 8 is the full naming reference (exact patterns, accepted extensions, special files, and common mistakes).
Shows — Show Name (year)/Season NN/sNNeNN.mkv:
The Wire (2002)/Season 01/s01e01.mkv
Movies — one file (or a folder) per movie, name ending in the year:
Heat (1995)/Heat (1995).mkv
Music — Artist/Album/track:
Miles Davis/Kind of Blue/01 So What.flac
Books — one logical book per author, in two interchangeable forms that converge on the same book entry: an epub directly under the author, and/or an audiobook folder of numbered chapters:
Terry Pratchett/Guards! Guards!.epub
Terry Pratchett/Guards! Guards!/001_Chapter 1.mp3
Read-aloud (EPUB 3 media-overlay) epubs are detected automatically from the epub's contents, never from the filename.
Comics — series-first: {Series Name (optional year)}/Volume 27.cbz. Also .pdf and
.epub; loose patterns like attackontitan_vol27.pdf, series_issue8.pdf and name#3.cbz
are tolerated.
Games — platform-first: {platform}/{Title (year)}/…. A ROM is one file in its game folder
(n64/Super Mario 64 (1996)/Super Mario 64.z64), a PS3 game is its whole folder (marked by
PS3_GAME/PARAM.SFO), a PC game is a portable zip (Windows) or a GOG-style .sh installer /
tar archive (Linux), and a Steam game is its installed steamapps/common/<installdir> tree
next to its appmanifest_<appid>.acf. Games are played on the client (the desktop player
starts RetroArch, RPCS3, the game or Steam); the server keeps the files and every user's saves.
In SHOW and MOVIE libraries the recognised video containers are mkv, mp4, webm, m4v, flv and avi; subtitles: .srt
next to the video (picture-based subtitles inside the container — Blu-ray PGS, DVD VobSub — are shown as they are, no conversion); local artwork: jpg/png;
.nfo files are read for metadata hints. Other library types accept their own extension lists —
see the naming reference.
Podcasts
A PODCAST library needs no directory at all — it is feed-based:
- Subscribe from the client (or the GraphQL
subscribePodcast(feedUrl)mutation — admin-only, as isunsubscribePodcast); the directory search in the client uses the free iTunes Search API. - Feeds refresh hourly; the newest episodes (default 3,
auto-download-count) are downloaded automatically into the cache directory, older ones on demand when a user plays them. - Downloads expire after 30 days (
podcast-retention-days) unless someone is mid-episode.
Scanning and analyzing
The maintenance mutations (also exposed in the client's admin screens):
| Action | When to use it | Cost |
|---|---|---|
scanLibraries(libraryId?) | New files were added — there is no filesystem watcher. Optionally scoped to one library. | Cheap; known files are skipped. |
refreshMetadata(MISSING, libraryId?) | Backfill: fetch metadata/artwork only where missing (e.g. after adding a TMDB key), recompute missing blur-hashes. | Cheap and idempotent; safe anytime. |
refreshMetadata(FORCE, libraryId) | Rebuild one library: wipe stored metadata, artwork and stream info, then re-fetch everything (e.g. after a wrong match, or to pick up newly added fields on old items). | Heavy: an external fetch per item, ffprobe per file. |
refreshMovie/Show/Episode/Person/Album/Track(id) | The same wipe-and-refetch for a single item (the ⋮ menu on its detail page). There is no per-item refresh for books, comics or podcasts. | One item (a show fans out to its episodes). |
rebuildSearchIndex | Rebuild the Typesense index into a fresh collection (after enabling search or changing languages). | Reads the whole database once; search stays available. |
replayDeadLetters | Events whose handler kept failing wait in the dead-letter queue (deadLetterCount shows how many). After the cause is fixed, this sends them back to their original queue. | One republish per event; one that fails again dead-letters again. |
refreshPodcasts | Re-fetch every subscribed feed now instead of waiting for the hourly refresh. Not admin-only. | Cheap (conditional GET per feed). |
downloadPodcastEpisode(episodeId) | Pull one older episode into the cache on demand. Not admin-only. | One download. |
All are asynchronous — they queue events and return immediately; progress is visible in the client's activity view. A scan does not re-fetch metadata for existing items, and a MISSING refresh does not touch items that are already complete. Details of the pipeline are in the architecture documentation.
Where to next
- Multi-node — directories spread over several servers
- Maintenance — what happens to caches over time