skip to content
docsspec
github

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.

FieldMeaning
schemaAlways zaentrum.library.metadata/1.
itemId, typeEqual the manifest's; the validator checks both.
rev, createdAt, updatedAtupdatedAt is when the texts last changed.
titlesprimary (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, contentRatingAs published.
reference.runtimeMsThe 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.
collectionFranchise membership of a movie.
seriesSeries only: status, firstAirDate, lastAirDate, network, and seasons[].
episode.airDateEpisode 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.
curationWhat a person decided: metadataLocked, lockedFields, notes.
fieldOriginsWhere each set value came from (tmdb, legacy-catalog, filename, folder-name, file-tags, manual), keyed by field path. Images carry their own origin.
FieldMeaning
kindposter, backdrop, logo, still, banner, thumb.
fileA plain file name in this folder — no subfolders — listed once.
seasonOn a series: the season the image belongs to, or null for the series itself.
languageFor posters and logos with text, the language of that text.
sha256, sizeBytes, contentType, width, heightThe validator checks each against the file itself. Caches key on sha256.
sourceUrl, fetchedAt, originWhere the image came from, so the exact chosen image can be fetched again.

File names say what the image is:

ItemFiles
Movieposter.jpg, backdrop.jpg, logo.png
Seriesposter.jpg, backdrop.jpg, logo.png, and one season-NN-poster.jpg per season (season-00-poster.jpg for specials)
Episodestill.jpg
Any, localisedposter.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.

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.

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"]
  1. Read type, externalIds and match from the manifest. Skip items whose match is unmatched or disputed until a person has resolved the review. On an episode, tmdbTv is the parent series; the episode is fetched through the series with its aired season and episode numbers (or tmdbEpisode).
  2. Build the new texts and download the chosen images.
  3. Keep every field listed in curation.lockedFields — and everything, if metadataLocked — exactly as it was.
  4. Write the images first, then metadata.json (see writing safely), incrementing rev.

A re-sync never touches manifest.json: identity, versions and playback belong to the media pipeline.