cinemeta

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: MIT Imports: 11 Imported by: 0

README

module-cinemeta

Mosaic's default metadata provider: a client of Cinemeta, Stremio's public film and television metadata service.

It fills three provider roles (ADR 0027):

Role What it answers
RoleMetadata Overview, genres, IMDb rating, runtime, poster/backdrop/clearlogo, billed cast names, and for a series a per-episode preview with stills and synopses
RoleSearch Free-text search over film and television
RoleCatalog Popular and Featured collections, for both types

It needs no configuration. No API key, no addon URL, no settings document — constructing it is all the setup there is.

Why it exists

Metadata and search are a required capability class (ADR 0035): a Mosaic that cannot identify or find content reads as broken rather than as unconfigured. ADR 0062 makes a provider for that class a core module under its guarantee clause — compiled into the binary, first-party, with no install step that can fail.

Before this module, that guarantee was met by a Cinemeta addon URL bundled inside module-stremio-addons: a default belonging to an extension module, reached through a general addon protocol, sitting in a user-managed list that could be emptied. ADR 0035 recorded that placement as unresolved in as many words. A guarantee cannot be delegated to something that is not itself guaranteed to be present.

So this is not a second Stremio addon client. It speaks the Stremio addon protocol because that is what Cinemeta serves, but it talks to one known service whose resources are fixed: no manifest to fetch, no resources to negotiate, no addon list, no ordering policy. What it gives up in generality it gains in being unable to be misconfigured — and module-stremio-addons remains the right place for everything general.

What it deliberately does not do

  • No streams, no subtitles. Cinemeta describes content; it does not index or host it. An import through this module materialises a Work and its season/episode tree with no Parts — the meta-only shape the Platform already supports. A deployment that wants something to play installs a stream source alongside it.
  • No settings screen. There is nothing to set, so there is no RoleSettingsUI (ADR 0038).
  • No clearart, banners, collections, "similar", or cast headshots and character names. These are ADR 0034's recorded gaps and they are structural to the source rather than decoding this module skipped. A TMDB- or Fanart-class provider is what closes them. This module reports honestly empty fields instead of inventing.

Identity and dedup

Cinemeta's own identifiers are IMDb ids — its manifest declares the tt prefix and nothing else — so a Work is bound under the imdb scheme rather than under a name of this module's own. That is not a convenience: it is the accurate name for what the id is, and it is what makes a title added here the same Work as one a Stremio addon would have added, rather than a duplicate (ADR 0028).

The boundary is the point

This module imports only the published sdk and the standard library, enforced by boundary_test.go parsing every import. Being a core module is a delivery decision, not a contract one (ADR 0062): the code is shaped exactly as a third party's would be, does not know which tier it is in, and could move out of process as a build change rather than a rewrite (ADR 0064).

Everything Cinemeta-shaped stops in cinemeta.go (ADR 0051): a rating as a string, a year range with an en dash, a cast hidden in a categorised links array, episodes numbered under either episode or number, genres under either genres or genre, and posters served at a thumbnail size that is upgraded to the one Mosaic renders at.

Build and test

Everything runs in a container; nothing is built or tested on the host.

docker compose -f docker-compose.test.yml run --rm test

That runs gofmt, go build ./..., go vet ./... and go test ./... against a pinned toolchain. The tests are hermetic — the fake Cinemeta is an httptest server — so they need no network beyond resolving the SDK.

The fake is checked against the live service by hand, and that has already paid. Cinemeta answers 200 for an id it does not know, in two different shapes: an unknown series returns an empty document, and an unknown film returns a meta echoing the id and the type with no name. Testing emptiness the obvious way passed the fake and would have materialised a library Work titled tt99999999.

Status

Built: the three roles, the import path for films and series, and the hermetic test suite. Verified against the live service by hand — search, both metadata shapes, all four catalogs, paging, and the unknown-id shapes above.

MIT-licensed (ADR 0022), like Mosaic's other modules and unlike the Platform's AGPL.

Documentation

Overview

Package cinemeta is Mosaic's default metadata provider: a client of Cinemeta, Stremio's public film and television metadata service, filling the metadata, search and catalog provider roles (ADR 0027).

It is a **core module** (ADR 0062) under the guarantee clause. Metadata and search are a required capability class (ADR 0035) — a Mosaic that cannot identify or find content reads as broken rather than as unconfigured — so at least one provider must be present in every binary, with no install step that can fail and no configuration that can be omitted. This module is that provider. It needs no credential, no key and no addon URL: constructing it is all the configuration there is.

The tier is a delivery decision, not a contract decision. This module is shaped exactly like an extension module — its own Go repository importing only the published SDK and the standard library — and it does not know which tier it is in.

Why it exists rather than the arrangement it replaces

The metadata Mosaic shipped with was a Cinemeta addon URL bundled *inside* module-stremio-addons, which is an extension module: a default belonging to something a deployment might not install, reached through a general addon protocol and a user-managed addon list that could be emptied. ADR 0035 recorded that placement as unresolved — "whether the default belongs to the Platform or to the module is a question this record answers one way and the code answers the other" — and ADR 0062 answered it the other way by making a metadata provider core. A guarantee cannot be delegated to a module that is not guaranteed to be there.

So this is not a second Stremio client. It is a direct client of one service, with no manifest negotiation, no addon list, no resource discovery and no ordering policy — none of which a fixed, known upstream needs. What it gives up in generality it gains in being unable to be misconfigured.

What it provides

  • RoleMetadata — descriptive detail for a ref: overview, genres, IMDb rating, runtime, poster/backdrop/clearlogo, billed cast names, and for a series a per-episode preview with stills and synopses.
  • RoleSearch — free-text search over film and television. The other half of the required class, and the half that produces a ref at all: without it nothing could name the content the metadata role answers for, which is why ADR 0035 makes the two one class rather than two.
  • RoleCatalog — Cinemeta's Popular and Featured collections for both types, so a fresh install has rails to render rather than an empty home screen.

It fills no stream or subtitle role, and it fills no settings-UI role. Cinemeta describes content and does not index or host it, so an import through this module materialises a Work and its season/episode tree with **no Parts** — the meta-only shape the Platform already supports. A deployment that wants something to play installs a stream source alongside it.

What it deliberately cannot do

Cinemeta has no clearart or banner artwork, no franchise collections, no "similar titles", and no character names or headshots on its cast. Those are ADR 0034's recorded gaps and they are structural to the source, not decoding this module skipped. A TMDB- or Fanart-class provider is what closes them; this module reports honestly empty fields rather than inventing.

It owns no schema (ADR 0012): everything it writes goes through ContentService, acting as the Caller the Platform hands it (ADR 0017).

Index

Constants

View Source
const (
	// CapabilityID is the id the Platform registers this module under, the id a
	// ref names to route an import back here, and the key any settings document
	// would be stored under (ADR 0021). There is no settings document — see New.
	CapabilityID = "cinemeta"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Capability

type Capability struct {
	// contains filtered or unexported fields
}

Capability is the Cinemeta metadata module. It holds an HTTP client and nothing else.

func New

func New(httpClient *http.Client) *Capability

New builds the capability over an HTTP client (nil for a default). The Platform passes its own, which carries the netguard dial guard and the outbound telemetry seam (ADR 0055).

It takes no settings, and that is the point rather than an omission. Every provider role receives the module's settings document on each invocation (ADR 0021) and this module ignores it, because a guarantee-clause core module (ADR 0062) that can be configured is one that can be misconfigured: there is no key to be missing, no URL to be wrong, and no list a user can empty. The client is therefore built once here rather than per invocation, unlike a module whose configuration can change between two calls.

func (*Capability) CatalogItems

CatalogItems lists one collection's entries as virtual candidates (ADR 0028). It touches no part of the object graph: browsing a source must not flood the library with everything the source knows about, and Cinemeta knows about everything.

func (*Capability) Catalogs

Catalogs lists the collections this module exposes (RoleCatalog). The set is fixed rather than fetched: Cinemeta declares its catalogs in a manifest, and half of them require an argument a browse surface addressing a catalog by id alone has no way to supply, so which of them are usable is a decision that belongs at this boundary rather than being rediscovered per request.

func (*Capability) Import

Import materialises the virtual item named by req.Ref — a result a search or catalog browse produced (ADR 0028) — into the object graph.

It creates the Work with its artwork and external id, binds the source, and builds the containment tree: a film as Work → feature item, a series as Work → season container → episode item. It attaches **no Parts**, which is the shape rather than a gap: Cinemeta knows what exists, not where to get it, so an import through this module is a described library that needs a stream source installed beside it before anything plays.

func (*Capability) Manifest

func (c *Capability) Manifest() v1.Manifest

Manifest is the module's self-declaration, including the provider roles it fills (ADR 0027).

Three roles, and the three it does not declare are as deliberate: no stream and no subtitles, because Cinemeta describes content rather than indexing it; no settings UI, because there is nothing to set.

func (*Capability) Metadata

Metadata resolves descriptive detail for a ref (RoleMetadata). It backs the detail screen for both a virtual result and an in-library node, and it is the detail Import draws on (ADR 0027, ADR 0034).

It is the role the required capability class (ADR 0035) is really about: with no provider filling it, a Mosaic can list what it owns and describe none of it.

func (*Capability) Search

Search returns virtual candidates for free text (RoleSearch).

The media-type filter selects which of Cinemeta's catalogs is asked rather than filtering what came back, so a narrowed search is one request instead of two and cannot return a row it then has to discard.

type CatalogDecl

type CatalogDecl struct {
	ID   string
	Type string
	Name string
}

CatalogDecl is one collection this module exposes.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a Cinemeta API client. It holds only an HTTP client: there is nothing to configure, so there is nothing else to hold.

func NewClient

func NewClient(httpClient *http.Client) *Client

NewClient builds a client over an HTTP client (nil for a default). The Platform passes its own, which carries the netguard dial guard and the outbound telemetry seam (ADR 0055); a module that builds its own bypasses both.

func (*Client) CatalogItems

func (c *Client) CatalogItems(ctx context.Context, catalogID, nativeType, genre string, skip int) ([]Preview, bool, error)

CatalogItems lists one catalog's entries. skip is an item offset, which is what Cinemeta's own paging parameter means, so it passes through untranslated. genre narrows the listing when non-empty, as the addon protocol's `genre` extra; the caller has already checked it against what the catalog declared.

It reports whether another page exists, and this is the **weaker** of the two statements the SDK describes: Cinemeta returns no total, so a full page is all there is to go on. Only the provider can make even that claim, because only the provider knows the page size — and its cost is bounded and visible, a final page of exactly a hundred asking for one more that comes back empty. The alternative is what was here before: paging built, and dead, because nothing ever said there was more.

func (*Client) Catalogs

func (c *Client) Catalogs() []CatalogDecl

Catalogs is the set of collections this module exposes.

It is a curated subset of what Cinemeta's manifest declares, and the omissions are deliberate rather than an oversight. Cinemeta's "New" catalogs require a year to be supplied as a genre parameter, and its "Last videos" and "Calendar videos" catalogs require a list of series ids the caller must already hold — neither fits a browse surface that addresses a catalog by id alone (ADR 0028). What is left is the four that answer with no argument.

The names are Cinemeta's own, qualified by type. Its manifest calls both of the "top" catalogs "Popular", which is unambiguous in a client that shows film and television separately and useless in a flat list of collections.

func (*Client) Meta

func (c *Client) Meta(ctx context.Context, nativeType, id string) (Title, error)

Meta fetches one title's full record.

One request is the whole story here, and it is worth contrasting with a metadata API that splits a record across endpoints: Cinemeta returns the description, artwork, rating, runtime, cast and — for a series — every episode of every season in a single document. That is the one thing this source is unambiguously better at.

func (*Client) Search

func (c *Client) Search(ctx context.Context, text string, nativeTypes []string) ([]Preview, error)

Search queries Cinemeta's search-capable catalogs for free text and returns the union, de-duplicated by id.

nativeTypes selects which catalogs are asked — film, television or both. The media-type filter therefore *chooses the endpoint* rather than filtering results afterwards, which is one fewer round trip when a caller has narrowed the query. A type that errors is skipped rather than failing the search: half an answer beats none when the other half is a transient 502.

type Episode

type Episode struct {
	Season    int
	Episode   int
	Title     string
	Overview  string
	Thumbnail string
	Released  string
}

Episode is one episode of a series.

type Preview

type Preview struct {
	ID         string
	NativeType string
	Title      string
	Year       int
	Poster     string
}

Preview is one search or catalog result — enough to render a row and to address the title afterwards.

type Title

type Title struct {
	ID         string
	NativeType string
	Title      string
	Year       int
	Overview   string
	Poster     string
	Backdrop   string
	Genres     []string
	Rating     float64
	Runtime    string
	// Cast is billed names, and names only. Cinemeta carries no character and no
	// headshot for a credit, which is one of ADR 0034's recorded gaps rather than
	// something this decode drops — so the type says so instead of offering
	// fields that would always be empty.
	Cast []string
	// Episodes is populated for a series, in season/episode order.
	Episodes []Episode
}

Title is one film or series, as fully as Cinemeta describes it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL