metadata.json
Every text and image of an item, in the item's metadata/ folder. Schema:
https://zaentrum.github.io/schemas/library/v1/metadata.schema.json.
Status — format ahead of the code. No platform service writes or reads this document yet; the reference migrator produces it. The re-sync described below is how the format is designed to be maintained, not a shipped job.
The images sit in the same folder as metadata.json, so the folder is complete
on its own: copy it and you have everything a viewer sees about the item.
Fields
Section titled “Fields”| Field | Meaning |
|---|---|
schema | Always zaentrum.library.metadata/1. |
itemId, type | Equal the manifest's; the validator checks both. |
rev, createdAt, updatedAt | updatedAt is when the texts last changed. |
titles | primary (display title; may differ from the manifest title when a person edited it), original, sort, qualifier (US, UK, 2009 — disambiguates remakes and local versions), and localized keyed by BCP 47 language with title, sortTitle, tagline, overview. |
releaseDate, genres, tags, rating, contentRating | As published. |
reference.runtimeMs | The runtime the reference database lists. It lists one runtime for every cut, which is why the manifest's measured runtime is kept separately. |
credits[] | personId (stable, keys person pages), name, role, character, order, and tmdbPerson. |
collection | Franchise membership of a movie. |
series | Series only: status, firstAirDate, lastAirDate, network, and seasons[]. |
episode.airDate | Episode only. An episode's own name and overview are in titles. |
images[] | Every image file in this folder. |
videos[] | Trailers, teasers and clips published elsewhere, by reference: site, key, url, name, kind, language, durationMs, publishedAt, and origin (where the reference came from). A downloaded, packaged trailer is listed in the manifest's trailers[] instead. |
curation | What a person decided: metadataLocked, lockedFields, notes. |
fieldOrigins | Where each set value came from (tmdb, legacy-catalog, filename, folder-name, file-tags, manual), keyed by field path. Images carry their own origin. |
Images
Section titled “Images”| Field | Meaning |
|---|---|
kind | poster, backdrop, logo, still, banner, thumb. |
file | A plain file name in this folder — no subfolders — listed once. |
season | On a series: the season the image belongs to, or null for the series itself. |
language | For posters and logos with text, the language of that text. |
sha256, sizeBytes, contentType, width, height | The validator checks each against the file itself. Caches key on sha256. |
sourceUrl, fetchedAt, origin | Where the image came from, so the exact chosen image can be fetched again. |
File names say what the image is:
| Item | Files |
|---|---|
| Movie | poster.jpg, backdrop.jpg, logo.png |
| Series | poster.jpg, backdrop.jpg, logo.png, and one season-NN-poster.jpg per season (season-00-poster.jpg for specials) |
| Episode | still.jpg |
| Any, localised | poster.de.jpg, logo.fr.png |
A missing kind falls back to the parent when read: an episode without a still
shows its season poster, then the series poster. Nothing is copied to fill a gap.
Series and seasons
Section titled “Series and seasons”A series' metadata.json describes the series as a whole and every season the
reference database lists, whether or not its episodes are on storage — so a
viewer can see which seasons are missing. Each season keeps its own reference id.
From the example series:
"series": { "status": "returning", "firstAirDate": "2024-01-10", "lastAirDate": "2024-03-06", "network": null, "seasons": [ { "number": 1, "tmdbSeason": null, "name": "Season 1", "overview": "The first season.", "airDate": "2024-01-10", "episodeCountReference": 8 } ]}The episodes that are on storage are listed by the series'
manifest; compare episodeCountReference with that list
to show gaps.
Re-syncing from a reference database
Section titled “Re-syncing from a reference database”Because the manifest carries explicit reference ids, the metadata of any item can be fetched again. This is the designed procedure:
flowchart LR
M["manifest.json<br/>type + externalIds"] --> T{type}
T -- movie --> A["movie details<br/>(tmdbMovie)"]
T -- series --> B["TV details + every season<br/>(tmdbTv)"]
T -- episode --> C["episode details<br/>(tmdbTv, season, episode)"]
A & B & C --> W["write metadata.json + images<br/>skip lockedFields"]
- Read
type,externalIdsandmatchfrom the manifest. Skip items whose match isunmatchedordisputeduntil a person has resolved thereview. On an episode,tmdbTvis the parent series; the episode is fetched through the series with its aired season and episode numbers (ortmdbEpisode). - Build the new texts and download the chosen images.
- Keep every field listed in
curation.lockedFields— and everything, ifmetadataLocked— exactly as it was. - Write the images first, then
metadata.json(see writing safely), incrementingrev.
A re-sync never touches manifest.json: identity, versions and playback belong
to the media pipeline.