Documentation
¶
Overview ¶
Package core holds the domain types and the interfaces between Ether's components. It has no dependencies on other Ether packages, so every component can import it. Changing a type here is a contract change: keep edits additive.
Index ¶
- Constants
- Variables
- func CheckReleaseHealth(ctx context.Context, hc HealthChecker, releaseID string, nzb []byte) (bool, error)
- func ClassifyDevice(product, platform, device string) string
- func DaypartAt(t time.Time) string
- func IsWeekend(t time.Time) bool
- func TargetReleaseID(releaseID string, t ReleaseTarget) string
- type ActivityDay
- type AlignRequest
- type ArticleFetcher
- type ArticlePart
- type Auth
- type BrowseQuery
- type Catalog
- type Chapter
- type ClientCaps
- type Collection
- type CollectionArtwork
- type CollectionPreview
- type CollectionRotate
- type CollectionRow
- type CollectionRows
- type CollectionRule
- type CollectionSchedule
- type CollectionSource
- type CollectionSpec
- type CollectionTargets
- type CollectionTemplate
- type Collections
- type ContextualRecommender
- type CustomFormat
- type DASHSession
- type DecideOptions
- type Decision
- type DecisionKind
- type DownloadRemuxRequest
- type DownloadRemuxer
- type Engine
- type FacetValue
- type Filter
- type FormatSpecification
- type GoodputSession
- type HLSLadderSession
- type HLSRequest
- type HLSRung
- type HLSSession
- type HWAccel
- type HealthChecker
- type HistoryEntry
- type HomeLayout
- type HomePreview
- type HomeRow
- type Hub
- type HubContext
- type HubImpression
- type IDs
- type ImageURLFunc
- type ImpressionSink
- type Indexer
- type IntentTracker
- type KeyKind
- type KeyframePoint
- type LayoutPage
- type LayoutTuning
- type LayoutWhen
- type LookupResult
- type MediaInfo
- type MediaSource
- type MediaType
- type MicroReason
- type MicroReasoner
- type Page
- type Person
- type PersonalScorer
- type PlaySession
- type Playback
- type Playlist
- type Playlists
- type PostPlayPick
- type PostPlayer
- type PrewarmOptions
- type Prewarmer
- type Priority
- type QualityProfile
- type RankerFeature
- type RankerInsight
- type RatingKey
- func CollectionKey(id int64) RatingKey
- func EphemeralSmartKey(seq int64) RatingKey
- func EpisodeKey(show int64, season, episode int) RatingKey
- func ExtraKey(id int64) RatingKey
- func LocalKey(id int64) RatingKey
- func MovieKey(tmdb int64) RatingKey
- func ParseRatingKey(s string) (RatingKey, error)
- func PlaylistKey(id int64) RatingKey
- func SeasonKey(show int64, season int) RatingKey
- func ShowKey(tmdb int64) RatingKey
- func SmartKey(id int64) RatingKey
- func (k RatingKey) Episode() int
- func (k RatingKey) ExtraID() int64
- func (k RatingKey) IsEphemeral() bool
- func (k RatingKey) Kind() KeyKind
- func (k RatingKey) Parent() RatingKey
- func (k RatingKey) Season() int
- func (k RatingKey) String() string
- func (k RatingKey) TMDB() int64
- func (k RatingKey) Valid() bool
- type Recommender
- type Release
- type ReleaseHealthChecker
- type ReleaseInfo
- type ReleaseTarget
- type ReleaseVariant
- type RemuxRequirer
- type Resolution
- type ResolveRequest
- type Resolver
- type RowDescriptor
- type RowInsight
- type RowPolicy
- type SearchRequest
- type SegmentCache
- type SortOrder
- type Source
- type State
- type StreamInfo
- type TasteDecade
- type TasteHabits
- type TasteLanguage
- type TasteOverride
- type TastePerson
- type TasteProfile
- type TasteWeight
- type TimelineAligner
- type Title
- type Transcoder
- type TrickplayProvider
- type TrickplaySource
- type User
- type UserTargets
- type VariantPlayback
- type VariantResolver
- type VibeQuery
- type VibeRefiner
- type VibeSearcher
- type ViewerActivity
- type ViewerLayout
- type ViewerStats
- type WatchStatus
Constants ¶
const ( ProtocolUsenet = "" // the zero value: an NZB from a Newznab indexer ProtocolTorrent = "torrent" // a Torznab result streamed through a debrid service )
Release protocols (Release.Protocol).
const ( DeviceTV = "tv" DeviceMobile = "mobile" DeviceWeb = "web" DeviceDesktop = "desktop" )
Device classes for HubContext.Device.
const ( DaypartMorning = "morning" // 05:00–11:59 DaypartAfternoon = "afternoon" // 12:00–16:59 DaypartEvening = "evening" // 17:00–21:59 DaypartLate = "late" // 22:00–04:59 )
Dayparts for HubContext.Daypart, in the server's local time.
const ( RowSourceEngine = "engine" // generated by the personalization engine RowSourceOwner = "owner" // an owner-designed collection RowSourcePlex = "plex" // built by the Plex front end (Continue Watching, playlists) )
Row sources for HomeRow.Source.
const ( ProtocolHLS = "hls" ProtocolDASH = "dash" )
Streaming protocols of HLSRequest.Protocol.
const ( PrewarmDetail = "detail" // a detail page was opened PrewarmTrailer = "trailer" // a trailer or Cinema Trailer for the title is playing PrewarmPostPlay = "postplay" // the viewer is near the end of something and this is up next )
Reasons for PrewarmOptions.Reason.
const ( EphemeralSmartBase int64 = 800_000_000_000_000 EphemeralSmartEnd int64 = 900_000_000_000_000 )
EphemeralSmartBase and EphemeralSmartEnd bound the KindSmart payloads reserved for ephemeral generated collections. The range stops where personalize's Year in Review collections begin (payload 9*10^14 and up), and database smart-collection ids stay far below it.
Variables ¶
var ( ErrNotFound = errors.New("not found") ErrNotAvailable = errors.New("not available yet") // no acceptable release exists ErrArticleMissing = errors.New("article missing on all providers") ErrArticleCorrupt = errors.New("corrupt article or media data") ErrReleaseRejected = errors.New("release cannot be streamed") ErrBudgetExhausted = errors.New("indexer daily budget exhausted") ErrStreamLimit = errors.New("stream limit reached") )
var ErrDownloadBudget = errors.New("download: temporary disk budget exceeded")
ErrDownloadBudget is returned when a download would exceed the temporary disk budget for remuxed files.
var ErrDownloadNeedsTranscode = errors.New("download: source codecs cannot be copied to MP4")
ErrDownloadNeedsTranscode is returned by DownloadRemuxer when the resolved streams cannot be copied into an MP4 a mobile client plays (e.g. AV1/VP9 video, TrueHD/DTS audio). Ether downloads never transcode offline, so the download decision reports this honestly as an error instead of silently re-encoding.
var ErrNotCached = fmt.Errorf("not cached by any debrid provider: %w", ErrNotAvailable)
ErrNotCached reports that no debrid provider can serve a torrent release instantly. It is a per-release skip, never proof the release is broken, so it wraps ErrNotAvailable and is not a permanent failure.
Functions ¶
func CheckReleaseHealth ¶
func CheckReleaseHealth(ctx context.Context, hc HealthChecker, releaseID string, nzb []byte) (bool, error)
CheckReleaseHealth runs hc.CheckRelease when hc implements it, else hc.Check.
func ClassifyDevice ¶
ClassifyDevice maps a Plex client's X-Plex-Product, X-Plex-Platform and X-Plex-Device headers onto a device class (DeviceTV, DeviceMobile, DeviceWeb, DeviceDesktop).
- TV: Apple TV, Android TV / Google TV / Fire TV / Shield, Roku, smart TVs (Samsung Tizen, LG webOS, Vizio, Hisense, Sony...), game consoles (Xbox, PlayStation) and set-top boxes.
- Desktop: Plex HTPC, Plex for Windows/Mac/Linux, Plex Media Player.
- Web: Plex Web in any browser.
- Mobile: Plex for iOS / Android phones and tablets.
Unknown clients fall back on their platform, then on DeviceWeb.
func DaypartAt ¶
DaypartAt returns the daypart of t in t's own location: morning 05:00–11:59, afternoon 12:00–16:59, evening 17:00–21:59, late 22:00–04:59.
func IsWeekend ¶
IsWeekend reports whether t (in its own location) falls in the weekend viewing window: Friday from 17:00 through Sunday. Friday night behaves like a weekend night (films, longer sessions), Sunday daytime does too.
func TargetReleaseID ¶
func TargetReleaseID(releaseID string, t ReleaseTarget) string
TargetReleaseID derives a stable release ID for one episode of a multi-file release, so health, pins, cache and bad marks apply to the episode file rather than the whole pack.
Types ¶
type ActivityDay ¶
type ActivityDay struct {
Date string `json:"date"` // YYYY-MM-DD, server local time
Plays int `json:"plays"`
Hours float64 `json:"hours"`
UniqueViewers int `json:"uniqueViewers"`
}
ActivityDay is one day of server-wide viewing.
type AlignRequest ¶
type AlignRequest struct {
Key RatingKey `json:"key"`
From Release `json:"from"`
To Release `json:"to"`
FromDuration time.Duration `json:"fromDuration,omitempty"`
ToDuration time.Duration `json:"toDuration,omitempty"`
}
AlignRequest names two releases of the same title whose timelines should be matched (for example the failing and the replacement release of a mid-stream failover, or a marker source's reference cut and the release being played).
type ArticleFetcher ¶
type ArticleFetcher interface {
// Body returns the decoded payload of one article (one NZB segment).
// Returns ErrArticleMissing when no provider has it.
Body(ctx context.Context, messageID string, groups []string, prio Priority) ([]byte, error)
// BodyPart is Body plus the article's yEnc =ybegin/=ypart header, which
// gives the segment's exact byte range inside the posted file.
BodyPart(ctx context.Context, messageID string, groups []string, prio Priority) ([]byte, ArticlePart, error)
// Stat reports whether any provider has the article.
Stat(ctx context.Context, messageID string) (bool, error)
}
ArticleFetcher fetches and yEnc-decodes articles across the configured providers, primary first, backfilling from block accounts.
type ArticlePart ¶
type ArticlePart struct {
Name string `json:"name"`
FileSize int64 `json:"fileSize"`
Part int `json:"part"`
Total int `json:"total"`
Begin int64 `json:"begin"`
End int64 `json:"end"`
}
ArticlePart is the yEnc header of one article. Begin and End are 1-based inclusive offsets in the posted file, as in =ypart; for single-part posts Begin is 1 and End is FileSize.
type BrowseQuery ¶
type Catalog ¶
type Catalog interface {
Get(ctx context.Context, key RatingKey) (*Title, error)
// Children: seasons of a show, episodes of a season.
Children(ctx context.Context, key RatingKey) ([]Title, error)
// Grandchildren: all episodes of a show.
Grandchildren(ctx context.Context, key RatingKey) ([]Title, error)
Browse(ctx context.Context, q BrowseQuery) (Page, error)
// Search reaches all of TMDB; matched titles join the browsable set.
Search(ctx context.Context, query string, types []MediaType, limit int) ([]Title, error)
// Lists from TMDB: "trending_day", "trending_week", "top_rated", "now_playing", "popular", "airing_today", "upcoming".
List(ctx context.Context, list string, t MediaType, limit int) ([]Title, error)
Similar(ctx context.Context, key RatingKey, limit int) ([]Title, error)
Collection(ctx context.Context, tmdbCollection int64) (name string, items []Title, err error)
// Facets for filter UIs: "genre", "year", "decade", "contentRating", "studio", "country".
Facet(ctx context.Context, t MediaType, facet string) ([]FacetValue, error)
// NextEpisode returns the episode after the given one, or ErrNotFound.
NextEpisode(ctx context.Context, episode RatingKey) (*Title, error)
// ByExternalID looks up a title by imdb ("tt123") or tvdb id.
ByExternalID(ctx context.Context, source, id string) (*Title, error)
}
Catalog is Ether's library: a local mirror of a TMDB slice plus on-demand fetches for anything else.
type ClientCaps ¶
type ClientCaps struct {
Product string `json:"product"` // X-Plex-Product: "Plex Web", "Plex for iOS", "Plex for Apple TV"
Platform string `json:"platform"` // X-Plex-Platform
PlatformVersion string `json:"platformVersion"`
Device string `json:"device"`
DeviceName string `json:"deviceName"`
ClientID string `json:"clientId"` // X-Plex-Client-Identifier
Version string `json:"version"`
MaxHeight int `json:"maxHeight"` // 2160, 1080, 720...
HDR10 bool `json:"hdr10"`
DolbyVision bool `json:"dolbyVision"`
VideoCodecs []string `json:"videoCodecs"` // h264, hevc, av1, vp9
AudioCodecs []string `json:"audioCodecs"` // aac, ac3, eac3, truehd, dts, opus, flac
Containers []string `json:"containers"` // mp4, mkv, mpegts
HLSContainers []string `json:"hlsContainers,omitempty"` // explicit segmented output targets; distinct from direct-play containers
MaxBitrateKbps int `json:"maxBitrateKbps"` // 0 = unlimited
SubtitleCodecs []string `json:"subtitleCodecs,omitempty"` // native image/text subtitle rendering
DVProfiles []int `json:"dvProfiles,omitempty"` // explicit Dolby Vision profiles; empty uses product defaults
VideoProfiles map[string][]string `json:"videoProfiles,omitempty"`
VideoLevels map[string]int `json:"videoLevels,omitempty"` // codec level in ffprobe units (H.264 51 = 5.1)
Remote bool `json:"remote"` // not on the server's LAN
// Download marks a request for an offline download (Plex Downloads /
// Sync) rather than live playback: the resolver prefers compact,
// mobile-native releases and the whole file is remuxed (additive).
Download bool `json:"download,omitempty"`
}
ClientCaps is what a Plex client can play, derived from X-Plex-* headers, the client profile and its declared limits.
func (ClientCaps) SupportsAudio ¶
func (c ClientCaps) SupportsAudio(codec string) bool
func (ClientCaps) SupportsContainer ¶
func (c ClientCaps) SupportsContainer(ct string) bool
func (ClientCaps) SupportsVideo ¶
func (c ClientCaps) SupportsVideo(codec string) bool
type Collection ¶
type Collection struct {
Key RatingKey `json:"key"`
Title string `json:"title"`
Summary string `json:"summary,omitempty"`
Kind string `json:"kind"` // "franchise", "trending", "smart", "user"
Type MediaType `json:"type"`
Filter *Filter `json:"filter,omitempty"` // smart
Sort SortOrder `json:"sort,omitempty"`
OwnerUserID int64 `json:"ownerUserId,omitempty"` // 0 = server-wide
PosterPath string `json:"posterPath,omitempty"`
BackdropPath string `json:"backdropPath,omitempty"`
ChildCount int `json:"childCount"`
// UpdatedAt changes when the collection's members or artwork change, so
// Plex clients refetch artwork (additive).
UpdatedAt time.Time `json:"updatedAt,omitempty"`
}
type CollectionArtwork ¶
type CollectionArtwork struct {
Mode string `json:"mode"` // auto | collage | backdrop | tmdb | custom
URL string `json:"url,omitempty"`
Accent string `json:"accent,omitempty"` // "#rrggbb"
}
CollectionArtwork selects the poster Plex shows for the collection.
type CollectionPreview ¶
type CollectionPreview struct {
Total int `json:"total"`
Items []Title `json:"items"`
Warnings []string `json:"warnings"`
TookMs int64 `json:"tookMs"`
}
CollectionPreview is the result of evaluating an unsaved spec.
type CollectionRotate ¶
type CollectionRotate struct {
Size int `json:"size"`
Every string `json:"every"` // "daily" | "weekly"
}
CollectionRotate shows a rotating subset of the members.
type CollectionRow ¶
type CollectionRow struct {
Hub Hub `json:"hub"`
CollectionID string `json:"collectionId"`
HomeRow bool `json:"homeRow"`
SectionRow bool `json:"sectionRow"`
}
CollectionRow is an owner-designed collection offered as a Home or section row for a user in a context (targeting and schedules already applied).
type CollectionRows ¶
type CollectionRows interface {
HomeRowsFor(ctx context.Context, user *User, hc HubContext) ([]CollectionRow, error)
}
CollectionRows is implemented by the collection engine for the personalization engine.
type CollectionRule ¶
type CollectionRule struct {
Field string `json:"field"`
Op string `json:"op"`
Value json.RawMessage `json:"value"`
Label string `json:"label,omitempty"`
}
CollectionRule is one condition. Value is typed by Field; Label is display text for ids.
type CollectionSchedule ¶
type CollectionSchedule struct {
Days []int `json:"days,omitempty"`
FromHour *int `json:"fromHour,omitempty"`
ToHour *int `json:"toHour,omitempty"`
Devices []string `json:"devices,omitempty"`
}
CollectionSchedule limits when rows appear. Days use 0 = Sunday … 6 = Saturday; hours are local, FromHour inclusive and ToHour exclusive, wrapping past midnight when FromHour > ToHour.
type CollectionSource ¶
type CollectionSource struct {
Kind string `json:"kind"`
List string `json:"list,omitempty"`
MediaType string `json:"mediaType,omitempty"`
ID int64 `json:"id,omitempty"`
Label string `json:"label,omitempty"`
Params map[string]string `json:"params,omitempty"`
Region string `json:"region,omitempty"`
URL string `json:"url,omitempty"`
Keys []string `json:"keys,omitempty"`
// Role narrows tmdbPerson credits: "cast", "director", "writer",
// "crew" or "" (every credit). Additive to the contract.
Role string `json:"role,omitempty"`
}
CollectionSource is one place candidates come from. Kind selects which other fields apply:
catalog the whole browsable catalogue tmdbList List, MediaType trending_day|trending_week|popular|top_rated|now_playing|upcoming|airing_today|on_the_air tmdbCollection ID, Label a TMDB franchise tmdbDiscover Params raw /discover parameters (names allowed for ids) tmdbKeyword | tmdbCompany | tmdbNetwork | tmdbPerson ID, Label (Role for people) watchProvider ID, Region, Label what's streaming on a service traktList | letterboxdList | mdblist URL titles Keys hand-picked rating keys
When ID is 0 the Label is resolved to a TMDB id by name.
type CollectionSpec ¶
type CollectionSpec struct {
ID string `json:"id,omitempty"` // server-assigned
Name string `json:"name"`
Description string `json:"description,omitempty"`
MediaType string `json:"mediaType"` // "movie" | "show" | "mixed"
Sources []CollectionSource `json:"sources"`
Match string `json:"match"` // "all" | "any"
Rules []CollectionRule `json:"rules"`
Exclude []CollectionRule `json:"exclude"`
// Sort: popularity | rating | released | title | added | random | personal | source.
Sort string `json:"sort"`
SortDesc bool `json:"sortDesc"`
Limit int `json:"limit"` // 0 = no limit
Refresh string `json:"refresh"` // live | hourly | daily | weekly
Rotate *CollectionRotate `json:"rotate,omitempty"`
Artwork CollectionArtwork `json:"artwork"`
Targets CollectionTargets `json:"targets"`
Enabled bool `json:"enabled"`
TemplateID string `json:"templateId,omitempty"`
// Server-maintained.
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
LastBuiltAt *time.Time `json:"lastBuiltAt,omitempty"`
ItemCount int `json:"itemCount"`
// Additive fields beyond the contract (read-only for clients).
BuiltIn bool `json:"builtIn,omitempty"` // shipped with Ether; only targets/artwork/rotate/enabled are editable
RatingKey string `json:"ratingKey,omitempty"` // the Plex ratingKey of the published collection
Warnings []string `json:"warnings,omitempty"` // from the last build
// OwnerUserID is set for private collections created by a Plex user
// (core.Collections.CreateSmart); 0 = server-wide.
OwnerUserID int64 `json:"ownerUserId,omitempty"`
}
CollectionSpec is an owner-designed collection: candidates gathered from Sources, filtered by Rules, sorted, limited and published to Plex.
type CollectionTargets ¶
type CollectionTargets struct {
CollectionsTab bool `json:"collectionsTab"`
HomeRow bool `json:"homeRow"`
SectionRow bool `json:"sectionRow"`
Users UserTargets `json:"users"`
Schedule *CollectionSchedule `json:"schedule,omitempty"`
}
CollectionTargets says where and for whom the collection is published.
type CollectionTemplate ¶
type CollectionTemplate struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Category string `json:"category"`
Spec CollectionSpec `json:"spec"`
}
CollectionTemplate is a curated starting point in the Collection Studio.
type Collections ¶
type Collections interface {
List(ctx context.Context, user *User, t MediaType) ([]Collection, error)
Get(ctx context.Context, user *User, key RatingKey) (*Collection, error)
Items(ctx context.Context, user *User, key RatingKey, offset, limit int) (Page, error)
CreateSmart(ctx context.Context, c Collection) (*Collection, error)
DeleteSmart(ctx context.Context, key RatingKey) error
}
type ContextualRecommender ¶
type ContextualRecommender interface {
HomeHubsFor(ctx context.Context, user *User, hc HubContext, limit int) ([]Hub, error)
SectionHubsFor(ctx context.Context, user *User, t MediaType, hc HubContext, limit int) ([]Hub, error)
}
ContextualRecommender is implemented by recommenders that adapt rows to the request context. internal/plex prefers it over Recommender when available.
type CustomFormat ¶
type CustomFormat struct {
ID string `json:"trash_id"`
Name string `json:"name"`
Scores map[string]int `json:"trash_scores,omitempty"`
Specifications []FormatSpecification `json:"specifications"`
}
CustomFormat retains the native TRaSH/Radarr/Sonarr JSON for owner editing. Unknown specifications are rejected at import rather than silently matching.
type DASHSession ¶
type DASHSession interface {
HLSSession
// ServeManifest writes the MPD.
ServeManifest(w http.ResponseWriter, r *http.Request)
// ServeInit writes the initialization segment of a representation.
ServeInit(w http.ResponseWriter, r *http.Request, rep int)
// ServeMedia writes media segment n of a representation, waiting for
// ffmpeg and restarting it at the right offset when a seek jumps ahead.
ServeMedia(w http.ResponseWriter, r *http.Request, rep, n int)
}
DASHSession is a running ffmpeg remux or transcode producing MPEG-DASH: a static, full-length MPD with separate video and audio representations of fMP4 segments addressed by number ($Number$, from 0). Its HLSSession methods map onto the first representation; ServePlaylist writes the MPD.
type DecideOptions ¶
type DecideOptions struct {
DirectPlay bool // client allows direct play
DirectStream bool // client allows remux
MaxBitrateKbps int // client-requested cap (maxVideoBitrate)
MaxHeight int // from videoResolution
AudioStream int // selected stream index, -1 default
SubtitleStream int // -1 none
AudioFilters []string // trusted owner/viewer audio processing; forces audio encoding
BurnSubtitles bool
}
DecideOptions are per-request choices from the Plex decision request.
type Decision ¶
type Decision struct {
Kind DecisionKind `json:"kind"`
VideoCopy bool `json:"videoCopy"`
AudioCopy bool `json:"audioCopy"`
VideoCodec string `json:"videoCodec"` // target codec when transcoding
AudioCodec string `json:"audioCodec"`
Container string `json:"container"` // "mpegts" (HLS) or original
MaxHeight int `json:"maxHeight"`
BitrateKbps int `json:"bitrateKbps"`
AudioStream int `json:"audioStream"`
SubtitleStream int `json:"subtitleStream"` // -1 none
BurnSubtitles bool `json:"burnSubtitles"`
Reason string `json:"reason"`
AudioFilters []string `json:"audioFilters,omitempty"`
// ToneMap converts HDR (HDR10, HLG, Dolby Vision) video to SDR while
// transcoding, for clients without HDR support.
ToneMap bool `json:"toneMap,omitempty"`
// StripDolbyVision copies the compatible HDR10 base layer while
// removing Dolby Vision RPU/configuration data; it never re-encodes.
StripDolbyVision bool `json:"stripDolbyVision,omitempty"`
}
type DecisionKind ¶
type DecisionKind string
const ( DecisionDirectPlay DecisionKind = "directplay" DecisionRemux DecisionKind = "copy" // Plex calls this "direct stream" DecisionTranscode DecisionKind = "transcode" )
type DownloadRemuxRequest ¶
type DownloadRemuxRequest struct {
// InputURL is the loopback URL of the playback session feeding ffmpeg.
InputURL string
// Info is the probed media info of the resolved source.
Info MediaInfo
// AudioStream is the source stream index to keep; -1 picks the
// default (first) audio stream.
AudioStream int
}
DownloadRemuxRequest is one whole-file remux to MP4.
type DownloadRemuxer ¶
type DownloadRemuxer interface {
// RemuxDownload runs ffmpeg -c copy to a temp MP4, reporting progress
// as a fraction in [0,1] when progress is non-nil. Cancelling ctx
// kills ffmpeg and removes the partial file.
RemuxDownload(ctx context.Context, req DownloadRemuxRequest, progress func(fraction float64)) (path string, size int64, err error)
// ReleaseDownload deletes a remuxed file and frees its budget.
// Releasing an unknown path is a no-op.
ReleaseDownload(path string)
}
DownloadRemuxer copies a resolved source into a temporary faststart MP4. Implemented by internal/transcode. The returned path is owned by the remuxer until ReleaseDownload is called; files are never kept permanently and are bounded by a disk budget.
type Engine ¶
type Engine interface {
// Open builds the byte-offset index for the main video file of an NZB.
Open(ctx context.Context, releaseID string, nzb []byte) (MediaSource, error)
// Prefetch warms [offset, offset+length) at background priority.
Prefetch(ctx context.Context, releaseID string, nzb []byte, offset, length int64) error
}
type FacetValue ¶
type Filter ¶
type Filter struct {
Type MediaType `json:"type,omitempty"` // movie or show
Genres []string `json:"genres,omitempty"`
YearFrom int `json:"yearFrom,omitempty"`
YearTo int `json:"yearTo,omitempty"`
MinRating float64 `json:"minRating,omitempty"`
MaxRuntime time.Duration `json:"maxRuntime,omitempty"`
MinRuntime time.Duration `json:"minRuntime,omitempty"`
Languages []string `json:"languages,omitempty"`
Countries []string `json:"countries,omitempty"`
ContentRatings []string `json:"contentRatings,omitempty"`
MaxContentRating string `json:"maxContentRating,omitempty"` // per-user limit
Collection int64 `json:"collection,omitempty"`
Person int64 `json:"person,omitempty"` // cast or crew TMDB id
Studio string `json:"studio,omitempty"`
Query string `json:"query,omitempty"` // title substring
}
Filter narrows a browse or a smart collection. Zero values mean "any".
type FormatSpecification ¶
type GoodputSession ¶
type GoodputSession interface {
// GoodputKbps is the rolling segment-delivery estimate; 0 unknown.
GoodputKbps() int
}
GoodputSession is a session that measures client delivery goodput.
type HLSLadderSession ¶
type HLSLadderSession interface {
HLSSession
// Rungs lists the ladder, highest first; Rungs()[0] is "base".
Rungs() []HLSRung
// ServeRungPlaylist writes rung's media playlist.
ServeRungPlaylist(w http.ResponseWriter, r *http.Request, rung string)
// ServeRungSegment writes segment n of rung (InitSegment numbering as
// in ServeSegment: the caller passes the parsed segment number).
ServeRungSegment(w http.ResponseWriter, r *http.Request, rung string, n int)
}
HLSLadderSession is an HLSSession that can serve a multi-rung ABR ladder. Only the primary rung's encoder runs until a client requests a segment on another rung; at most one rung encodes at a time.
type HLSRequest ¶
type HLSRequest struct {
SessionID string
InputURL string // ffmpeg input; Ether serves sources on loopback HTTP with range support
// InputHeaders are optional HTTP headers ffmpeg must send when reading
// InputURL (e.g. for resolved trailer URLs). Transcoders that cannot
// send headers ignore them; most resolved URLs need none (additive).
InputHeaders map[string]string
Info MediaInfo
Decision Decision
StartOffset time.Duration
// SegmentDuration defaults to 4s when zero (5s for DASH).
SegmentDuration time.Duration
// Protocol is ProtocolHLS (default when empty) or ProtocolDASH. A DASH
// session also implements DASHSession; Transcoder.Session returns it as
// an HLSSession, so type-assert to reach the DASH methods.
Protocol string
}
type HLSRung ¶
type HLSRung struct {
// ID is the rung's URL component: session/{id}/{ID}/index.m3u8. The
// primary rung is always "base" (served by the HLSSession methods).
ID string
// Bandwidth is the peak bitrate in bits per second (#EXT-X-STREAM-INF
// BANDWIDTH / DASH Representation bandwidth).
Bandwidth int
// AverageBandwidth is the average bitrate in bits per second.
AverageBandwidth int
Width int
Height int
// Codecs is an RFC 6381 codecs string ("avc1...,mp4a...").
Codecs string
FrameRate float64
}
HLSRung is one variant of an adaptive HLS/DASH ladder. All rungs of a session share deterministic segment numbering over one segment timeline: segment N covers the same media on every rung.
type HLSSession ¶
type HLSSession interface {
ID() string
// ServePlaylist writes the media playlist (index.m3u8).
ServePlaylist(w http.ResponseWriter, r *http.Request)
// ServeSegment writes segment n, waiting for ffmpeg to produce it and
// restarting ffmpeg at the right offset when a seek jumps ahead.
ServeSegment(w http.ResponseWriter, r *http.Request, n int)
// SwapInput restarts the session on a new input at the given position
// (mid-stream failover). Segment numbering continues.
SwapInput(ctx context.Context, inputURL string, info MediaInfo, at time.Duration) error
LastAccess() time.Time
Close() error
}
HLSSession is one running ffmpeg remux or transcode producing HLS (or DASH; see DASHSession).
type HealthChecker ¶
HealthChecker checks a sample of an NZB's articles exist (NNTP STAT). It also inspects archive headers: compressed or encrypted RARs and releases without a playable video file report healthy=false with a reason. Implemented by internal/stream.
type HistoryEntry ¶
type HomeLayout ¶
type HomeLayout struct {
Global []LayoutPage `json:"global"`
Viewers []ViewerLayout `json:"viewers"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
}
HomeLayout is the owner's global template, with complete row policies in viewer layers replacing matching global policies. Slots are one-based.
type HomePreview ¶
type HomePreview struct {
Rows []HomeRow `json:"rows"`
GeneratedAt time.Time `json:"generatedAt"`
Context HubContext `json:"context"`
// HomeDeduped counts the titles kept off a Movies or TV Shows page
// because the viewer's Home leads with them (DedupeHome).
HomeDeduped int `json:"homeDeduped,omitempty"`
}
HomePreview is a viewer's assembled rows for one context.
type HomeRow ¶
type HomeRow struct {
HubID string `json:"hubId"`
Layer string `json:"layer,omitempty"`
Slot int `json:"slot,omitempty"`
AnchorSlot int `json:"anchorSlot,omitempty"`
Placement string `json:"placement,omitempty"`
HiddenReason string `json:"hiddenReason,omitempty"`
Policy *RowPolicy `json:"policy,omitempty"`
PolicyLayer string `json:"policyLayer,omitempty"`
Title string `json:"title"`
Kind string `json:"kind"` // e.g. "because-you-watched", "collection:<id>"
Reason string `json:"reason"`
Score float64 `json:"score"` // predicted engagement used for ordering
Pinned bool `json:"pinned,omitempty"`
Hidden bool `json:"hidden,omitempty"` // hidden by a layout rule: not sent to Plex
Source string `json:"source"`
Items []Title `json:"items"`
ItemReasons map[RatingKey]string `json:"itemReasons,omitempty"`
// Hub is the row as handed to the Plex front end.
Hub Hub `json:"-"`
}
HomeRow is one row of a viewer's Home or section as Plex receives it, with the engine's explanation. Items are catalogue titles; the admin API turns them into TitleCards.
type Hub ¶
type Hub struct {
ID string `json:"id"` // stable identifier, e.g. "trending.week.movie"
Title string `json:"title"`
Type MediaType `json:"type"` // item type in the row ("mixed" allowed via "")
Context string `json:"context"` // Plex hub context, e.g. "hub.movie.recentlyreleased"
Style string `json:"style,omitempty"` // "shelf", "hero", "clip"
Promoted bool `json:"promoted"` // show on Home
Items []Title `json:"items"`
More bool `json:"more"`
Key RatingKey `json:"key,omitempty"` // backing collection, if any
}
Hub is a Home row or a section row.
func SectionView ¶
SectionView restricts a Home hub to one library section's media type, so mixed rows that belong on a section too (the watchlist, Rediscover) can appear there with only that section's titles. A typed hub passes through when its type matches. Recaps (Year in Review) stay on Home. ok is false when nothing of the type remains.
type HubContext ¶
type HubContext struct {
Device string `json:"device"`
Daypart string `json:"daypart"`
Weekend bool `json:"weekend"`
Now time.Time `json:"now"`
SectionID int `json:"sectionId,omitempty"`
Product string `json:"product,omitempty"` // X-Plex-Product
Platform string `json:"platform,omitempty"` // X-Plex-Platform
ClientID string `json:"clientId,omitempty"`
}
HubContext is the situation a Plex client asked for rows in: which kind of device, when, and for which section (empty = Home).
func NewHubContext ¶
func NewHubContext(now time.Time, product, platform, device string) HubContext
NewHubContext derives the request context for a hubs call at now (in the server's local time; pass time.Now() or a time already converted with In(loc)) from the client's X-Plex-* headers.
func (HubContext) Bucket ¶
func (hc HubContext) Bucket() string
Bucket is a stable cache key for the context's coarse shape: "section/device/daypart/weekday|weekend".
type HubImpression ¶
type HubImpression struct {
UserID int64 `json:"userId"`
HubID string `json:"hubId"`
Position int `json:"position"`
Keys []RatingKey `json:"keys"`
Context HubContext `json:"context"`
At time.Time `json:"at"`
}
HubImpression is one row as served to a user, for the personalization feedback loop.
type IDs ¶
type IDs struct {
TMDB int64 `json:"tmdb,omitempty"`
IMDB string `json:"imdb,omitempty"`
TVDB int64 `json:"tvdb,omitempty"`
}
IDs are the external identifiers of a title. Episodes carry their show's IMDb/TVDB ids in ShowIDs on Title, and their own TMDB episode id here.
type ImageURLFunc ¶
ImageURL returns a full TMDB image URL for a path and size ("w500", "original").
type ImpressionSink ¶
type ImpressionSink interface {
RecordImpressions(user *User, hc HubContext, hubs []Hub)
}
ImpressionSink receives served rows. Implementations must not block.
type IntentTracker ¶
type IntentTracker interface {
// RecordDetailOpen notes that a viewer opened a title's detail page.
RecordDetailOpen(ctx context.Context, user *User, t *Title)
// NotePlayback notes that playback of key started; an open for the
// same title moments earlier was a conversion, not browsing intent.
NotePlayback(user *User, key RatingKey)
}
IntentTracker receives live in-session browsing signals (item #32): single-item detail opens that do not turn into playback. Signals are in-memory only and decay over ~45 minutes. Implementations must not block the caller for long.
type KeyframePoint ¶
KeyframePoint ties a byte offset in a media file to the presentation time of the sync sample (keyframe) or cue point that starts there, as read from the container index (Matroska Cues, MP4 stss with stco/co64) or a packet scan. MediaInfo.Keyframes holds them sorted by Offset and Time.
type LayoutPage ¶
type LayoutPage struct {
Section string `json:"section"`
MaxRows int `json:"maxRows,omitempty"`
DeviceMaxRows map[string]int `json:"deviceMaxRows,omitempty"`
Rows []RowPolicy `json:"rows"`
// DedupeHome keeps titles the viewer's Home leads with off this page
// (Movies/TV Shows only). Nil means the default: on.
DedupeHome *bool `json:"dedupeHome,omitempty"`
// Tuning shapes how the page's rows and titles are chosen. Nil fields
// inherit (viewer page → global page → engine default).
Tuning *LayoutTuning `json:"tuning,omitempty"`
}
type LayoutTuning ¶
type LayoutTuning struct {
// Discovery: 0 sticks to proven favourites, 1 explores new territory.
Discovery *float64 `json:"discovery,omitempty"`
// Freshness: 0 favours timeless picks, 1 favours new and trending.
Freshness *float64 `json:"freshness,omitempty"`
// Variety: 0 keeps rows focused on the strongest tastes, 1 spreads
// across genres, eras and moods.
Variety *float64 `json:"variety,omitempty"`
}
LayoutTuning is the owner's three dials for a page, each 0..1 with 0.5 as the engine default.
type LayoutWhen ¶
type LayoutWhen struct {
Days []int `json:"days,omitempty"`
Dayparts []string `json:"dayparts,omitempty"`
Months []int `json:"months,omitempty"`
StartDate string `json:"startDate,omitempty"`
EndDate string `json:"endDate,omitempty"`
FromHour *int `json:"fromHour,omitempty"`
ToHour *int `json:"toHour,omitempty"`
Devices []string `json:"devices,omitempty"`
}
LayoutWhen restricts a Home layout row to days (0 = Sunday … 6 = Saturday), an hour window [FromHour, ToHour) in server local time (wraps past midnight when FromHour > ToHour) and device classes.
type LookupResult ¶
type LookupResult struct {
ID int64 `json:"id"`
Name string `json:"name"`
Subtitle string `json:"subtitle,omitempty"`
ImagePath string `json:"imagePath,omitempty"` // TMDB image path
}
LookupResult is one TMDB entity (person, keyword, company, network, collection, watch provider) found by name for the collection builder.
type MediaInfo ¶
type MediaInfo struct {
Chapters []Chapter `json:"chapters,omitempty"`
Container string `json:"container"` // mkv, mp4, avi, mpegts
Duration time.Duration `json:"duration"`
Size int64 `json:"size"`
BitrateKbps int `json:"bitrateKbps"`
Streams []StreamInfo `json:"streams"`
Estimated bool `json:"estimated"` // derived from release name, not probed
// Keyframes is the container's keyframe/cue index, sorted, when it was
// read on open or probe; empty when unknown. Not serialized: it can hold
// thousands of points (additive).
Keyframes []KeyframePoint `json:"-"`
}
func (MediaInfo) Video ¶
func (m MediaInfo) Video() *StreamInfo
type MediaSource ¶
type MediaSource interface {
io.ReaderAt
Size() int64
Name() string // file name inside the release, e.g. "Movie.2024.1080p.mkv"
ReleaseID() string
Close() error
}
MediaSource is a seekable byte stream of one playable file (the video file inside a release, after NZB/RAR/7z unwrapping).
type MicroReason ¶
type MicroReason struct {
Header string `json:"header,omitempty"`
MatchPercent int `json:"matchPercent,omitempty"`
HasMatch bool `json:"hasMatch,omitempty"`
Reason string `json:"reason,omitempty"`
EndsAt time.Time `json:"endsAt,omitempty"`
HasEnd bool `json:"hasEnd,omitempty"`
}
MicroReason is the per-viewer decision header for a detail page (item #31): a calibrated match percentage, the strongest taste reason and the local finish time, e.g. "96% Match · Directed by Denis Villeneuve · Ends at 10:45 PM". Parts that cannot be supported honestly (low taste confidence, unknown runtime) are left empty.
func (MicroReason) Empty ¶
func (m MicroReason) Empty() bool
Empty reports whether there is nothing worth showing.
type MicroReasoner ¶
type MicroReasoner interface {
MicroReason(ctx context.Context, user *User, t *Title, now time.Time) MicroReason
}
MicroReasoner builds a MicroReason for one viewer and title. The title is only read, never mutated, so a shared catalogue record can never leak one viewer's header to another.
type PersonalScorer ¶
type PersonalScorer interface {
ScoreTitles(ctx context.Context, user *User, titles []Title) (map[RatingKey]float64, error)
}
PersonalScorer scores titles for a user (higher = better fit). The collection engine uses it for the "personal" sort.
type PlaySession ¶
type PlaySession struct {
PlaybackGroup string `json:"playbackGroup,omitempty"`
ID string
UserID int64
ClientID string
Key RatingKey
Resolution *Resolution
Source MediaSource // swaps on failover; read through Playback.Source
Info MediaInfo
StartedAt time.Time
// Status for the admin dashboard and /status/sessions, filled on the
// snapshots Playback returns.
TitleName string // "Movie (2024)" or "Show - S01E02 - Episode"
Username string // user display name
Client ClientCaps // the client's product, device and limits
DecisionReason string `json:"decisionReason,omitempty"`
Decision DecisionKind // directplay once bytes go straight to the client; "" until known
BytesServed int64 // source bytes delivered (direct play and ffmpeg input)
SpeedBps int64 // recent delivery rate, bytes per second
LastActive time.Time
Failovers int // mid-stream release swaps so far
// DownFailovers counts speed-driven swaps to a lower-bitrate release
// (a subset of Failovers) (additive).
DownFailovers int `json:"downFailovers,omitempty"`
// RequiresRemux is set when a direct-play failover could not splice or
// find an exact-size replacement: the next decision/start for this
// session must be a zero-encode remux (additive).
RequiresRemux bool `json:"requiresRemux,omitempty"`
// Node is the cluster node serving a session reported by another node
// (empty for this node's own sessions and on a standalone server).
Node string `json:"node,omitempty"`
}
PlaySession binds a user, a title and a client to a resolved, open source.
type Playback ¶
type Playback interface {
// Estimate is the virtual media info shown before a title is resolved.
Estimate(ctx context.Context, t *Title, caps ClientCaps) MediaInfo
// Prepare resolves and opens a source for the user and client, enforcing
// stream limits. Reuses an existing session for the same user/client/key.
Prepare(ctx context.Context, user *User, key RatingKey, caps ClientCaps) (*PlaySession, error)
Session(id string) (*PlaySession, bool)
// SessionFor finds the live session of a client for a title.
SessionFor(userID int64, clientID string, key RatingKey) (*PlaySession, bool)
// ServeRange serves the session's current source with HTTP range
// support, failing over to the next release when articles are missing.
ServeRange(w http.ResponseWriter, r *http.Request, sessionID string)
// InputURL is a loopback URL ffmpeg can read the session's source from.
InputURL(sessionID string) string
// Failover swaps the session to the next candidate release, keeping the
// timestamp. Returns the new media info.
Failover(ctx context.Context, sessionID string, at time.Duration) (*PlaySession, error)
Stop(sessionID string)
Sessions() []*PlaySession
}
type Playlist ¶
type Playlist struct {
Key RatingKey `json:"key"`
UserID int64 `json:"userId"`
Title string `json:"title"`
Smart bool `json:"smart"`
Items []RatingKey `json:"items"`
UpdatedAt time.Time `json:"updatedAt"`
}
Playlists are user-created lists.
type Playlists ¶
type Playlists interface {
List(ctx context.Context, userID int64) ([]Playlist, error)
Get(ctx context.Context, userID int64, key RatingKey) (*Playlist, error)
Create(ctx context.Context, userID int64, title string, items []RatingKey) (*Playlist, error)
Add(ctx context.Context, userID int64, key RatingKey, items []RatingKey) error
Remove(ctx context.Context, userID int64, key RatingKey, itemIndex int) error
Move(ctx context.Context, userID int64, key RatingKey, from, to int) error
Delete(ctx context.Context, userID int64, key RatingKey) error
}
type PostPlayPick ¶
type PostPlayPick struct {
Title Title `json:"title"`
Score float64 `json:"score"`
TasteFit float64 `json:"tasteFit"`
Similarity float64 `json:"similarity"`
RuntimeFit float64 `json:"runtimeFit"`
Reason string `json:"reason,omitempty"`
}
PostPlayPick is one ranked post-play recommendation with its explained score components (item #30): Score = taste factor × similarity factor × runtime factor (× a continuation bonus for the franchise's next entry), where TasteFit is the raw taste fit (-1..1), Similarity the feature overlap with the just-watched title (0..1) and RuntimeFit how well the runtime fits the viewer's remaining time tonight (0..1).
type PostPlayer ¶
type PostPlayer interface {
PostPlay(ctx context.Context, user *User, justWatched RatingKey, hc HubContext, limit int) ([]PostPlayPick, error)
}
PostPlayer ranks a personalized post-play "hook queue" after a title. Implementations cache the ranking per user and just-watched title for the session, so the pick pre-buffered during playback is the pick the post-play endpoint returns first.
type PrewarmOptions ¶
type PrewarmOptions struct {
// Reason labels the trigger for logs and budgets (Prewarm* constants).
Reason string `json:"reason"`
// PrefetchBytes is how much of the opening of the file to pre-buffer at
// background priority once the release is resolved, opened and probed.
// Zero uses the implementation's default for the reason.
PrefetchBytes int64 `json:"prefetchBytes,omitempty"`
// DuringPlayback allows the warm-up while the viewer already has an
// active session (a trailer, or the end of a film, is playing).
DuringPlayback bool `json:"duringPlayback,omitempty"`
}
PrewarmOptions tune a speculative warm-up of a title before the viewer presses Play.
type Prewarmer ¶
type Prewarmer interface {
Prewarm(ctx context.Context, user *User, key RatingKey, caps ClientCaps, opts PrewarmOptions) bool
}
Prewarmer is implemented by playback managers that can resolve, open, probe and pre-buffer a title ahead of Play, so the later Prepare for the same user, client and title is near-instant. A warm-up never reserves a stream or writes resume/history records. It reports whether a warm-up was scheduled (false when skipped by budgets or already warm).
type QualityProfile ¶
type QualityProfile struct {
AllowedQualities []string `json:"allowedQualities,omitempty"` // imported source-resolution gates
CustomFormatScores map[string]int `json:"customFormatScores,omitempty"`
MinCustomFormatScore *int `json:"minCustomFormatScore,omitempty"`
Name string `json:"name"`
MaxHeight int `json:"maxHeight"` // cap, e.g. 1080
MinHeight int `json:"minHeight"` // floor, e.g. 720
AllowHDR bool `json:"allowHdr"` // only applied when the client also supports it
AllowDolbyVision bool `json:"allowDolbyVision"`
PreferredSources []Source `json:"preferredSources"` // best first
PreferredCodecs []string `json:"preferredCodecs"` // best first; codecs the client can't play are dropped anyway
PreferredAudio []string `json:"preferredAudio"`
MinSizeMBPerMin float64 `json:"minSizeMbPerMin"` // size sanity, per minute of runtime
MaxSizeMBPerMin float64 `json:"maxSizeMbPerMin"`
PreferredLanguages []string `json:"preferredLanguages"`
AllowCam bool `json:"allowCam"` // also gated by the owner's global cam setting
}
QualityProfile is an owner-editable rule set ranking releases for a class of client.
type RankerFeature ¶
type RankerFeature struct {
Name string `json:"name"`
Label string `json:"label"`
Weight float64 `json:"weight"`
}
RankerFeature is one learned weight, largest magnitude first.
type RankerInsight ¶
type RankerInsight struct {
TrainedAt *time.Time `json:"trainedAt,omitempty"`
// Examples/Positives are the impressions and plays the model last
// trained on.
Examples int `json:"examples"`
Positives int `json:"positives"`
// AUC of the learned model and of the hand-tuned baseline on held-out
// impressions (0.5 = chance). Zero when there is not enough data.
AUC float64 `json:"auc"`
BaselineAUC float64 `json:"baselineAuc"`
// Active is true when the learned model is being used for ranking.
Active bool `json:"active"`
Features []RankerFeature `json:"features"`
}
RankerInsight describes the learned ranking model for the owner UI.
type RatingKey ¶
type RatingKey int64
RatingKey is the Plex ratingKey of an Ether item. It is derived from TMDB identifiers so a title keeps the same key before and after anything about it is resolved or cached.
Layout (decimal, kept below 2^53 so JavaScript clients parse it safely):
kind * 10^15 + payload
KindMovie payload = tmdb movie id
KindShow payload = tmdb tv id
KindSeason payload = show*10^4 + season (season < 10^4)
KindEpisode payload = show*10^7 + season*10^4 + ep (show < 10^8, season < 1000, ep < 10^4)
KindCollection payload = tmdb collection id (franchise)
KindSmart payload = local smart-collection / generated hub id;
payloads in [EphemeralSmartBase, EphemeralSmartEnd)
are reserved for ephemeral (in-memory, per-viewer)
generated collections, and payloads from
EphemeralSmartEnd up for personalize's Year in Review
collections; database ids stay far below both
KindPlaylist payload = local playlist id
KindExtra payload = compact extra id from the extras registry
KindLocal payload = local file id (spike / test library)
func CollectionKey ¶
func EphemeralSmartKey ¶
EphemeralSmartKey builds an ephemeral collection key from a sequence number (1 .. 10^14-1).
func EpisodeKey ¶
func ParseRatingKey ¶
func PlaylistKey ¶
func (RatingKey) IsEphemeral ¶
IsEphemeral reports whether k is an ephemeral generated collection key.
type Recommender ¶
type Recommender interface {
// HomeHubs are the rows on the Home screen across both sections.
HomeHubs(ctx context.Context, user *User, limit int) ([]Hub, error)
// SectionHubs are the rows at the top of a library section.
SectionHubs(ctx context.Context, user *User, t MediaType, limit int) ([]Hub, error)
// Related are rows on a detail page ("More like this", franchise).
Related(ctx context.Context, user *User, key RatingKey, limit int) ([]Hub, error)
}
Recommender produces per-user Home and section rows.
type Release ¶
type Release struct {
ID string `json:"id"` // stable: sha1(indexer + guid)
GUID string `json:"guid"` // indexer guid
Indexer string `json:"indexer"` // indexer name
Name string `json:"name"` // release name
NZBURL string `json:"nzbUrl"`
Size int64 `json:"size"`
PubDate time.Time `json:"pubDate"`
Grabs int `json:"grabs,omitempty"`
Password bool `json:"password,omitempty"` // indexer flagged passworded
Category int `json:"category,omitempty"`
Info ReleaseInfo `json:"info"`
Score float64 `json:"score"`
// Torrent releases (additive). Protocol is "" for Usenet and
// ProtocolTorrent for Torznab results; torrents are streamed through
// an owner-configured debrid service (internal/debrid).
Protocol string `json:"protocol,omitempty"`
InfoHash string `json:"infoHash,omitempty"` // lower-case hex BitTorrent v1 info hash
MagnetURL string `json:"magnetUrl,omitempty"`
Seeders int `json:"seeders,omitempty"`
Peers int `json:"peers,omitempty"`
// Target narrows a multi-file release (a season pack) to the wanted
// episode; set by the resolver on candidates before fetching.
Target *ReleaseTarget `json:"target,omitempty"`
}
Release is an indexer search result for a title.
type ReleaseHealthChecker ¶
type ReleaseHealthChecker interface {
HealthChecker
CheckRelease(ctx context.Context, releaseID string, nzb []byte) (healthy bool, err error)
}
ReleaseHealthChecker is a HealthChecker that is also told which release the NZB belongs to, so what the check reads (the PAR2 index, archive headers) lands in the segment cache under the ID a following Open uses. Optional: callers fall back to Check when it is not implemented (additive).
type ReleaseInfo ¶
type ReleaseInfo struct {
Title string `json:"title"`
Edition string `json:"edition,omitempty"`
Year int `json:"year,omitempty"`
Seasons []int `json:"seasons,omitempty"`
Episodes []int `json:"episodes,omitempty"`
FullSeason bool `json:"fullSeason,omitempty"`
Height int `json:"height,omitempty"` // 480, 720, 1080, 2160
Source Source `json:"source,omitempty"`
VideoCodec string `json:"videoCodec,omitempty"` // h264, hevc, av1
HDR10 bool `json:"hdr10,omitempty"`
DolbyVision bool `json:"dolbyVision,omitempty"`
AudioCodec string `json:"audioCodec,omitempty"` // aac, ac3, eac3, truehd, dts, dtshd
Atmos bool `json:"atmos,omitempty"`
Channels string `json:"channels,omitempty"` // "5.1"
Group string `json:"group,omitempty"`
Languages []string `json:"languages,omitempty"`
Proper bool `json:"proper,omitempty"`
Repack bool `json:"repack,omitempty"`
Container string `json:"container,omitempty"`
}
ReleaseInfo is what can be parsed from a release name.
type ReleaseTarget ¶
type ReleaseTarget struct {
Season int `json:"season,omitempty"`
Episode int `json:"episode,omitempty"`
}
ReleaseTarget is the episode wanted from a multi-file release such as a season pack.
type ReleaseVariant ¶
type ReleaseVariant struct {
// ID is stable for the title and release; front ends encode it (or its
// position) in Media/Part ids.
ID string `json:"id"`
// Label is the viewer-facing name, e.g. "4K Dolby Vision Remux" or
// "1080p WEB-DL (High Compatibility)".
Label string `json:"label"`
// Edition is the cut, e.g. "Director's Cut", "Extended", "IMAX"; empty
// for the theatrical/unspecified cut.
Edition string `json:"edition,omitempty"`
// Auto marks the resolver's own pick for this client ("Best for Device").
Auto bool `json:"auto,omitempty"`
Release Release `json:"release"`
// Info is the expected media info, estimated from the release name until
// the release has been probed.
Info MediaInfo `json:"info"`
}
ReleaseVariant is one playable version of a title, offered in the Plex "Play Version" picker: a distinct candidate release, by quality tier or edition (cut).
type RemuxRequirer ¶
RemuxRequirer is implemented by playback managers that can report whether a session must be (re)started as a zero-encode remux (HLS/DASH) rather than direct play — set when a mid-stream failover of a non-spliceable direct-play session found no exact-size replacement release, so the client needs fresh container headers at the mapped timestamp.
type Resolution ¶
type Resolution struct {
Title RatingKey `json:"title"`
Release Release `json:"release"`
NZB []byte `json:"-"`
Alternates []Release `json:"alternates"` // next candidates, best first
ResolvedAt time.Time `json:"resolvedAt"`
}
Resolution is the resolver's answer for a title and client: the winning release, already health-checked, plus fallbacks for mid-stream failover.
type ResolveRequest ¶
type ResolveRequest struct {
Title *Title
Client ClientCaps
User *User
// Exclude releases already tried (failover).
Exclude []string
// Prefer is a release ID to try first (the viewer picked a version in
// Plex's "Play Version" picker). It is health-checked like any other
// candidate; when it is unavailable, normal ranking applies (additive).
Prefer string
}
type Resolver ¶
type Resolver interface {
// Resolve finds a healthy release. Returns ErrNotAvailable when nothing
// acceptable exists.
Resolve(ctx context.Context, req ResolveRequest) (*Resolution, error)
// MarkBad records a release as broken so nobody gets it again.
MarkBad(ctx context.Context, releaseID string, reason string) error
// Available reports a cached availability answer without searching:
// (known, available).
Available(ctx context.Context, key RatingKey) (known bool, available bool)
}
type RowDescriptor ¶
type RowDescriptor struct {
HubID string `json:"hubId"`
Title string `json:"title"`
Kind string `json:"kind"`
Description string `json:"description"`
// Sections lists the pages this row can appear on ("home", "movies",
// "shows"); empty means every page.
Sections []string `json:"sections,omitempty"`
// Group is the designer's category: "picks", "explore", "shows",
// "household", "collections" or "plex".
Group string `json:"group,omitempty"`
}
RowDescriptor describes a kind of row the owner can place in the Home layout.
type RowInsight ¶
type RowInsight struct {
HubID string `json:"hubId"`
Title string `json:"title"`
Impressions7d int `json:"impressions7d"`
Plays7d int `json:"plays7d"`
PlayRate float64 `json:"playRate"`
}
RowInsight is the 7-day engagement of one row kind.
type RowPolicy ¶
type RowPolicy struct {
Row string `json:"row"`
Status string `json:"status"`
Slot int `json:"slot,omitempty"`
MinSlot int `json:"minSlot,omitempty"`
MaxSlot int `json:"maxSlot,omitempty"`
Priority float64 `json:"priority,omitempty"`
MaxPerPage int `json:"maxPerPage,omitempty"`
MaxDaysPerWeek int `json:"maxDaysPerWeek,omitempty"`
When *LayoutWhen `json:"when,omitempty"`
}
type SearchRequest ¶
type SegmentCache ¶
type SegmentCache interface {
Get(releaseID string, segment int) ([]byte, bool)
Put(releaseID string, segment int, data []byte) error
// Pin protects every segment of a release from eviction.
Pin(releaseID string, reason string) error
Unpin(releaseID string, reason string) error
Usage() (used, budget int64)
}
SegmentCache stores decoded segments on disk under an LRU budget.
type State ¶
type State interface {
Get(ctx context.Context, userID int64, key RatingKey) (WatchStatus, error)
GetMany(ctx context.Context, userID int64, keys []RatingKey) (map[RatingKey]WatchStatus, error)
// Progress records a timeline update. Crossing 90% marks it watched.
Progress(ctx context.Context, userID int64, key RatingKey, offset, duration time.Duration, state string) error
Scrobble(ctx context.Context, userID int64, key RatingKey) error // mark watched
Unscrobble(ctx context.Context, userID int64, key RatingKey) error // mark unwatched
Rate(ctx context.Context, userID int64, key RatingKey, rating float64) error
// OnDeck: in-progress items and next episodes, most recent first.
InProgress(ctx context.Context, userID int64, limit int) ([]WatchStatus, error)
History(ctx context.Context, userID int64, since time.Time, limit int) ([]HistoryEntry, error)
// InProgressAny lists keys in progress for any user (cache protection, prefetch).
InProgressAny(ctx context.Context) ([]RatingKey, error)
}
type StreamInfo ¶
type StreamInfo struct {
Index int `json:"index"`
Type string `json:"type"` // video, audio, subtitle
Codec string `json:"codec"`
Profile string `json:"profile,omitempty"`
Level int `json:"level,omitempty"`
Language string `json:"language,omitempty"`
Title string `json:"title,omitempty"`
Default bool `json:"default,omitempty"`
Forced bool `json:"forced,omitempty"`
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
FrameRate float64 `json:"frameRate,omitempty"`
BitDepth int `json:"bitDepth,omitempty"`
ColorTransfer string `json:"colorTransfer,omitempty"` // smpte2084 = HDR10, arib-std-b67 = HLG
DolbyVision bool `json:"dolbyVision,omitempty"`
Channels int `json:"channels,omitempty"`
ChannelLayout string `json:"channelLayout,omitempty"`
SampleRate int `json:"sampleRate,omitempty"`
BitrateKbps int `json:"bitrateKbps,omitempty"`
External bool `json:"external,omitempty"` // sidecar subtitle (OpenSubtitles)
ExternalURL string `json:"externalUrl,omitempty"`
// DolbyVisionProfile is the DV profile (5, 7, 8...) from the DOVI
// configuration record; 0 when unknown or not Dolby Vision.
DolbyVisionProfile int `json:"dolbyVisionProfile,omitempty"`
DolbyVisionLevel int `json:"dolbyVisionLevel,omitempty"`
DolbyVisionCompatibilityID int `json:"dolbyVisionCompatibilityId,omitempty"`
CodecString string `json:"codecString,omitempty"` // RFC 6381, derived from the codec configuration
// Configuration-record facts are optional: codec tags alone do not
// establish whether the enhancement/base layers or RPU are present.
DolbyVisionVersion string `json:"dolbyVisionVersion,omitempty"`
DolbyVisionBLPresent *bool `json:"dolbyVisionBLPresent,omitempty"`
DolbyVisionELPresent *bool `json:"dolbyVisionELPresent,omitempty"`
DolbyVisionRPUPresent *bool `json:"dolbyVisionRPUPresent,omitempty"`
}
type TasteDecade ¶
TasteDecade is an affinity for a release decade (1990 = the 90s).
type TasteHabits ¶
type TasteHabits struct {
WeeknightMinutes float64 `json:"weeknightMinutes"` // typical minutes watched on an active weeknight
WeekendMinutes float64 `json:"weekendMinutes"` // typical minutes watched on an active weekend day
PreferredDevice string `json:"preferredDevice"` // DeviceTV, DeviceMobile, ... or ""
BingeScore float64 `json:"bingeScore"` // 0..1: share of episodes watched in same-day runs of 3+
RewatchScore float64 `json:"rewatchScore"` // 0..1: share of finished titles watched again
PeakHours []int `json:"peakHours"` // most common start hours (local), busiest first
}
TasteHabits are a viewer's learned viewing habits.
type TasteLanguage ¶
TasteLanguage is an affinity for an original language (ISO 639-1).
type TasteOverride ¶
type TasteOverride struct {
Kind string `json:"kind"`
Value string `json:"value"`
Weight float64 `json:"weight"`
}
TasteOverride is an owner correction applied on top of the learned model: Kind "genre" (Value = genre name), "person" (Value = TMDB person id) or "keyword" (Value = keyword); Weight -1..1 replaces the learned weight.
type TastePerson ¶
type TastePerson struct {
ID int64 `json:"id"`
Name string `json:"name"`
Role string `json:"role"` // "director", "writer", "actor"
Weight float64 `json:"weight"`
Image string `json:"image,omitempty"` // browser-loadable URL
}
TastePerson is an affinity for a cast or crew member.
type TasteProfile ¶
type TasteProfile struct {
UserID int64 `json:"userId"`
UpdatedAt time.Time `json:"updatedAt"`
Confidence float64 `json:"confidence"` // 0..1, grows with history
Genres []TasteWeight `json:"genres"`
People []TastePerson `json:"people"`
Keywords []TasteWeight `json:"keywords"`
Decades []TasteDecade `json:"decades"`
Languages []TasteLanguage `json:"languages"`
Habits TasteHabits `json:"habits"`
Overrides []TasteOverride `json:"overrides"`
}
TasteProfile is the explainable view of one viewer's taste model.
type TasteWeight ¶
TasteWeight is one named affinity, weight in -1..1.
type TimelineAligner ¶
type TimelineAligner interface {
AlignTime(ctx context.Context, req AlignRequest, at time.Duration) (mapped time.Duration, ok bool)
}
TimelineAligner maps a moment in one release to the same moment in another release of the title using dialogue (subtitle) timing rather than video, so nothing has to be downloaded. The mapping may be piecewise (different cuts or intros shift later timestamps). ok is false when there is no confident alignment; callers then fall back to duration-based alignment.
type Title ¶
type Title struct {
Key RatingKey `json:"key"`
Type MediaType `json:"type"`
IDs IDs `json:"ids"`
// Seasons and episodes.
ShowKey RatingKey `json:"showKey,omitempty"`
ShowIDs IDs `json:"showIds,omitempty"`
ShowTitle string `json:"showTitle,omitempty"`
SeasonTitle string `json:"seasonTitle,omitempty"`
Season int `json:"season,omitempty"`
Episode int `json:"episode,omitempty"`
Title string `json:"title"`
OriginalTitle string `json:"originalTitle,omitempty"`
SortTitle string `json:"sortTitle,omitempty"`
Tagline string `json:"tagline,omitempty"`
Summary string `json:"summary,omitempty"`
Year int `json:"year,omitempty"`
Released time.Time `json:"released,omitempty"` // first air / release date
Runtime time.Duration `json:"runtime,omitempty"`
Status string `json:"status,omitempty"` // TMDB status: Released, In Production, Ended...
Genres []string `json:"genres,omitempty"`
Keywords []string `json:"keywords,omitempty"` // TMDB keywords (lower-case), for content-based recommendations
Studios []string `json:"studios,omitempty"`
Countries []string `json:"countries,omitempty"`
Language string `json:"language,omitempty"` // ISO 639-1 original language
ContentRating string `json:"contentRating,omitempty"`
Adult bool `json:"adult,omitempty"`
Rating float64 `json:"rating,omitempty"` // TMDB vote average 0-10
VoteCount int `json:"voteCount,omitempty"`
Popularity float64 `json:"popularity,omitempty"`
PosterPath string `json:"posterPath,omitempty"`
BackdropPath string `json:"backdropPath,omitempty"`
StillPath string `json:"stillPath,omitempty"` // episodes
LogoPath string `json:"logoPath,omitempty"`
Cast []Person `json:"cast,omitempty"`
Directors []Person `json:"directors,omitempty"`
Writers []Person `json:"writers,omitempty"`
CollectionID int64 `json:"collectionId,omitempty"` // TMDB franchise
CollectionName string `json:"collectionName,omitempty"`
ChildCount int `json:"childCount,omitempty"` // seasons of a show, episodes of a season
LeafCount int `json:"leafCount,omitempty"` // episodes of a show
UpdatedAt time.Time `json:"updatedAt,omitempty"`
}
Title is a catalogue item: movie, show, season or episode. Image paths are TMDB paths ("/abc.jpg"); the front end turns them into URLs.
type Transcoder ¶
type Transcoder interface {
Probe(ctx context.Context, inputURL string) (*MediaInfo, error)
Decide(info MediaInfo, caps ClientCaps, opts DecideOptions) Decision
// StartHLS starts an HLS session, or a DASH session (a DASHSession)
// when req.Protocol is ProtocolDASH.
StartHLS(ctx context.Context, req HLSRequest) (HLSSession, error)
Session(id string) (HLSSession, bool)
Stop(id string)
HWAccel() HWAccel
}
type TrickplayProvider ¶
type TrickplayProvider interface {
// EnsureTrickplay starts (or continues) progressive thumbnail
// generation for the source in the background. It returns
// immediately; duplicate calls for a release already generating are
// no-ops.
EnsureTrickplay(ctx context.Context, src TrickplaySource)
// TrickplayAvailable reports whether thumbnails for the release
// exist or are being generated, so a front end can advertise them
// (Plex's indexes="sd").
TrickplayAvailable(releaseID string) bool
// TrickplayBIF returns a snapshot of the release's sparse BIF index
// built from the frames generated so far.
TrickplayBIF(releaseID string) ([]byte, bool)
// TrickplayFrame returns the JPEG of the latest generated frame at
// or before at (a fallback still when no real frame precedes it).
TrickplayFrame(releaseID string, at time.Duration) ([]byte, bool)
// SetTrickplayFallback records a low-fidelity still (TMDB episode
// still or backdrop, already JPEG at roughly BIF width) used to fill
// not-yet-generated intervals. Real frames replace it as they arrive.
SetTrickplayFallback(releaseID string, jpeg []byte)
}
TrickplayProvider supplies on-the-fly preview thumbnails (Plex "BIF" indexes) for releases as they stream. It is an optional capability: callers reach it through a type assertion or an optional dependency and must tolerate its absence. Implementations only extract from sources handed to EnsureTrickplay (an active playback or warmup session); they never resolve or download a title just for thumbnails.
type TrickplaySource ¶
type TrickplaySource struct {
ReleaseID string
InputURL string
Info MediaInfo
// AvailableUntil is the playback position already read by the client.
// Generators must only fill intervals at or before this point and may
// advance it on later calls as playback progresses.
AvailableUntil time.Duration
}
TrickplaySource identifies one playable source thumbnails can be extracted from: the release being streamed and the loopback URL its bytes are readable through (Playback.InputURL). Info carries the duration and, when known, the container keyframe index used to align extractions to keyframes.
type User ¶
type User struct {
ID int64 `json:"id"` // local id
PlexID int64 `json:"plexId"`
UUID string `json:"uuid,omitempty"`
Username string `json:"username"`
Title string `json:"title"` // display name
Email string `json:"email,omitempty"`
Thumb string `json:"thumb,omitempty"`
IsOwner bool `json:"isOwner"`
StreamLimit int `json:"streamLimit"` // 0 = server default
MaxContentRating string `json:"maxContentRating"` // "" = unrestricted, e.g. "PG-13", "TV-14"
SubtitleLanguages []string `json:"subtitleLanguages"` // ISO 639-1, preference order
AudioLanguages []string `json:"audioLanguages"`
AllowedSections []int `json:"allowedSections"` // empty = all
}
User is a Plex account with access to this server: the owner or a friend the owner shared with on plex.tv.
type UserTargets ¶
UserTargets is either every user ("all" in JSON) or a list of local user ids.
func (UserTargets) Includes ¶
func (u UserTargets) Includes(user *User) bool
Includes reports whether a user (nil = anonymous) is targeted.
func (UserTargets) MarshalJSON ¶
func (u UserTargets) MarshalJSON() ([]byte, error)
func (*UserTargets) UnmarshalJSON ¶
func (u *UserTargets) UnmarshalJSON(b []byte) error
type VariantPlayback ¶
type VariantPlayback interface {
PrepareVariant(ctx context.Context, user *User, key RatingKey, caps ClientCaps, releaseID string) (*PlaySession, error)
}
VariantPlayback is implemented by playback managers that can prepare a session on a specific release (the viewer picked a version). The release is health-checked like any other; when it is unavailable the manager falls back to the normal choice for the client.
type VariantResolver ¶
type VariantResolver interface {
Variants(ctx context.Context, req ResolveRequest, max int) ([]ReleaseVariant, error)
}
VariantResolver lists distinct candidate releases of a title for a client, best first, without health-checking every one of them. Implemented by internal/resolver.
type VibeQuery ¶
type VibeQuery struct {
// Query is the viewer's original text.
Query string `json:"query,omitempty"`
// Title is the human collection title, e.g.
// "90s Thrillers Under 100 Minutes".
Title string `json:"title,omitempty"`
// Filter is the catalogue filter the members are browsed with.
Filter Filter `json:"filter"`
Sort SortOrder `json:"sort,omitempty"`
Desc bool `json:"desc,omitempty"`
// SimilarSeed is the unresolved seed title text of a
// "shows like the office" query; SimilarKey is it resolved.
SimilarSeed string `json:"similarSeed,omitempty"`
SimilarKey RatingKey `json:"similarKey,omitempty"`
// PersonName is an unresolved person name ("christopher nolan
// films"); once resolved it lands in Filter.Person.
PersonName string `json:"personName,omitempty"`
}
VibeQuery is a natural-language ("vibe") search turned into a structured catalogue filter, e.g. "90s thrillers under 100 mins". It backs the ephemeral collections synthesized in search results (additive).
type VibeRefiner ¶
type VibeRefiner interface {
RefineVibe(ctx context.Context, user *User, query string) (VibeQuery, bool, error)
}
VibeRefiner maps an otherwise unparseable vibe query to a VibeQuery (implemented by the optional LLM service). It must be quick, respect ctx, and report ok=false when it cannot help.
type VibeSearcher ¶
type VibeSearcher interface {
VibeCollection(ctx context.Context, user *User, query string) (c *Collection, items []Title, ok bool, err error)
// PromoteEphemeral saves an ephemeral collection as a persisted
// personal smart collection for its owner. ok is false when key is
// not a live ephemeral collection owned by user.
PromoteEphemeral(ctx context.Context, user *User, key RatingKey) (c *Collection, ok bool, err error)
}
VibeSearcher is optionally implemented by the collections engine: it turns a vibe query into a browseable ephemeral collection for the viewer, alongside the collection's top items. ok is false when the query is not a vibe query (plain title searches stay untouched).
type ViewerActivity ¶
type ViewerActivity struct {
Title Title `json:"title"`
At time.Time `json:"at"`
Completed bool `json:"completed"`
Progress float64 `json:"progress"` // 0..1
}
ViewerActivity is one recent viewing.
type ViewerLayout ¶
type ViewerLayout struct {
UserID int64 `json:"userId"`
Pages []LayoutPage `json:"pages"`
}
type ViewerStats ¶
type ViewerStats struct {
UserID int64 `json:"userId"`
LastActive time.Time `json:"lastActive,omitempty"`
Plays30d int `json:"plays30d"`
HoursWatched30d float64 `json:"hoursWatched30d"`
}
ViewerStats is a viewer's recent activity summary.
type WatchStatus ¶
type WatchStatus struct {
Key RatingKey `json:"key"`
ViewOffset time.Duration `json:"viewOffset"`
ViewCount int `json:"viewCount"`
LastViewedAt time.Time `json:"lastViewedAt,omitempty"`
UserRating float64 `json:"userRating,omitempty"` // 0-10
Duration time.Duration `json:"duration,omitempty"`
UpdatedAt time.Time `json:"updatedAt"`
}