markers

package
v0.3.5 Latest Latest
Warning

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

Go to latest
Published: Nov 7, 2026 License: GPL-3.0 Imports: 23 Imported by: 0

Documentation

Overview

Package markers supplies release-duration-specific skip ranges. Remote lookups are opt-in and cached; no submissions are ever sent.

Index

Constants

View Source
const BreakConfidence = 0.90

Variables

View Source
var ErrBreakBandwidth = errors.New("act-break pre-air bandwidth budget unavailable")

Functions

func Probe added in v0.3.0

func Probe(ctx context.Context, baseURL, apiKey string) error

Probe checks that TheIntroDB answers, and that apiKey is accepted when one is set. An empty key probes the public lookup. 404 still means the service accepted the request.

Types

type BreakAnalyzer

type BreakAnalyzer struct {
	DB         *sql.DB
	FFmpeg     string
	OnCost     func(video, cpu, wall time.Duration)
	ScanPolicy func() BreakScanPolicy
	Hardware   func() core.HWAccel
	// OnEvidence is a trusted local diagnostic observer. The source lease remains
	// open during this synchronous call; observers must not close or retain it.
	// Nil performs no export and installs no HTTP endpoint.
	OnEvidence func(context.Context, BreakInput, BreakEvidence)
}

func (*BreakAnalyzer) Analyze

func (a *BreakAnalyzer) Analyze(ctx context.Context, in BreakInput) (points []BreakPoint, err error)

func (*BreakAnalyzer) CachedBreakPoints

func (a *BreakAnalyzer) CachedBreakPoints(ctx context.Context, in BreakInput) ([]BreakPoint, error)

CachedBreakPoints returns only locally stored positive evidence; no resolve, probe or decode. Full analyses and partial playout facts reinforce each other.

func (*BreakAnalyzer) ObservePoints

func (a *BreakAnalyzer) ObservePoints(ctx context.Context, in BreakInput, points []BreakPoint) error

ObservePoints merges verified facts from partial playout into a separate release cache. It never represents a partial/empty viewing as a full scan.

type BreakEvidence

type BreakEvidence struct {
	ProviderStatsKnown bool           `json:"providerStatsKnown"`
	ProviderRequests   int64          `json:"providerRequests"`
	ProviderBytes      int64          `json:"providerBytes"`
	ProviderErrors     int64          `json:"providerErrors"`
	DecodedThroughMS   int64          `json:"decodedThroughMs"`
	CoverageComplete   bool           `json:"coverageComplete"`
	Mode               string         `json:"mode,omitempty"`
	Backend            string         `json:"backend,omitempty"`
	AudioCandidates    []BreakSignal  `json:"audioCandidates,omitempty"`
	KeyframeBlack      []BreakSignal  `json:"keyframeBlack,omitempty"`
	CandidateWindows   []BreakSignal  `json:"candidateWindows,omitempty"`
	ReadBytesKnown     bool           `json:"readBytesKnown"`
	Partial            bool           `json:"partial"`
	ReservedBytes      int64          `json:"reservedBytes"`
	ReadBytes          int64          `json:"readBytes"`
	Version            int            `json:"version"`
	Key                core.RatingKey `json:"key"`
	DurationMS         int64          `json:"durationMs"`
	CacheHit           bool           `json:"cacheHit"`
	Decoded            bool           `json:"decoded"`
	Complete           bool           `json:"complete"`
	Chapters           []BreakSignal  `json:"chapters"`
	Black              []BreakSignal  `json:"black"`
	Silence            []BreakSignal  `json:"silence"`
	Scenes             []BreakSignal  `json:"scenes"`
	Points             []BreakPoint   `json:"points"`
	CPUMS              int64          `json:"cpuMs"`
	WallMS             int64          `json:"wallMs"`
}

BreakEvidence intentionally contains neither source grants nor media. A cache hit reports CacheHit=true, Decoded=false and cached accepted points; historical decode candidates are unavailable unless a local observer saved them earlier. Complete=false candidates are partial and must not enter quality denominators.

type BreakInput

type BreakInput struct {
	ShouldYield       func() bool                 `json:"-"`
	Drain             func(context.Context) error `json:"-"`
	ProviderStatsBase *[3]int64                   `json:"-"`

	Key           core.RatingKey `json:"key"`
	ReleaseKey    string
	ReleaseID     string
	Revision      string
	URL           string                       `json:"-"`
	Info          core.MediaInfo               `json:"-"`
	Close         func()                       `json:"-"`
	BytesRead     func() int64                 `json:"-"`
	ReadErrors    func() int64                 `json:"-"`
	ProviderStats func() (int64, int64, int64) `json:"-"`
	// contains filtered or unexported fields
}

BreakInput is leased exclusively for background analysis. ReleaseKey must bind the source revision, payload, file and duration, never just a catalogue key. Close releases the source even when a cached analysis is reused.

func PlaybackBreakInput

func PlaybackBreakInput(ps *core.PlaySession) BreakInput

PlaybackBreakInput binds playout observations to the same immutable release key as pre-air analysis, without performing source reads or resolving media.

type BreakPoint

type BreakPoint struct {
	AtMS       int64   `json:"atMs"`
	Source     string  `json:"source"`
	Confidence float64 `json:"confidence"`
}

func ChapterBreaks

func ChapterBreaks(info core.MediaInfo) []BreakPoint

Generic chapter numbers, intros and credits are deliberately not evidence.

func SelectBreaks

func SelectBreaks(points []BreakPoint, duration int64, k int, spacing int64) []BreakPoint

SelectBreaks solves the ordered K-point spacing problem with dynamic programming. Confidence dominates; spacing towards ideal act lengths breaks ties. It returns fewer than K whenever evidence or safe spacing is lacking.

type BreakScanPolicy

type BreakScanPolicy struct {
	DailyBytes        int64 `json:"dailyBytes,omitempty"`
	ReleaseBytes      int64 `json:"releaseBytes,omitempty"`
	LargeReleaseBytes int64 `json:"largeReleaseBytes,omitempty"`
	AllowLarge        bool  `json:"allowLarge,omitempty"`
}

BreakScanPolicy bounds extra pre-air source reads. Zero uses defaults; negative daily/release limits disable full scans. Chapters/cache bypass it.

type BreakSignal

type BreakSignal struct {
	StartMS      int64    `json:"startMs"`
	EndMS        int64    `json:"endMs"`
	Score        *float64 `json:"score,omitempty"`
	ChapterMatch bool     `json:"chapterMatch,omitempty"`
}

BreakSignal contains measured source-media times. Score is the actual scdet percent when supplied by ffmpeg, never a synthesized confidence. It is absent for black/silence intervals and chapters, which have no measured score.

type BreakTap

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

BreakTap samples already decoded limited-range Y and signed little-endian PCM. It performs no I/O, allocation per frame, buffering of media or extra decode. Every frame samples <=2304 luma values plus its cheap audio RMS envelope. Only verified positive points survive a partial viewing; no empty full-scan cache entry is created from a tune, seek or interrupted programme.

func NewBreakTap

func NewBreakTap(duration time.Duration) *BreakTap

func (*BreakTap) Frame

func (t *BreakTap) Frame(at, span time.Duration, y, pcm []byte, width, height int)

func (*BreakTap) Points

func (t *BreakTap) Points() []BreakPoint

type Coverage

type Coverage struct {
	ShowID          int64 `json:"showId"`
	CachedEpisodes  int   `json:"cachedEpisodes"`
	IntroEpisodes   int   `json:"introEpisodes"`
	CreditsEpisodes int   `json:"creditsEpisodes"`
	RecapEpisodes   int   `json:"recapEpisodes"`
}

type DialogueCue

type DialogueCue struct {
	Start, End time.Duration
}

DialogueCue is one already-available subtitle cue time (text not needed).

type Lookup added in v0.3.0

type Lookup struct {
	Enabled     bool
	APIKey      string
	IntroOff    bool
	CreditsOff  bool
	SourceOrder []string
}

Lookup is the live skip-marker configuration. IntroOff and CreditsOff hide those buttons. The zero value offers both.

type Marker

type Marker struct {
	Type    string `json:"type"`
	StartMS int64  `json:"startTimeOffset"`
	EndMS   int64  `json:"endTimeOffset"`
	Final   bool   `json:"final"`
	Source  string `json:"source"`
}

func ApplyStingers

func ApplyStingers(markers []Marker, stingers []Stinger, duration time.Duration) []Marker

ApplyStingers cuts stinger ranges out of credits markers, splitting one credits marker into the pieces before and after each stinger. Pieces under two seconds are dropped (a breath of crawl is not skippable). Afterwards exactly the last credits marker may be Final, and only when it reaches the end of the media — the shape PMS emits.

func FromChapters

func FromChapters(info core.MediaInfo) []Marker

FromChapters uses explicit chapter labels only; generic Chapter 01 is not evidence.

type Options

type Options struct {
	Config func() Lookup // current IntroDB lookup and which skip buttons are offered

	Logger     *slog.Logger
	DB         *sql.DB
	Enabled    bool
	BaseURL    string
	APIKey     string
	HTTPClient *http.Client
	TTL        time.Duration
	Now        func() time.Time

	// Aligner, when set, shifts TheIntroDB markers from the reference
	// runtime they were submitted against into the played release's
	// timeline (dialogue-based, no video downloaded).
	Aligner core.TimelineAligner
	// AlignTimeout bounds one marker-shifting alignment (default 10s);
	// on failure the unshifted markers are kept.
	AlignTimeout time.Duration
	// Dialogue supplies already-available subtitle cue times for the
	// played release (cached sidecars), used for stinger detection.
	Dialogue func(ctx context.Context, key core.RatingKey, info core.MediaInfo) ([]DialogueCue, bool)
}

type RefreshTarget added in v0.3.3

type RefreshTarget struct {
	Key        core.RatingKey
	Duration   time.Duration
	LastPlayed time.Time
}

type Service

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

func New

func New(o Options) *Service

func (*Service) CachedMarkers added in v0.3.0

func (s *Service) CachedMarkers(ctx context.Context, key core.RatingKey, info core.MediaInfo) ([]Marker, error)

CachedMarkers uses chapters and locally cached ranges only. It never fetches or aligns remote markers, making it safe for non-resolving detail pages.

func (*Service) Coverage

func (s *Service) Coverage(ctx context.Context, show int64) (Coverage, error)

Coverage counts observed cached episodes; it does not imply coverage of the show's entire catalogue or that an official client displayed skip controls.

func (*Service) DeletePlugin added in v0.3.3

func (s *Service) DeletePlugin(ctx context.Context, source string) error

DeletePlugin removes cached results when a plugin is disabled or removed.

func (*Service) Handler

func (s *Service) Handler() http.Handler

Handler serves GET /shows/{TMDBID}, behind the owner's admin authentication.

func (*Service) Markers

func (s *Service) Markers(ctx context.Context, key core.RatingKey, info core.MediaInfo) ([]Marker, error)

Markers returns the skip ranges for one probed release: chapter and remote markers (shifted into this release's timeline when they were submitted against a different cut), with stinger scenes cut out of the credits ranges.

func (*Service) Prune

func (s *Service) Prune(ctx context.Context) (int64, error)

Prune deletes expired rows from the remote-lookup cache (markers_cache). An expired row is never served, so this only reclaims space; the next playback re-fetches as it would anyway.

func (*Service) Put

func (s *Service) Put(ctx context.Context, key core.RatingKey, d time.Duration, ranges []Marker) error

func (*Service) PutPlugin added in v0.3.3

func (s *Service) PutPlugin(ctx context.Context, key core.RatingKey, d time.Duration, source string, ranges []Marker) error

PutPlugin stores a background result separately for each source.

func (*Service) RefreshTargets added in v0.3.3

func (s *Service) RefreshTargets(ctx context.Context) ([]RefreshTarget, error)

func (*Service) RememberRefreshTarget added in v0.3.3

func (s *Service) RememberRefreshTarget(ctx context.Context, key core.RatingKey, duration time.Duration) error

RememberRefreshTarget persists the bounded set of recently played and upcoming titles.

type Stinger

type Stinger struct {
	Start, End time.Duration
}

Stinger is a scene inside the credits range that must not be skipped.

func StingersFromChapters

func StingersFromChapters(info core.MediaInfo) []Stinger

StingersFromChapters finds post-credits scenes in the chapter layout: explicitly named stinger chapters, and the structural pattern of a short chapter sandwiched after a long credits chapter near the tail.

func StingersFromDialogue

func StingersFromDialogue(cues []DialogueCue, creditsStart, duration time.Duration) []Stinger

StingersFromDialogue finds dialogue clusters that resume well inside the credits range after a long cue gap: the shape of a mid-/post-credits scene. Lyrics running continuously from the credits start never qualify, because their first cue follows the credits start without a long gap and their cues continue without one.

Jump to

Keyboard shortcuts

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