-- 0013_series.sql — television: series, seasons, episodes. -- -- Vellum had no season or episode model. media_items was a flat tree and the -- scanner never parsed S01E02, so a library of television was a list of files -- in a folder. That is the difference between a movie server and a media -- server, and it is also what blocks TV metadata: there is nothing to attach a -- series' poster or overview to. -- -- Two decisions worth stating, because both are cheap now and expensive later. -- -- * The new columns are additive and nullable. A film keeps NULL for both -- numbers, so every existing row is untouched and every existing query, the -- web app and the Jellyfin surface keep working exactly as they do today. -- Nothing is rewritten, so there is no backfill and no migration risk. -- -- * kind gains 'series' and 'season', but an EPISODE STAYS kind='video'. A -- new 'episode' kind would mean touching every query that says video, for -- no gain: the parent chain already tells an episode from a film, and a -- series row is the only genuinely new concept. Keeping video also means a -- library that has not been rescanned yet behaves identically. -- -- A series and a season have no file of their own, so their path is the -- directory they stand for. That is unique per library, which is what the -- existing UNIQUE (library_id, path) requires, and it gives the row a stable -- identity across rescans — the scanner can upsert on it rather than -- duplicating a series every time it runs. -- -- The tree itself is not new. parent_id already carries albums and audiobook -- chapters, so this is the same shape applied to television: library -> series -- -> season -> episode. ALTER TABLE media_items ADD COLUMN season_number INTEGER; ALTER TABLE media_items ADD COLUMN episode_number INTEGER; -- Listing a series' seasons and a season's episodes is the hot path, and both -- read by parent and then order by the number. CREATE INDEX idx_items_parent_season ON media_items(parent_id, season_number); -- Finding every series in a library, and every season of one, without a scan. CREATE INDEX idx_items_kind_parent ON media_items(kind, parent_id);