Documentation
¶
Overview ¶
Package catalog implements core.Catalog: a SQLite mirror of a filtered TMDB slice plus on-demand fetches for anything else.
Index ¶
- Constants
- Variables
- func EstimateTitles(minPopularity float64, minVotes int) int
- func MeetsThreshold(cfg config.Catalog, t core.Title, now time.Time) bool
- func RatingExceeds(rating, max string) bool
- func RatingRank(rating string) int
- func WithinRatingLimit(rating, max string) bool
- type Catalog
- func (c *Catalog) BackfillHomeReleases(ctx context.Context, limit int) (int, error)
- func (c *Catalog) Browse(ctx context.Context, q core.BrowseQuery) (core.Page, error)
- func (c *Catalog) ByExternalID(ctx context.Context, source, id string) (*core.Title, error)
- func (c *Catalog) CachedDiscoveryEpisodes(ctx context.Context, shows []core.RatingKey) (map[core.RatingKey][]core.Title, error)
- func (c *Catalog) CachedTitle(ctx context.Context, key core.RatingKey) (*core.Title, error)
- func (c *Catalog) CachedTitles(ctx context.Context, keys []core.RatingKey) ([]core.Title, error)
- func (c *Catalog) Children(ctx context.Context, key core.RatingKey) ([]core.Title, error)
- func (c *Catalog) Collection(ctx context.Context, tmdbCollection int64) (string, []core.Title, error)
- func (c *Catalog) CrowdGraphBatch(ctx context.Context, keys []core.RatingKey, maxPerKey, maxTargets int) (map[core.RatingKey][]core.RatingKey, map[core.RatingKey]core.Title, error)
- func (c *Catalog) CrowdRecommendations(ctx context.Context, key core.RatingKey, pages int, fetch bool) (titles []core.Title, fetched bool, err error)
- func (c *Catalog) Facet(ctx context.Context, t core.MediaType, facet string) ([]core.FacetValue, error)
- func (c *Catalog) FilterVisibility(ctx context.Context, ts []core.Title) []core.Title
- func (c *Catalog) Get(ctx context.Context, key core.RatingKey) (*core.Title, error)
- func (c *Catalog) Grandchildren(ctx context.Context, key core.RatingKey) ([]core.Title, error)
- func (c *Catalog) List(ctx context.Context, list string, t core.MediaType, limit int) ([]core.Title, error)
- func (c *Catalog) NextEpisode(ctx context.Context, episode core.RatingKey) (*core.Title, error)
- func (c *Catalog) PreviewVisibility(ctx context.Context, specs []core.LibraryExclusion) (any, error)
- func (c *Catalog) RecentEpisodes(ctx context.Context, show core.RatingKey, since time.Time) ([]core.Title, error)
- func (c *Catalog) RecentMovies(ctx context.Context, since time.Time) ([]core.Title, error)
- func (c *Catalog) RecentShows(ctx context.Context, since time.Time) ([]core.Title, error)
- func (c *Catalog) ReleaseMatches(ctx context.Context, releases []core.Release) (map[string][]core.Title, error)
- func (c *Catalog) Run(ctx context.Context)
- func (c *Catalog) RunTrends(ctx context.Context)
- func (c *Catalog) Search(ctx context.Context, query string, types []core.MediaType, limit int) ([]core.Title, error)
- func (c *Catalog) SeedGenres(mediaType string, m map[int]string)
- func (c *Catalog) ShowTrajectories(ctx context.Context, shows []core.RatingKey) (map[core.RatingKey]core.ShowTrajectory, error)
- func (c *Catalog) Similar(ctx context.Context, key core.RatingKey, limit int) ([]core.Title, error)
- func (c *Catalog) StandaloneEpisodes(ctx context.Context, minRating float64, limit int) ([]core.Title, error)
- func (c *Catalog) Stats(ctx context.Context) (Stats, error)
- func (c *Catalog) Sync(ctx context.Context) error
- func (c *Catalog) SyncProgress() SyncProgress
- func (c *Catalog) TrendStatus(ctx context.Context) []TrendStatus
- func (c *Catalog) UpdateConfig(cfg config.Catalog)
- func (c *Catalog) UpsertTitle(ctx context.Context, t core.Title, browsable bool) error
- func (c *Catalog) Visible(t *core.Title) bool
- func (c *Catalog) WarmDetails(ctx context.Context, limit int) (int, error)
- type Estimate
- type Mirror
- func (m *Mirror) AirDates(ctx context.Context, now time.Time, premiere bool) (map[core.RatingKey]int64, error)
- func (m *Mirror) CollectionArt(ctx context.Context, id int64) (core.Collection, error)
- func (m *Mirror) Fingerprint(ctx context.Context) (MirrorFingerprint, error)
- func (m *Mirror) Franchises(ctx context.Context) ([]core.Collection, error)
- func (m *Mirror) Snapshot(ctx context.Context) ([]MirrorItem, error)
- func (m *Mirror) Titles(ctx context.Context, keys []core.RatingKey) ([]core.Title, error)
- type MirrorFingerprint
- type MirrorItem
- type Option
- type Stats
- type SyncProgress
- type TMDB
- type TrendStatus
- type VisibilityPreview
Constants ¶
const DefaultCrowdTTL = 72 * time.Hour
DefaultCrowdTTL is how long a stored co-engagement list is fresh.
Variables ¶
var ErrSyncInProgress = fmt.Errorf("catalog: sync already in progress")
Sync (re)builds the browsable set from TMDB discover. A second call while one is running returns ErrSyncInProgress; use SyncProgress to poll.
Functions ¶
func EstimateTitles ¶
EstimateTitles is a convenience wrapper returning only the total.
func MeetsThreshold ¶
MeetsThreshold reports whether a title qualifies for the browsable set under cfg: popularity >= MinPopularity OR votes >= MinVotes, and released.
func RatingExceeds ¶
RatingExceeds reports whether rating is stricter than max ("" max = unrestricted).
func RatingRank ¶
Content-rating ordering. All scales map onto a unified severity rank:
1 everyone G, TV-G, TV-Y, U 2 parental PG, TV-PG, TV-Y7 3 teens PG-13, TV-14, 12/15-ish, M 4 adults R, TV-MA, MA15+ 5 adults-only NC-17, 18/R18+
Unknown / unrated ratings rank 0. Restricted viewers cannot access them.
func WithinRatingLimit ¶
WithinRatingLimit is the negation, for readability at call sites.
Types ¶
type Catalog ¶
type Catalog struct {
// contains filtered or unexported fields
}
Catalog implements core.Catalog.
func New ¶
func New(db *sql.DB, client TMDB, cfg config.Catalog, log *slog.Logger, opts ...Option) (*Catalog, error)
New creates a catalogue. client may be nil (mirror-only / offline mode).
func (*Catalog) BackfillHomeReleases ¶ added in v0.2.34
BackfillHomeReleases fetches at most limit details, never from a browse. Missing and future dates are revisited daily; failures also cool down for a day so an unavailable title cannot starve the remaining catalogue. Recently reached dates are rechecked daily; settled dates are revisited after 90 days to pick up later corrections.
func (*Catalog) Browse ¶
Browse queries the browsable mirror entirely in SQL, with generation-stamped in-memory count and page caching so repeated library/recommendation reads return without hitting disk.
func (*Catalog) ByExternalID ¶
ByExternalID looks up by imdb ("tt123") or tvdb id.
func (*Catalog) CachedDiscoveryEpisodes ¶ added in v0.2.9
func (c *Catalog) CachedDiscoveryEpisodes(ctx context.Context, shows []core.RatingKey) (map[core.RatingKey][]core.Title, error)
CachedDiscoveryEpisodes returns regular episode metadata without hydration or remote lookups. Unknown/future dates are retained so callers can distinguish an unmirrored premiere from a known but unreleased episode.
func (*Catalog) CachedTitle ¶
CachedTitle returns a mirrored title without contacting TMDB.
func (*Catalog) CachedTitles ¶ added in v0.2.6
CachedTitles returns released, non-adult mirrored titles in requested order, without refreshing remote metadata. Batch consumers such as recommendation builders use this to avoid one full hydration round trip per history entry.
func (*Catalog) Collection ¶
func (c *Catalog) Collection(ctx context.Context, tmdbCollection int64) (string, []core.Title, error)
Collection returns a TMDB franchise and its parts sorted by release date.
func (*Catalog) CrowdGraphBatch ¶ added in v0.2.7
func (c *Catalog) CrowdGraphBatch(ctx context.Context, keys []core.RatingKey, maxPerKey, maxTargets int) (map[core.RatingKey][]core.RatingKey, map[core.RatingKey]core.Title, error)
CrowdGraphBatch reads stored co-engagement lists from catalog_crowd for a batch of keys without contacting TMDB, and resolves up to maxTargets of the referenced titles from the local mirror. Used for 2-hop crowd expansion.
func (*Catalog) CrowdRecommendations ¶
func (c *Catalog) CrowdRecommendations(ctx context.Context, key core.RatingKey, pages int, fetch bool) (titles []core.Title, fetched bool, err error)
CrowdRecommendations returns TMDB's crowd co-engagement list for a title (/recommendations only: never metadata similarity), best first, from up to pages pages (1–2). A stored list younger than the crowd TTL with enough pages is served from the local mirror. Otherwise, with fetch set, TMDB is asked and fetched reports it (callers budget those calls); without fetch, or when TMDB fails, a stale list is served when there is one and nil otherwise. Titles not yet released are dropped.
func (*Catalog) Facet ¶
func (c *Catalog) Facet(ctx context.Context, t core.MediaType, facet string) ([]core.FacetValue, error)
Facet returns value counts over the browsable set.
func (*Catalog) FilterVisibility ¶ added in v0.2.12
FilterVisibility enriches remote summaries with already-known local facets; there are no detail fetches or indexer calls. Unknown metadata stays unknown.
func (*Catalog) Grandchildren ¶
Grandchildren: all episodes of a show.
func (*Catalog) List ¶
func (c *Catalog) List(ctx context.Context, list string, t core.MediaType, limit int) ([]core.Title, error)
List returns TMDB curated/trending lists with a short in-memory TTL.
func (*Catalog) NextEpisode ¶
NextEpisode returns the next regular episode (specials, season 0, are skipped as a target; calling it on a special jumps to S1E1-order sequence).
func (*Catalog) PreviewVisibility ¶ added in v0.2.12
func (*Catalog) RecentEpisodes ¶
func (c *Catalog) RecentEpisodes(ctx context.Context, show core.RatingKey, since time.Time) ([]core.Title, error)
RecentEpisodes returns only local, released episode identities for artwork badges. Callers apply viewer state through core.State instead of a SQL join.
func (*Catalog) RecentMovies ¶ added in v0.2.9
RecentMovies reads the release-date index, without refreshing any title.
func (*Catalog) RecentShows ¶ added in v0.2.9
RecentShows only reads the local premiere-date index.
func (*Catalog) ReleaseMatches ¶ added in v0.2.8
func (c *Catalog) ReleaseMatches(ctx context.Context, releases []core.Release) (map[string][]core.Title, error)
ReleaseMatches joins a feed page to local metadata in one narrow scan. It never spends a metadata API call per feed item. Ambiguous names are ignored; explicit identifier conflicts always defeat a textual match. Season packs are expanded only to locally known episodes, never to invented episode identities.
func (*Catalog) Run ¶
Run performs an initial sync and then refreshes on RefreshInterval until ctx ends. Intended to be run in its own goroutine by the app wiring. With WithResumeSchedule the initial sync waits until the interval has passed since the last recorded sync.
func (*Catalog) RunTrends ¶ added in v0.2.12
RunTrends has an independent schedule: a slow catalogue crawl cannot delay charts. App lifetime owns and joins it, and only the cluster leader polls.
func (*Catalog) Search ¶
func (c *Catalog) Search(ctx context.Context, query string, types []core.MediaType, limit int) ([]core.Title, error)
Search reaches all of TMDB; matched titles join the browsable set.
func (*Catalog) SeedGenres ¶
SeedGenres pre-populates the genre id->name cache (tests, offline warm-up).
func (*Catalog) ShowTrajectories ¶ added in v0.2.7
func (c *Catalog) ShowTrajectories(ctx context.Context, shows []core.RatingKey) (map[core.RatingKey]core.ShowTrajectory, error)
ShowTrajectories computes season/episode rating trajectories for a batch of shows from local mirror data without contacting TMDB.
func (*Catalog) StandaloneEpisodes ¶ added in v0.2.7
func (c *Catalog) StandaloneEpisodes(ctx context.Context, minRating float64, limit int) ([]core.Title, error)
StandaloneEpisodes returns acclaimed mirrored episodes from anthology series or top-rated standalone chapters that can be watched without a multi-season commitment.
func (*Catalog) SyncProgress ¶
func (c *Catalog) SyncProgress() SyncProgress
SyncProgress returns the current sync progress snapshot.
func (*Catalog) TrendStatus ¶ added in v0.2.12
func (c *Catalog) TrendStatus(ctx context.Context) []TrendStatus
func (*Catalog) UpdateConfig ¶
UpdateConfig swaps the catalogue config (wire to config.Store.Subscribe).
func (*Catalog) UpsertTitle ¶
UpsertTitle stores a title (exported for tests/seeders and other components that need to insert known titles). Browsable/added_at are preserved once set.
func (*Catalog) WarmDetails ¶ added in v0.2.7
WarmDetails enriches up to limit browsable titles whose detailed metadata (credits, crew, keywords, studios, runtime, content rating, seasons and crowd recommendations) has not been fetched yet, plus returning series whose detail TTL has expired. New releases (<120 days old) are warmed first, followed by the most popular and most-voted titles. Settled titles are marked detail=1 and never re-queried.
type Estimate ¶
type Estimate struct {
Movies int `json:"movies"`
Shows int `json:"shows"`
Total int `json:"total"`
}
Estimate is a rough catalogue-size prediction for given thresholds. It is a heuristic from TMDB's documented popularity behaviour (a daily- recomputed, heavily right-skewed score) and public catalogue counts. Treat it as order-of-magnitude guidance for the setup UI, not a measurement.
func EstimateCatalogSize ¶
EstimateCatalogSize estimates browsable titles with MeetsThreshold's OR rule. The union of the popularity set and vote set overlaps substantially; the estimate uses max + 40% of the smaller set as the non-overlapping remainder.
type Mirror ¶
type Mirror struct {
// contains filtered or unexported fields
}
Mirror exposes bounded local reads for catalogue consumers. It owns schema knowledge; callers never compose SQL against catalog_* tables.
func (*Mirror) AirDates ¶ added in v0.2.34
func (m *Mirror) AirDates(ctx context.Context, now time.Time, premiere bool) (map[core.RatingKey]int64, error)
AirDates returns dates from locally mirrored regular episodes, excluding future air dates. For premieres, the highest numbered season with an aired episode must have an aired episode one.
func (*Mirror) CollectionArt ¶
func (*Mirror) Fingerprint ¶
func (m *Mirror) Fingerprint(ctx context.Context) (MirrorFingerprint, error)
func (*Mirror) Franchises ¶
type MirrorFingerprint ¶
type Option ¶
type Option func(*Catalog)
Option customises a Catalog.
func WithCrowdTTL ¶
WithCrowdTTL overrides how long co-engagement lists stay fresh.
func WithListTTL ¶
func WithResumeSchedule ¶
func WithResumeSchedule() Option
WithResumeSchedule makes Run continue the refresh schedule recorded in the database instead of syncing as soon as it starts: the first sync waits until RefreshInterval has passed since the last successful sync (by any node). A cluster uses it because Run restarts on every leader handoff; with no recorded sync Run still syncs at once.
func WithStaleAfter ¶
func WithSyncYears ¶
WithSyncYears bounds the year slices used when discover exceeds 500 pages.
type Stats ¶
type Stats struct {
Trending []TrendStatus `json:"trending"`
Movies int `json:"movies"`
Shows int `json:"shows"`
Seasons int `json:"seasons"`
Episodes int `json:"episodes"`
HomeReleaseChecked int `json:"homeReleaseChecked"`
HomeReleasePending int `json:"homeReleasePending"`
BrowsableMovies int `json:"browsableMovies"`
BrowsableShows int `json:"browsableShows"`
Collections int `json:"collections"`
LastSync time.Time `json:"lastSync,omitempty"`
Sync SyncProgress `json:"sync"`
}
Stats is the admin-UI snapshot of the mirror.
type SyncProgress ¶
type SyncProgress struct {
Running bool `json:"running"`
Phase string `json:"phase,omitempty"` // e.g. "movie/popularity", "tv/votes/1994"
PagesDone int `json:"pagesDone"`
TitlesSeen int `json:"titlesSeen"`
TitlesAdded int `json:"titlesAdded"`
StartedAt time.Time `json:"startedAt,omitempty"`
FinishedAt time.Time `json:"finishedAt,omitempty"`
LastError string `json:"lastError,omitempty"`
}
SyncProgress describes a running or last-finished catalogue sync.
type TMDB ¶
type TMDB interface {
MovieDetails(ctx context.Context, id int64) (*tmdb.MovieDetails, error)
TVDetails(ctx context.Context, id int64) (*tmdb.TVDetails, error)
SeasonDetails(ctx context.Context, tvID int64, season int) (*tmdb.SeasonDetails, error)
EpisodeExternalIDs(ctx context.Context, tvID int64, season, episode int) (*tmdb.ExternalIDs, error)
CollectionDetails(ctx context.Context, id int64) (*tmdb.CollectionDetails, error)
DiscoverMovies(ctx context.Context, p tmdb.DiscoverParams) (*tmdb.Paged[tmdb.Movie], error)
DiscoverTV(ctx context.Context, p tmdb.DiscoverParams) (*tmdb.Paged[tmdb.TVShow], error)
SearchMovies(ctx context.Context, q string, page int) (*tmdb.Paged[tmdb.Movie], error)
SearchTV(ctx context.Context, q string, page int) (*tmdb.Paged[tmdb.TVShow], error)
SearchMulti(ctx context.Context, q string, page int) (*tmdb.Paged[tmdb.MultiResult], error)
TrendingMovies(ctx context.Context, window string, page int) (*tmdb.Paged[tmdb.Movie], error)
TrendingTV(ctx context.Context, window string, page int) (*tmdb.Paged[tmdb.TVShow], error)
TrendingAll(ctx context.Context, window string, page int) (*tmdb.Paged[tmdb.MultiResult], error)
MovieList(ctx context.Context, list string, page int) (*tmdb.Paged[tmdb.Movie], error)
TVList(ctx context.Context, list string, page int) (*tmdb.Paged[tmdb.TVShow], error)
MovieSimilar(ctx context.Context, id int64, page int) (*tmdb.Paged[tmdb.Movie], error)
MovieRecommendations(ctx context.Context, id int64, page int) (*tmdb.Paged[tmdb.Movie], error)
TVSimilar(ctx context.Context, id int64, page int) (*tmdb.Paged[tmdb.TVShow], error)
TVRecommendations(ctx context.Context, id int64, page int) (*tmdb.Paged[tmdb.TVShow], error)
FindByExternalID(ctx context.Context, source, id string) (*tmdb.FindResult, error)
Genres(ctx context.Context, mediaType string) ([]tmdb.Genre, error)
}
TMDB is the subset of the TMDB client the catalogue needs. *tmdb.Client satisfies it.
type TrendStatus ¶ added in v0.2.12
type TrendStatus struct {
Source string `json:"source"`
Scope string `json:"scope"`
Window string `json:"window"`
MediaType core.MediaType `json:"mediaType"`
Titles int `json:"titles"`
FetchedAt *time.Time `json:"fetchedAt,omitempty"`
AttemptedAt *time.Time `json:"attemptedAt,omitempty"`
State string `json:"state"`
}