Documentation
¶
Overview ¶
Package prerolls stores owner pre-roll videos, converts them for direct play and chooses at most one to play before a movie or episode (GitHub #298). Plex prepends the chosen pre-roll to the play queue next to Cinema Trailers; Jellyfin and Emby list it as an intro.
Selection never scans the library: per-play work is one cached list of pre-rolls, one bounded query of the viewer's recent pre-roll plays and checks of the single feature title.
Index ¶
- Constants
- Variables
- type Activity
- type Audience
- type Candidate
- type Converter
- type Decision
- type FFmpeg
- type Frequency
- type Input
- type Options
- type PlayRow
- type Preroll
- type Reason
- type Rules
- type Service
- func (s *Service) Active(ctx context.Context) bool
- func (s *Service) AttachPath(ctx context.Context, id, path string) (Preroll, error)
- func (s *Service) Clip(ctx context.Context, key core.RatingKey) (core.PrerollClip, bool)
- func (s *Service) Create(ctx context.Context, in Input) (Preroll, error)
- func (s *Service) Delete(ctx context.Context, id string) error
- func (s *Service) Files(ctx context.Context, id string) (string, string, error)
- func (s *Service) Get(ctx context.Context, id string) (Preroll, error)
- func (s *Service) ImportPMS(ctx context.Context, value string) ([]Preroll, error)
- func (s *Service) List(ctx context.Context) ([]Preroll, string, error)
- func (s *Service) Pick(ctx context.Context, req core.PrerollRequest) (core.PrerollClip, bool)
- func (s *Service) Plays(ctx context.Context, id string, limit int) ([]PlayRow, error)
- func (s *Service) Prune(ctx context.Context) error
- func (s *Service) Run(ctx context.Context)
- func (s *Service) Selection(ctx context.Context) string
- func (s *Service) SetSelection(ctx context.Context, mode string) error
- func (s *Service) Simulate(ctx context.Context, in SimulateInput) (Decision, error)
- func (s *Service) Started(ctx context.Context, play core.PrerollPlay)
- func (s *Service) Thumb(ctx context.Context, key core.RatingKey) (string, bool)
- func (s *Service) Update(ctx context.Context, id string, in Input) (Preroll, error)
- func (s *Service) Upload(ctx context.Context, id string, r io.Reader) (Preroll, error)
- type SimulateInput
- type ValidationError
- type Video
- type Winner
Constants ¶
const ( StateNone = "" StateProcessing = "processing" StateReady = "ready" StateFailed = "failed" ProblemUnreadable = "unreadable" ProblemTooLong = "too_long" ProblemConvertFailed = "convert_failed" SourceUpload = "upload" SourcePath = "path" )
Video states and problems (owner API contract).
const ( SelectPriority = "priority" SelectShuffle = "shuffle" )
Selection modes.
const ( FrequencyAlways = "always" FrequencyDaily = "daily" FrequencyHours = "hours" FrequencyTitle = "title" )
Frequency modes.
const ( // MaxDuration is the longest clip accepted. MaxDuration = 3 * time.Minute // MaxRetention bounds the play log. MaxRetention = 90 * 24 * time.Hour )
Limits.
const ( ReasonDisabled = "disabled" ReasonNotReady = "not_ready" ReasonUnsupportedApp = "unsupported_app" ReasonResumed = "resumed" ReasonKind = "kind" ReasonFirstEpisode = "first_episode" ReasonBingeGap = "binge_gap" ReasonGenre = "genre" ReasonExcludedGenre = "excluded_genre" ReasonCollection = "collection" ReasonRating = "rating" ReasonDateWindow = "date_window" ReasonAudience = "audience" ReasonNetwork = "network" ReasonDevice = "device" ReasonFrequency = "frequency" ReasonLowerPriority = "lower_priority" ReasonShuffledOut = "shuffled_out" )
Reason codes (owner API contract), in evaluation order.
const ( DefaultMaxFileBytes int64 = 500 << 20 DefaultMaxTotalBytes int64 = 5 << 30 )
Default storage caps.
Variables ¶
var ErrNotFound = core.ErrNotFound
ErrNotFound is returned for an unknown pre-roll.
var ErrStorageFull = errors.New("prerolls: storage full")
ErrStorageFull is returned when the pre-roll folder is at its total cap.
var ErrTooLarge = errors.New("prerolls: file too large")
ErrTooLarge is returned when an upload exceeds the per-file cap.
Functions ¶
This section is empty.
Types ¶
type Activity ¶
type Activity interface {
History(ctx context.Context, userID int64, since time.Time, limit int) ([]core.HistoryEntry, error)
InProgress(ctx context.Context, userID int64, limit int) ([]core.WatchStatus, error)
}
Activity is the part of watch state the binge rule reads: the viewer's most recent finished play and most recent stopped (resumable) play. Both are single indexed rows.
type Audience ¶
type Audience struct {
Mode string `json:"mode"` // everyone | only | except
Viewers []int64 `json:"viewers"`
}
Audience limits the viewers a pre-roll plays for.
type Candidate ¶
type Candidate struct {
ID string `json:"id"`
Name string `json:"name"`
Plays bool `json:"plays"`
Reasons []Reason `json:"reasons"`
}
Candidate is one pre-roll's verdict for a play.
type Converter ¶
type Converter interface {
// Probe reads the source's streams and duration.
Probe(ctx context.Context, path string) (*core.MediaInfo, error)
// Convert writes an H.264 High / AAC stereo MP4 (at most 1080p,
// faststart) to out.
Convert(ctx context.Context, in, out string, info *core.MediaInfo) error
// Thumbnail writes one JPEG frame taken at offset to out.
Thumbnail(ctx context.Context, in, out string, at time.Duration) error
}
Converter probes a source clip and converts it for direct play.
type Decision ¶
type Decision struct {
Winner *Winner `json:"winner"`
Candidates []Candidate `json:"candidates"`
// contains filtered or unexported fields
}
Decision is the outcome of one selection.
type FFmpeg ¶
type FFmpeg struct {
// Path returns the ffmpeg executable; "" means unavailable.
Path func() string
// ProbeFunc reads media info (the transcoder's ffprobe).
ProbeFunc func(ctx context.Context, input string) (*core.MediaInfo, error)
}
FFmpeg converts with the bundled FFmpeg (internal/transcode discovery).
type Input ¶
type Input struct {
Name string `json:"name"`
Rules Rules `json:"rules"`
Frequency Frequency `json:"frequency"`
Priority int `json:"priority"`
Order string `json:"order"`
Enabled *bool `json:"enabled"`
}
Input is the create/update body.
type Options ¶
type Options struct {
DB *sql.DB
// Dir holds converted clips and thumbnails (<data>/prerolls).
Dir string
// MaxFileBytes caps one source file; MaxTotalBytes caps the folder.
MaxFileBytes, MaxTotalBytes int64
// Converter probes and converts clips; nil leaves new clips processing
// until FFmpeg is available.
Converter Converter
// Activity feeds the binge rule; nil disables it.
Activity Activity
// Title loads one title (an episode's show for its genres, rating and
// collections; a simulated feature). Nil skips the show lookup.
Title func(ctx context.Context, key core.RatingKey) (*core.Title, error)
// InCollection reports whether key is a member of owner collection id.
// Nil makes collection rules never match.
InCollection func(ctx context.Context, id string, key core.RatingKey) bool
// Viewer loads a viewer for the simulator.
Viewer func(ctx context.Context, id int64) (*core.User, error)
Logger *slog.Logger
Now func() time.Time
// Rand returns a number in [0,n); nil uses math/rand.
Rand func(n int) int
}
Options are the service dependencies.
type PlayRow ¶
type PlayRow struct {
ViewerID int64 `json:"viewerId"`
ViewerName string `json:"viewerName"`
Title string `json:"title"`
RatingKey string `json:"ratingKey"`
At time.Time `json:"at"`
Client string `json:"client"`
}
PlayRow is one entry of a pre-roll's play log.
type Preroll ¶
type Preroll struct {
ID string `json:"id"`
Name string `json:"name"`
Enabled bool `json:"enabled"`
Video Video `json:"video"`
Rules Rules `json:"rules"`
Frequency Frequency `json:"frequency"`
Priority int `json:"priority"`
Order string `json:"order"`
Plays30d int `json:"plays30d"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
// contains filtered or unexported fields
}
Preroll is the owner API representation.
type Rules ¶
type Rules struct {
Kinds []string `json:"kinds"`
FirstEpisodeOnly bool `json:"firstEpisodeOnly"`
BingeGapMinutes int `json:"bingeGapMinutes"`
Genres []string `json:"genres"`
ExcludeGenres []string `json:"excludeGenres"`
Collections []string `json:"collections"`
MaxContentRating string `json:"maxContentRating"`
DateFrom string `json:"dateFrom"` // MM-DD, inclusive
DateTo string `json:"dateTo"` // MM-DD, inclusive; may wrap the new year
Audience Audience `json:"audience"`
Network string `json:"network"`
Devices []string `json:"devices"`
}
Rules decide which plays a pre-roll may precede. Empty lists match all.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service implements the owner API and core.Prerolls.
func (*Service) Active ¶
Active implements core.Prerolls: whether any pre-roll is enabled and ready, read from the cached list.
func (*Service) AttachPath ¶
AttachPath uses a file on the server as the clip source. The file is only read; Ether converts it into its own folder.
func (*Service) Clip ¶
Clip implements core.Prerolls. A disabled pre-roll already queued for a play still plays; a deleted or reconverting one does not.
func (*Service) Files ¶
Files returns a pre-roll's converted video and thumbnail paths ("" when absent) for the owner preview.
func (*Service) ImportPMS ¶
ImportPMS creates pre-rolls from a Plex Media Server CinemaTrailersPrerollID value. PMS plays semicolon-separated paths in order and picks one of comma-separated paths at random. Ether plays one pre-roll per play, so each semicolon group gets a lower priority than the one before it (the first group wins) and the paths inside a group share a priority (ties are chosen at random). Every path is validated before anything is created.
func (*Service) List ¶
List returns every pre-roll with its 30-day play count and the selection mode.
func (*Service) Pick ¶
func (s *Service) Pick(ctx context.Context, req core.PrerollRequest) (core.PrerollClip, bool)
Pick implements core.Prerolls: it chooses at most one pre-roll for a play. Nothing is recorded until Started.
func (*Service) SetSelection ¶
SetSelection stores the selection mode.
func (*Service) Simulate ¶
Simulate explains which pre-roll a play would get, through the same rules as playback, without recording anything.
func (*Service) Started ¶
func (s *Service) Started(ctx context.Context, play core.PrerollPlay)
Started implements core.Prerolls: it records one play per viewer, pre-roll and feature, however many requests the client makes.
type SimulateInput ¶
type SimulateInput struct {
ViewerID int64 `json:"viewerId"`
RatingKey string `json:"ratingKey"`
Device string `json:"device"`
Away bool `json:"away"`
Resume bool `json:"resume"`
At string `json:"at"`
// Client optionally names the app, to explain unsupported apps.
Client string `json:"client,omitempty"`
}
SimulateInput is the simulator request.
type ValidationError ¶
type ValidationError struct{ Field, Message string }
ValidationError is an owner input problem (HTTP 400).
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
type Video ¶
type Video struct {
State string `json:"state"`
DurationMs int64 `json:"durationMs"`
Width int `json:"width"`
Height int `json:"height"`
ThumbURL string `json:"thumbUrl"`
PreviewURL string `json:"previewUrl"`
Problem string `json:"problem"`
Source string `json:"source"`
}
Video describes the converted clip.