Skip to main content

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 is unsubscribePodcast); 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):

ActionWhen to use itCost
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).
rebuildSearchIndexRebuild the Typesense index into a fresh collection (after enabling search or changing languages).Reads the whole database once; search stays available.
replayDeadLettersEvents 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.
refreshPodcastsRe-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