Mediatypes en metadata
Elk mediatype volgt hetzelfde patroon: de scanner registreert entities op basis van het pad,
*_FOUND-events triggeren verrijking vanuit een externe provider, afbeeldingen lopen via
IMAGE_FOUND. Metadata-providers staan in worker/.../events/; hun HTTP-clients zijn
gecentraliseerd in worker/.../http/MetadataRestClients, en elke externe base-URL is een
property met de echte service als default — de e2e van de chart serveert ze allemaal vanuit één
WireMock-pod en faalt op dead-lettered events, dus een hardcoded URL breekt de CI.
Talen
app.ister.languages / ISTER_LANGUAGES (ISO-639-1-tags, default en,nl, ontsloten als
LanguageProperties) is dé app-brede talenlijst. De movie/show/episode-handlers halen TMDB-details
één keer per geconfigureerde tag op, wat per taal per item één MetadataEntity-rij oplevert; de
taal wordt opgeslagen als ISO-639-3, ook al gebruikt de fetch ISO-639-1. De eerste tag is de
primaire/fallback-taal. Dezelfde lijst stuurt het zoekschema aan (hoofdstuk 6). Een
taal toevoegen vereist een re-scan plus een reindex.
Films, shows, afleveringen (TMDB)
De MOVIE_FOUND- / SHOW_FOUND- / EPISODE_FOUND-handlers halen TMDB-details per taal op, slaan
MetadataEntity-rijen op en downloaden posters/achtergronden (verstuurd als IMAGE_FOUND op de
cache-directory).
Credits komen in dezelfde pass mee: movie credits, show aggregate credits en episode credits (cast
- guest stars) worden
PersonEntity- +CreditEntity-rijen, direct in de database geschreven. EenCreditEntitykoppelt een persoon aan precies één van movie/show/episode. EenPersonEntitywordt gedeeld tussen acteurs en muziekartiesten; TMDB-castleden worden gededupliceerd tegen bestaande personen op exacte naam + geboortejaar. Het GraphQL-typeCreditontsluit de terugverwijzingen (movie/show/episode, batch-resolved inCreditController), zodat een filmografie opvraagbaar is viapersonById { credits { movie/show/episode } }.
Muziek (MusicBrainz)
Artiest-directories worden PersonEntity-rijen (PERSON_FOUND), albums AlbumEntity
(ALBUM_FOUND), tracks lopen via AUDIO_FILE_FOUND (ffprobe + ID3-tags + embedded cover).
Album-identiteit komt uit het pad, nooit uit tags. De worker-HandleAlbumFound bevraagt
MusicBrainz en downloadt de release-group-cover; de disk-kant
(HandlePersonFound/HandleAlbumFound) zoekt naar artist.nfo/album.nfo. Artiesten krijgen een
birthYear (MusicBrainz life-span, of de mapnaam) — precies zodat de TMDB-acteur-dedup hierboven ze
kan matchen.
Biografieën en portretten (Wikipedia/Wikidata)
WikipediaService (worker) verrijkt personen met meertalige biografieën en portretten: Wikidata
resolveert de entiteit en zijn afbeelding/sitelinks, het Wikipedia-summary-endpoint (een
URL-template-property) levert de extracts per taal. Dezelfde service voedt de beschrijvingen van
stripseries.
Boeken (LibraryType.BOOK)
De directorygrammatica is auteur-eerst: Author/Book.epub en Author/Book/NNN_Chapter.mp3. Alle
formaten van één (genormaliseerde) boeknaam convergeren op één BookEntity (auteur =
PersonEntity); formaten zijn bijlagen — epubs koppelen via MediaFileEntity.bookEntity,
audiobook-mp3's via ChapterEntity (gestreamd over hetzelfde audio-only HLS-pad als tracks).
HandleAudioFileFound brancht op library-type en maakt chapters in plaats van tracks.
- Media overlays (EPUB 3-voorleesaudio) worden gemarkeerd op
MediaFileEntity.mediaOverlays, uitsluitend gedetecteerd uit de inhoud van de epub (SMIL-entries in het OPF-manifest, geparst doordisk/.../epub/EpubParser) — nooit uit de bestandsnaam. BOOK_FOUNDtriggert Open Library-verrijking (beschrijving, en een cover alleen als er nog geen is); Wikidata voegt reekslidmaatschap toe (reeksnaam + positie); NFO-data wordt gededupliceerd tegen provider-data, zodat re-scans de beschrijvingen niet verdubbelen.- Epubs worden door de client lazy gelezen via
GET /epub/{mediaFileId}/resource/{entry}(hoofdstuk 7); de leespositie is eenWatchStatusEntitymetreadingLocation(epubcfi) +readingProgress.
Strips (LibraryType.COMIC, migratie V23)
Strips zijn serie-eerst, het omgekeerde van de boekengrammatica: {root}/{Series Name (start year)}/Volume 27.cbz (ook Vol 3 - Subtitle.pdf, Issue 8.epub, en getolereerde wilde patronen;
cover.jpg in de serie-directory is serie-artwork). Het (YYYY)-suffix is het startjaar van de
serie, geen auteursjaar — strips hebben geen auteur in het pad; het parsen zit in ComicPathObject
ComicFileNameParser, en alles dat dieper genest is dan de serie-directory wordt genegeerd.
ComicScanner maakt uit het pad de SeriesEntity en het volume (een BookEntity zonder auteur)
aan en hangt het bestand eraan als MediaFileEntity; alle formaten van één volume (zelfde basename)
convergeren op één volume-rij. Inhoud lezen gebeurt asynchroon: HandleComicFileFound (disk) leest
cbz via CbzParser en pdf via PDFBox (PdfParser) — de paginatelling gaat op de
MediaFileEntity, embedded ComicInfo.xml (cbz) wordt volume-metadata en kan de uit de
bestandsnaam afgeleide reekspositie en titel verfijnen, en de cover (eerste cbz-pagina, of
gerenderde pdf-pagina 1) wordt naar de cache geëxtraheerd. Epub-volumes hergebruiken de volledige
EPUB_FILE_FOUND-pijplijn. COMIC_SERIES_FOUND (worker, HandleComicSeriesFound) voegt per taal
seriebeschrijvingen en een thumbnail toe vanuit Wikipedia/Wikidata — lokale artwork wint altijd en
wordt nooit overschreven. Pagina's worden aan de reader geserveerd door ComicResourceController
(/comic/{mediaFileId}/manifest, /page/{index}, /file).
Podcasts (LibraryType.PODCAST)
Het eerste feed-gebaseerde librarytype: er is geen library-directory. subscribePodcast(feedUrl)
of de uurlijkse PodcastRefreshScheduler (met lastRefreshedAt-guard, zodat meerdere nodes niet
dubbel sweepen) stuurt PODCAST_REFRESH_REQUESTED (globale queue). De RssFeedParser van de worker
haalt de feed op met een conditional GET (ETag/Last-Modified), capt op 500 items, synct
kanaal-metadata + cover en maakt PodcastEpisodeEntity-rijen, gededupliceerd op guid.
De nieuwste N afleveringen (app.ister.worker.podcast.auto-download-count, default 3) krijgen
PODCAST_EPISODE_DOWNLOAD_REQUESTED op de cache-directory-queue van de node die de refresh
deed; de disk-handler downloadt de enclosure (volgt redirects) naar {cache}/podcasts/ en stuurt
AUDIO_FILE_FOUND, waarna afspelen identiek is aan tracks. Oudere afleveringen downloaden
on-demand via de downloadPodcastEpisode-mutation. Retentie: de dagelijkse cache-cleanup verwijdert
downloads ouder dan podcast-retention-days (default 30), behalve als iemand middenin de aflevering
zit — de afleverings-rij blijft bestaan en kan opnieuw downloaden. Zoeken in de podcastdirectory
loopt via de gratis iTunes Search API (ItunesSearchService, api-module).
NFO-bestanden
HandleNfoFileFound (disk) parst XML-NFO-bestanden naar metadata: titel, beschrijving en
releasedatum voor film/tv, biografie voor artiesten, review voor albums, boek-metadata voor boeken.
PERSON_FOUND/ALBUM_FOUND aan de disk-kant zoeken proactief naar artist.nfo/album.nfo naast
de media. NFO- en provider-metadata worden gededupliceerd, zodat de een nooit rijkere data van de
ander overschrijft.