Documentation
¶
Overview ¶
Package fanarttv is Mosaic's fanart.tv artwork module.
It fills one content role — v1.RoleArtwork (ADR 0075) — plus its own settings screen, and what it does *not* do is the shape of it.
It illustrates content; it does not identify it ¶
fanart.tv has no titles, no overviews, no years, no cast, no search and no catalogs. There is no query that turns "Blade Runner" into a result: you must already know which film you mean, and hand it an identifier. So this module can answer exactly one question — *what does this look like* — and none of the others.
That is why it declares no v1.RoleMetadata, and the temptation to is the specific mistake ADR 0075 exists to prevent. ADR 0035 makes a registered metadata role part of the composition-root check that a deployment can identify content; a module that cannot name a film satisfying that check would produce a Mosaic that boots and finds nothing. `boundary_test.go` asserts the manifest never grows the role.
It is reached by enrichment, never by a ref ¶
Every other source module is invoked because a search or catalog result named it in a v1.ContentRef. This one is never named, because it produces no results. The Platform reaches it through the artwork enrichment pass it runs after materialising a work (ADR 0075), addressing it by the work's *shared* external identity — the same mechanism ADR 0073 built for stream providers.
Capability.Import therefore exists only because v1.Capability requires it, and always refuses.
What it returns is a candidate set, not an answer ¶
The module ranks nothing and chooses nothing. It returns every image fanart.tv has as [v1.ArtworkCandidate]s carrying language and the source's own like count, and the Platform's selection rule (ADR 0074) resolves which one fills a slot — because that choice is ultimately a user's, and a module cannot hold it.
Two mappings in here are load-bearing and easy to get silently wrong:
- **`lang: "00"` means textless, not a language.** It maps to an empty Language, which is what lets the Platform prefer a backdrop with no burned-in title to sit under a hero's clearlogo. Carried through as a language, every textless image would look foreign and the preference would never fire.
- **An HD variant outranks its standard-definition twin.** Likes accumulate per image, so an older SD logo frequently has more of them than the HD one that replaced it. This is the one ranking fanart.tv's own data cannot express.
Honest limits ¶
- **A series needs a TVDB id.** fanart.tv keys television by TVDB id and nothing else. `module-tmdb` binds one; `module-cinemeta` binds only `imdb`, so a series imported through Cinemeta gets no artwork from here. Recorded in ADR 0075 rather than papered over — it is not fixable in this module.
- **Episode stills are not fetched.** A metadata provider already returns every episode's still in one call; asking here would be a request per episode for data the Platform has in bulk. Series and season art only.
- **The artwork type table is a guess that must be verified.** The mapping from fanart.tv's response keys to slots is data in one table precisely so that a type which has been renamed or added is a one-line correction. Check it against fanart.tv's current documentation before trusting the coverage.
Index ¶
- Constants
- type Capability
- func (c *Capability) Artwork(ctx context.Context, req v1.ArtworkRequest) (v1.ArtworkResponse, error)
- func (c *Capability) Import(ctx context.Context, svc v1.ContentService, req v1.ImportRequest) (v1.ImportResult, error)
- func (c *Capability) Manifest() v1.Manifest
- func (c *Capability) SettingsUI(ctx context.Context, req v1.SettingsUIRequest) (v1.SettingsUIResponse, error)
- type Client
- type Settings
Constants ¶
const ( // CapabilityID is the id the Platform registers this module under and the id // stamped onto every candidate it supplies, so a set assembled from several // providers stays attributable (ADR 0074). // // Unlike every module before it, this id never appears in a ContentRef: this // module sources nothing, so nothing ever routes an import back to it. CapabilityID = "fanart-tv" )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Capability ¶
type Capability struct {
// contains filtered or unexported fields
}
Capability is the fanart.tv artwork module.
It fills exactly one content role — RoleArtwork — plus its own settings screen, and the roles it does *not* fill are the whole shape of it. fanart.tv has no titles, no overviews, no years, no cast, no search and no catalogs; there is no query that turns "Blade Runner" into a result here, because you must already know which film you mean. It cannot describe content and must never claim it can (ADR 0075).
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).
The client is built per invocation rather than here, because the credential comes from the settings document the Platform hands in on each call and a user may change it between two of them.
func (*Capability) Artwork ¶
func (c *Capability) Artwork(ctx context.Context, req v1.ArtworkRequest) (v1.ArtworkResponse, error)
Artwork resolves artwork candidates for content this module did not source (RoleArtwork, ADR 0075).
It returns an empty response and no error when it cannot address the content — a film with neither a TMDB nor an IMDb id, a series with no TVDB id, or a title fanart.tv simply has nothing for. All three are ordinary and none is a failure: erroring would make an artwork lookup able to disrupt an import, and the art it might have added is by definition an improvement on top of art the node already has.
func (*Capability) Import ¶
func (c *Capability) Import(ctx context.Context, svc v1.ContentService, req v1.ImportRequest) (v1.ImportResult, error)
Import is required by v1.Capability and is unreachable for this module.
Import materialises the item named by a ContentRef, and a ref names the capability that produced it. This module fills no read role, so it produces no refs, so nothing can name it — the method exists because the interface requires it, not because there is a path to it.
It returns an error rather than an empty success. An empty success would say "I materialised nothing, and that is fine", which would let a bug that routed an import here pass silently; an error says what is actually true.
**This is a finding, not a workaround.** v1.Capability bundles identity with the one write verb, which fits a module that sources content and does not fit one that enriches it. An enrichment-only module has to stub the verb. Worth taking to the SDK if a second such module appears.
func (*Capability) Manifest ¶
func (c *Capability) Manifest() v1.Manifest
Manifest is the module's self-declaration, including the provider roles it fills (ADR 0027).
**The roles it does not declare matter more than the ones it does.** It does not declare RoleMetadata, and the temptation to is the specific mistake ADR 0075 exists to prevent: ADR 0035 makes a registered RoleMetadata part of the composition-root check that Mosaic can identify content, and a module that cannot name a film has no business satisfying it. Declaring the role to reach ContentMetadata's image fields would produce a deployment that boots and cannot identify anything — a failure no test would catch.
func (*Capability) SettingsUI ¶
func (c *Capability) SettingsUI(ctx context.Context, req v1.SettingsUIRequest) (v1.SettingsUIResponse, error)
SettingsUI renders the module's own settings screen as SDUI (RoleSettingsUI, ADR 0038): set or replace the project key, add a personal key for early access, and read the attribution fanart.tv's terms expect.
Every mutating control is an Invoke of the Platform's configureModule command carrying the complete new settings document, so the Platform stays the one that persists them and the module never holds state between invocations. The screen is returned as serialised UINode JSON, which is what keeps the SDK SDUI-agnostic.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a fanart.tv web service client.
func NewClient ¶
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).
func (*Client) Artwork ¶
func (c *Client) Artwork(ctx context.Context, mediaType v1.MediaType, identities []v1.ExternalIdentity, season int) ([]v1.ArtworkCandidate, error)
Artwork fetches the images fanart.tv has for one title, scoped to the series itself (season 0) or to one of its seasons.
The response is decoded into a map keyed by artwork type rather than a struct, because the type vocabulary *is* the schema: fanart.tv adds types over time, and a struct would silently drop the ones this build predates. The identity fields (`name`, `tmdb_id`, …) decode as something that is not an array and are skipped by the decoder.
type Settings ¶
type Settings struct {
// APIKey is the user's own fanart.tv project key.
//
// **It only ever holds the user's key.** It is never populated from
// defaultAPIKey as a convenience: configureModule replaces the whole
// settings document, so the bundled key reaching this field would write a
// shared build-time credential into a user's stored settings the next time
// they touched any control.
APIKey string `json:"apiKey,omitempty"`
// ClientKey is a user's personal fanart.tv key, which grants early access to
// artwork that has been uploaded but not yet promoted. It is optional and
// independent of APIKey — a user may supply one, both or neither.
ClientKey string `json:"clientKey,omitempty"`
}
Settings is this module's user-managed configuration (ADR 0021).
The Platform stores it uninterpreted and hands it back on every invocation; what the fields mean is this module's business.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
module-fanart-tv
command
Command module-fanart-tv runs this module as its own process, for a Platform that hosts it out of process (ADR 0064, ADR 0077).
|
Command module-fanart-tv runs this module as its own process, for a Platform that hosts it out of process (ADR 0064, ADR 0077). |