Documentation
¶
Overview ¶
Package subtitles fetches missing subtitle languages from OpenSubtitles and serves them as external (sidecar) tracks.
Flow: Sidecars is called with the tracks embedded in the chosen release; for each of the user's preferred languages not already embedded it searches OpenSubtitles, ranks candidates (trusted uploaders, download count, release-name match — a moviehash match is impossible before the file exists), downloads the best, normalises it to UTF-8 SRT + WebVTT and caches both on disk. The returned core.StreamInfo carries an ExternalURL of the form "ether-subtitle:<id>"; the Plex front end resolves it with Open / OpenFormat.
Index ¶
- Constants
- Variables
- func AcceptsUploadFormat(format string) bool
- func EmbeddedFontFamilyNames(data []byte) []string
- func FontAttachmentMIMEType(name string) string
- func HasMojibake(s string) bool
- func IsASSCodec(codec string) bool
- func IsFontAttachmentMeta(fileName, mimeType string) bool
- func MissingLanguages(user *core.User, embedded []core.StreamInfo) []string
- func MovieHash(r io.ReaderAt, size int64) (string, error)
- func NeededAutomaticLanguage(p AutomaticPlayback) string
- func NormalizeDialogue(s string) string
- func NormalizeLang(raw []byte, lang string) (srt, vtt string, err error)
- func ParseASSFontFamilies(script []byte) []string
- func Rank(cands []Candidate, releaseName string)
- func ReadMKVEmbeddedASS(ctx context.Context, src io.ReaderAt, size int64, streamIndex int) (data []byte, resultErr error)
- func RenderASS(cues []Cue) string
- func RenderSRT(cues []Cue) string
- func RenderVTT(cues []Cue) string
- func RepairMojibake(s string) string
- func SanitizeFontAttachmentName(rawName, mimeType string, index int) string
- func ShiftASS(raw []byte, offset time.Duration) []byte
- func SourceKey(raw string) string
- func Test(ctx context.Context, cfg config.OpenSubtitles) error
- type AlignOptions
- type AlignSegment
- type Aligner
- type AutomaticAttempt
- type AutomaticBudget
- func (b *AutomaticBudget) Begin(ctx context.Context, viewer int64, key core.RatingKey, now time.Time) (AutomaticAttempt, bool, error)
- func (b *AutomaticBudget) Finish(ctx context.Context, a AutomaticAttempt, status string) error
- func (b *AutomaticBudget) Pause(ctx context.Context, until time.Time) error
- func (b *AutomaticBudget) Spend(ctx context.Context, a AutomaticAttempt, now time.Time) (bool, error)
- func (b *AutomaticBudget) Status(ctx context.Context, viewer int64, key core.RatingKey) (AutomaticStatus, error)
- type AutomaticPlayback
- type AutomaticStatus
- type AutomaticTarget
- type CachedSubtitle
- type Candidate
- type Cue
- type FitOptions
- type FontAttachment
- type FontBundleItem
- type Options
- type Piecewise
- type PluginProvider
- type Preferences
- type ReleaseAccess
- type Service
- func (s *Service) CachedSidecars(title *core.Title, user *core.User, embedded []core.StreamInfo, ...) []core.StreamInfo
- func (s *Service) CachedSubtitles() ([]CachedSubtitle, error)
- func (s *Service) CuesForInfo(info core.MediaInfo) ([]Cue, bool)
- func (s *Service) DeleteUpload(key core.RatingKey, stream core.StreamInfo) error
- func (s *Service) DialogueTimeline(ctx context.Context, title *core.Title, lang, releaseName string, ...) ([]Cue, bool)
- func (s *Service) DownloadOnDemand(ctx context.Context, title *core.Title, fileID int64) (core.StreamInfo, error)
- func (s *Service) Enabled() bool
- func (s *Service) Enrich(ctx context.Context, title *core.Title, user *core.User, res *core.Resolution, ...)
- func (s *Service) ManualSidecars(title *core.Title, embedded []core.StreamInfo) []core.StreamInfo
- func (s *Service) Open(id string) (io.ReadCloser, string, error)
- func (s *Service) OpenFormat(id, format string) (io.ReadCloser, string, error)
- func (s *Service) OpenFormatFrom(id, format, source string) (io.ReadCloser, string, error)
- func (s *Service) RegisterPluginProvider(p PluginProvider)
- func (s *Service) RememberVideoHash(key core.RatingKey, releaseName, movieHash string, videoSize int64)
- func (s *Service) RunAutomatic(ctx context.Context, budget *AutomaticBudget, target AutomaticTarget) error
- func (s *Service) Search(ctx context.Context, title *core.Title, lang, releaseName string) ([]Candidate, error)
- func (s *Service) SearchOnDemand(ctx context.Context, title *core.Title, lang, releaseName string) ([]Candidate, error)
- func (s *Service) SearchOnDemandWithHash(ctx context.Context, title *core.Title, lang, releaseName, movieHash string, ...) ([]Candidate, error)
- func (s *Service) SearchWithHash(ctx context.Context, title *core.Title, lang, releaseName, movieHash string) ([]Candidate, error)
- func (s *Service) SetFFmpeg(path string)
- func (s *Service) Sidecars(ctx context.Context, title *core.Title, user *core.User, ...) ([]core.StreamInfo, error)
- func (s *Service) SidecarsForRelease(ctx context.Context, title *core.Title, user *core.User, ...) ([]core.StreamInfo, error)
- func (s *Service) SidecarsWithHash(ctx context.Context, title *core.Title, user *core.User, ...) ([]core.StreamInfo, error)
- func (s *Service) SourceText(key core.RatingKey, except string) (lang, text string, ok bool)
- func (s *Service) StartCorrection(ctx context.Context, title *core.Title, id string, access ReleaseAccess)
- func (s *Service) StoreText(title *core.Title, lang, label, text string, fileID int64) (core.StreamInfo, error)
- func (s *Service) StoreUpload(title *core.Title, filename, format, language string, body []byte) (core.StreamInfo, error)
- func (s *Service) StreamByFileID(title *core.Title, fileID int64) (core.StreamInfo, bool)
- func (s *Service) SyncHandler() http.Handler
- func (s *Service) TimelineAligner() *Aligner
- func (s *Service) UnregisterPluginProvider(id string)
- func (s *Service) UpdateConfig(cfg config.OpenSubtitles)
- func (s *Service) UpdateProviders(ps []config.SubtitleProvider)
- type SpeechWindow
- type TimingFit
- func FitSpeechEnergy(cues []Cue, windows []SpeechWindow) (TimingFit, bool)
- func FitSpeechEnergyOptions(cues []Cue, windows []SpeechWindow, opt FitOptions) (TimingFit, bool)
- func FitTiming(candidate, reference []Cue) (TimingFit, bool)
- func FitTimingOptions(candidate, reference []Cue, opt FitOptions) (TimingFit, bool)
Constants ¶
const ( // MaxFontAttachments bounds the number of font attachments inspected or // returned from a single Matroska container. MaxFontAttachments = 32 // MaxUnfilteredFontBytes (48 MiB) is the maximum total attachment payload // returned without filtering. Packs at or below this bound return all font // attachments in full; larger packs select only the whole font attachment // files whose SFNT family names (or fallback filenames) are referenced by // the ASS script's styles or inline \fn overrides. // Note: This performs attachment-level selection of whole font files, not // glyph-level font subsetting. MaxUnfilteredFontBytes = 48 << 20 // MaxFontScanBytes (512 MiB) bounds the total raw attachment payload an // oversized pack may scan when selecting referenced font attachments. MaxFontScanBytes = 512 << 20 // MaxASSScriptBytes (8 MiB) bounds the ASS/SSA script text parsed for font // family references. MaxASSScriptBytes = 8 << 20 )
const ASSHeader = "[Script Info]\n; Script generated by Ether\nScriptType: v4.00+\nPlayResX: 384\nPlayResY: 288\nScaledBorderAndShadow: yes\nYCbCr Matrix: None\n\n" +
"[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n" +
"Style: Default,Arial,16,&Hffffff,&Hffffff,&H0,&H0,0,0,0,0,100,100,0,0,1,1,0,2,10,10,10,1\n\n" +
"[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n"
ASSHeader is the script header RenderASS writes: FFmpeg's own header for a converted text track (PlayRes 384x288, one "Default" style), so a player that restyles "Default" (Plex Web's subtitle colour, size and position settings) treats it like the converted stream PMS sends.
const URLPrefix = "ether-subtitle:"
URLPrefix prefixes the ExternalURL of sidecar streams from this package.
Variables ¶
var ErrASSTrackIncomplete = errors.New("matroska ASS subtitle track incomplete or truncated")
ErrASSTrackIncomplete reports that an embedded Matroska ASS/SSA subtitle track is truncated, corrupt, or exceeds the bounded script ceiling.
ErrFontAttachmentSelectionUnavailable reports that an oversized font pack could not be narrowed to script-referenced font attachments.
var ErrFontPackExceedsLimit = errors.New("subtitle font attachments exceed size limit")
ErrFontPackExceedsLimit reports that the attached font payload (or the script-referenced attachment selection) exceeds the retained byte ceiling.
var ErrQuota = errors.New("opensubtitles: download quota exhausted")
ErrQuota is returned when OpenSubtitles refuses a download because the account's (or anonymous IP's) daily download quota is used up.
Functions ¶
func AcceptsUploadFormat ¶ added in v0.3.0
AcceptsUploadFormat is the Plex Web upload format check: case-sensitive on the wire. PMS accepts srt/ass/ssa/smi/txt/psb; Ether also accepts vtt.
func EmbeddedFontFamilyNames ¶
EmbeddedFontFamilyNames parses SFNT (TrueType/OpenType and TTC/OTC collection) name tables from a bounded byte slice and returns the family, typographic family, full name, and PostScript name records. Untrusted font bytes are strictly bounds-checked and never panic or allocate unboundedly.
func FontAttachmentMIMEType ¶
FontAttachmentMIMEType returns a safe, static MIME type derived strictly from the sanitized font extension rather than trusting container metadata.
func HasMojibake ¶ added in v0.3.0
HasMojibake reports UTF-8 that still looks like a 1252 mis-decode. Only the two-byte pair patterns count: a single Å/Ä/Ã is a real Nordic or German letter, not mojibake.
func IsASSCodec ¶
IsASSCodec reports whether a stream codec identifier names an ASS or SSA text subtitle track.
func IsFontAttachmentMeta ¶
IsFontAttachmentMeta reports whether an attachment filename or MIME type identifies a subtitle font file.
func MissingLanguages ¶
func MissingLanguages(user *core.User, embedded []core.StreamInfo) []string
MissingLanguages returns the user's preferred languages (in preference order) that no embedded subtitle track provides.
func MovieHash ¶
MovieHash reads exactly the first and last 64 KiB. Call only on cached source ranges so hashing does not fetch a release during first-frame startup. Implemented from the published OpenSubtitles algorithm, without copied code.
func NeededAutomaticLanguage ¶
func NeededAutomaticLanguage(p AutomaticPlayback) string
NeededAutomaticLanguage is conservative when playback or language evidence is unknown. Only the first valid preference is considered; suitable embedded tracks cover their language without consuming quota.
func NormalizeDialogue ¶
NormalizeDialogue reduces cue text to a comparison key: tags and hearing-impaired annotations dropped, punctuation and case folded away, so OCR/punctuation variants of the same line compare near-equal.
func NormalizeLang ¶
NormalizeLang is Normalize with the subtitle's language, which picks the legacy code page for non-UTF files (Windows-1251 for Russian, …).
func ParseASSFontFamilies ¶
ParseASSFontFamilies extracts the distinct font family names referenced by an ASS/SSA script: the Fontname column of every Style line under [V4+ Styles] / [V4 Styles] (honoring the section's Format line) plus every inline {\fn...} override tag in dialogue events.
func Rank ¶
Rank scores candidates in place, best first. There is no file yet, so no moviehash: download count (log-scaled), trusted uploaders, ratings and release-name token overlap carry the signal; AI-translated and hearing-impaired tracks are penalised (HI only slightly — sometimes it is the only option).
func ReadMKVEmbeddedASS ¶
func ReadMKVEmbeddedASS(ctx context.Context, src io.ReaderAt, size int64, streamIndex int) (data []byte, resultErr error)
ReadMKVEmbeddedASS extracts the complete authored ASS/SSA script (header CodecPrivate and dialogue events) for a Matroska subtitle track identified strictly by its 0-based stream index (or all ASS subtitle tracks when streamIndex < 0). Any truncated cluster, read error, or script cap overflow returns an error rather than partial dialogue.
func RenderASS ¶ added in v0.3.3
RenderASS renders cues as an ASS script with ASSHeader: <i>, <b> and <u> become override tags, other markup is dropped and line breaks are \N.
func RepairMojibake ¶ added in v0.3.0
RepairMojibake undoes UTF-8 that was read as Windows-1252 or Latin-1 and written back as UTF-8. Unchanged when the reverse is not valid UTF-8 or does not look cleaner.
func SanitizeFontAttachmentName ¶
SanitizeFontAttachmentName returns a safe display filename for an untrusted container attachment. It strips directory components, rejects traversal or control characters, and guarantees a supported font extension so an attachment name can never become a filesystem path.
func ShiftASS ¶
ShiftASS shifts the Start and End timestamps of every Dialogue event in an authored ASS/SSA script by offset while preserving all headers, styles, layers, margins, and inline override tags.
Types ¶
type AlignOptions ¶
type AlignOptions struct {
MinMatched int // default 10
MaxResidual time.Duration // median bound, default 500ms
MaxResidualP90 time.Duration // default 1s
}
AlignOptions bounds what counts as a confident alignment.
type AlignSegment ¶
type AlignSegment struct {
FromStart time.Duration `json:"fromStart"`
FromEnd time.Duration `json:"fromEnd"`
Slope float64 `json:"slope"`
Intercept time.Duration `json:"intercept"`
Matched int `json:"matched"`
}
AlignSegment is one affine run: To = Slope*From + Intercept (millisecond space), valid for From times in [FromStart, FromEnd].
type Aligner ¶
type Aligner struct {
// contains filtered or unexported fields
}
Aligner implements core.TimelineAligner: it maps a moment in one release of a title to the same moment in another release using only subtitle dialogue timing, so no video is ever downloaded. Timelines come from subtitles already cached on disk or from OpenSubtitles files matched to each release by name (or, when only a runtime is known, by how close the file's last cue lands to that runtime). Everything is off unless the OpenSubtitles service is enabled.
Failover calls AlignTime with a sub-second deadline: on a cache miss that returns ok=false immediately and warms the cache in the background. Callers with time to spare (marker shifting) get a synchronous build.
func (*Aligner) AlignTime ¶
func (a *Aligner) AlignTime(ctx context.Context, req core.AlignRequest, at time.Duration) (time.Duration, bool)
AlignTime implements core.TimelineAligner.
func (*Aligner) Prewarm ¶
func (a *Aligner) Prewarm(req core.AlignRequest)
Prewarm starts a background alignment build for a release pair so a later failover or marker lookup hits the cache. Duplicate and recently-failed pairs are skipped.
type AutomaticAttempt ¶
type AutomaticAttempt struct {
// contains filtered or unexported fields
}
AutomaticAttempt is an opaque fencing token for one search attempt. It cannot be constructed outside this package. A replacement search invalidates it.
type AutomaticBudget ¶
type AutomaticBudget struct {
// contains filtered or unexported fields
}
AutomaticBudget is the shared-database arbitration boundary for automatic subtitle work. It does not schedule playback work or authorize a viewer. Callers must establish actual playing, effective preferences and current manual/source revisions before using it. Cache reads need no reservation.
The permanent spent bit MUST be committed before any quota-consuming provider request. A crash, timeout, source change or ambiguous HTTP outcome never clears it. Session, source, language and node IDs deliberately are not budget keys. Use the playable episode rating key, never its show/season key.
func NewAutomaticBudget ¶
func NewAutomaticBudget(db *sql.DB) *AutomaticBudget
func (*AutomaticBudget) Begin ¶
func (b *AutomaticBudget) Begin(ctx context.Context, viewer int64, key core.RatingKey, now time.Time) (AutomaticAttempt, bool, error)
Begin reserves search work, with a durable six-hour negative/retry boundary. Expiry allows another search, never another download after Spend succeeds. SQL errors fail closed. Provider pause applies across all viewers and titles.
func (*AutomaticBudget) Finish ¶
func (b *AutomaticBudget) Finish(ctx context.Context, a AutomaticAttempt, status string) error
Finish stores bounded, secret-free machine status. It never refunds a spent reservation. Failure to persist a status cannot turn an uncertain request into a retry. A search finishing after replacement cannot change the new worker.
func (*AutomaticBudget) Pause ¶
Pause persists the provider's complete Retry-After/quota deadline, without shortening it to an HTTP worker's wait budget. A shorter concurrent pause never overrides a longer one. Call this before finishing a quota-limited attempt.
func (*AutomaticBudget) Spend ¶
func (b *AutomaticBudget) Spend(ctx context.Context, a AutomaticAttempt, now time.Time) (bool, error)
Spend commits the irreversible reservation. A false result MUST prohibit the download. This conditional write also fences expired/replaced search workers. Repeated Spend calls with the same token are false (not idempotent permission).
func (*AutomaticBudget) Status ¶
func (b *AutomaticBudget) Status(ctx context.Context, viewer int64, key core.RatingKey) (AutomaticStatus, error)
type AutomaticPlayback ¶
type AutomaticPlayback struct {
Title *core.Title
Viewer int64
Playing, Manual bool
Languages, UnderstoodAudio []string
SelectedAudio string
Preferences Preferences
Streams []core.StreamInfo
Release string
}
AutomaticPlayback is a detached, effective preference snapshot supplied by the authenticated session coordinator. SelectedAudio must be the language of the stream actually selected, never inferred from the title's language.
type AutomaticStatus ¶
AutomaticStatus is safe for structured logs and an owner status adapter. No credentials, release names, URLs or raw provider responses are stored here.
type AutomaticTarget ¶
type AutomaticTarget struct {
Current func(context.Context) (AutomaticPlayback, error)
Attach func(context.Context, AutomaticPlayback, core.StreamInfo) error
}
AutomaticTarget supplies the live session fence. Current must freshly check viewer authorization, effective rules, source revision and manual choices. Attach must atomically repeat those checks while publishing the actual track; a check-then-write implementation is not sufficient. The caller runs this worker off the timeline response, with the exact source's cancellation context.
type CachedSubtitle ¶
type CachedSubtitle struct {
ID string `json:"id"`
Language string `json:"language"`
Release string `json:"release"`
FetchedAt time.Time `json:"fetchedAt"`
Key string `json:"-"`
}
CachedSubtitle identifies a sidecar from its own metadata. Key is internal only; the app resolves it through the catalog. Legacy metadata without a key stays unknown, with no selection-index scan on the list request path.
type Candidate ¶
type Candidate struct {
FileID int64
FileName string
Language string
Release string
DownloadCount int
Ratings float64
Uploader string
UploaderRank string
HearingImp bool
AITranslated bool
HashMatch bool
// Forced marks a real forced track: OpenSubtitles'
// foreign_parts_only and NOT hearing_impaired (a HI
// foreign-parts track is SDH, not forced; HearingImp
// carries that signal separately).
Forced bool
Score float64 // filled by Rank
// Provider names the source shown to viewers ("OpenSubtitles" or an
// add-on name); ProviderID is its configured id. DownloadURL, when
// set, is the add-on file URL fetched only on a viewer pick.
Provider string
ProviderID string
DownloadURL string
PluginID string
PluginSubtitleID string
PluginFormat string
// contains filtered or unexported fields
}
Candidate is one OpenSubtitles search result.
type Cue ¶
Cue is one subtitle cue.
func ParseASS ¶
ParseASS converts Dialogue events using the declared Format field. Styling, positioning and karaoke effects are deliberately dropped for text fallbacks.
func ReadEmbeddedTextCues ¶ added in v0.3.0
ReadEmbeddedTextCues reads cue times from the first embedded TEXT subtitle track of a Matroska or MP4 source, using only the container index and bounded range reads.
type FitOptions ¶ added in v0.3.0
type FitOptions struct {
MinInliers int
MinConfidence float64
MaxOffset time.Duration
FrameSlack time.Duration
}
FitOptions tunes FitTiming. Zero values use the defaults above.
type FontAttachment ¶
FontAttachment is a single font file attached to a Matroska container for ASS/SSA subtitle rendering. Name is always a sanitized basename and must never be used as a filesystem path or cross-title cache key.
func ReadMKVFontAttachments ¶
func ReadMKVFontAttachments(ctx context.Context, src io.ReaderAt, size int64, assScript []byte) ([]FontAttachment, error)
ReadMKVFontAttachments reads embedded font attachments from a Matroska container using bounded EBML range reads. If the total attached font bytes exceed MaxUnfilteredFontBytes (48 MiB), only the font attachments referenced by assScript (or by the container's embedded ASS/SSA tracks) are retained.
func SelectReferencedFontAttachments ¶
func SelectReferencedFontAttachments(fonts []FontAttachment, assScript []byte, maxBytes int64) ([]FontAttachment, error)
SelectReferencedFontAttachments returns all fonts when their total byte size is at or below maxBytes (48 MiB). When the pack exceeds maxBytes, it selects only the whole font attachments whose SFNT family names (or filename stems) match the font families referenced by assScript. This is attachment-level selection of required font files, not glyph-level font subsetting.
type FontBundleItem ¶
FontBundleItem is the JSON-serializable representation of an attached font sent to web and Silo clients.
func EncodeFontBundle ¶
func EncodeFontBundle(fonts []FontAttachment) []FontBundleItem
EncodeFontBundle converts extracted font attachments into base64 JSON items.
type Options ¶
type Options struct {
Config config.OpenSubtitles
Providers []config.SubtitleProvider
CacheDir string // data dir subfolder for downloaded subtitles
BaseURL string // override for tests
HTTPClient *http.Client
Logger *slog.Logger
UserAgent string // OpenSubtitles requires an app User-Agent
// NegativeTTL is how long "no subtitle found" for a title+language is
// remembered before searching again (default 6h).
NegativeTTL time.Duration
// MaxRetryAfter caps a 429 Retry-After wait (default 3s).
MaxRetryAfter time.Duration
Now func() time.Time
// AlignLanguages are tried in order for dialogue timeline alignment
// (default ["en"]: the most widely subtitled language).
AlignLanguages []string
// FFmpeg is the owner-configured ffmpeg used for speech-energy
// measurement. Empty skips that path.
FFmpeg string
}
Options construct a Service. Config is a snapshot; call UpdateConfig on config changes if desired.
type Piecewise ¶
type Piecewise struct {
Segments []AlignSegment `json:"segments"`
Matched int `json:"matched"`
// Residual is the median absolute error of matched cues against the fit.
Residual time.Duration `json:"residual"`
}
Piecewise is a confident dialogue-based mapping between two releases.
func AlignPiecewise ¶
func AlignPiecewise(from, to []Cue, opts AlignOptions) (*Piecewise, error)
AlignPiecewise builds the mapping from the "from" cue timeline to the "to" cue timeline, or fails when there is no confident alignment (too few matched lines, or matched lines that do not fit within the residual bound — typically a different language or an unrelated subtitle file).
type PluginProvider ¶ added in v0.3.3
type PluginProvider interface {
ID() string
Search(context.Context, *core.Title, string, string, int64) ([]Candidate, error)
Pick(context.Context, *core.Title, Candidate) (context.Context, error)
Download(context.Context, Candidate) ([]byte, error)
}
PluginProvider is the viewer-driven plugin subtitle bridge.
type Preferences ¶ added in v0.2.34
type Preferences struct {
Mode string // off, forced-only, foreign-only, always
Style string // normal, sdh, forced
}
Preferences is the viewer's subtitle mode/style (trackprefs vocabulary) for automatic fetching. Empty means no preference: best-ranked wins.
type ReleaseAccess ¶ added in v0.3.0
type ReleaseAccess struct {
Reader io.ReaderAt
Size int64
URL string
Duration time.Duration
Info *core.MediaInfo
Source string
FFmpeg string
Done func()
Hold func() (io.ReaderAt, int64, func(), bool)
}
ReleaseAccess is enough of a playing session to measure a sidecar. Reader and URL must belong to that session's lifetime; Enrich must not pass a prepare-generation handle. Hold, when set, is taken after the speech semaphore so a retired source can close during a failover.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the subtitle client (OpenSubtitles and add-on providers) and disk cache.
func (*Service) CachedSidecars ¶ added in v0.2.21
func (s *Service) CachedSidecars(title *core.Title, user *core.User, embedded []core.StreamInfo, prefs ...Preferences) []core.StreamInfo
CachedSidecars lists, without any network call, the sidecars already on disk for a title: viewer-picked tracks, then for each of the viewer's preferred languages missing from embedded the most recently fetched track (whichever release it was matched to). Streams whose ExternalURL is already in embedded are skipped, so it can top up a session's enriched MediaInfo too. Nil when the service is disabled.
func (*Service) CachedSubtitles ¶
func (s *Service) CachedSubtitles() ([]CachedSubtitle, error)
func (*Service) CuesForInfo ¶
CuesForInfo returns dialogue cues for the release described by info when the system already holds their text: a cached sidecar stream in info.Streams. It never fetches video and never touches the network. Embedded tracks whose text was never extracted are not available here.
func (*Service) DeleteUpload ¶
DeleteUpload removes only the title/language upload that issued this track. Known index paths keep the mutation independent of the library's size.
func (*Service) DialogueTimeline ¶
func (s *Service) DialogueTimeline(ctx context.Context, title *core.Title, lang, releaseName string, dur time.Duration) ([]Cue, bool)
DialogueTimeline returns the best dialogue timeline for one release (cached sidecar first, then OpenSubtitles), without downloading video.
func (*Service) DownloadOnDemand ¶ added in v0.2.21
func (s *Service) DownloadOnDemand(ctx context.Context, title *core.Title, fileID int64) (core.StreamInfo, error)
DownloadOnDemand fetches a file a recent SearchOnDemand offered for the title, remembers it as the title's pick for its language (replacing any earlier pick) and returns its sidecar stream. core.ErrNotFound means the file was not offered for this title (or the offer expired).
func (*Service) Enrich ¶
func (s *Service) Enrich(ctx context.Context, title *core.Title, user *core.User, res *core.Resolution, src core.MediaSource, info *core.MediaInfo, prefs ...Preferences)
Enrich is playback.Deps.AugmentMedia. It attaches sidecars already on disk for this release. It does not search or download: a file is fetched only when the viewer picks one. The source is exclusively owned by the preparing generation, so local hashing cannot race disposal during failover.
func (*Service) ManualSidecars ¶ added in v0.3.0
func (s *Service) ManualSidecars(title *core.Title, embedded []core.StreamInfo) []core.StreamInfo
ManualSidecars lists viewer-picked tracks already on disk. Unlike CachedSidecars it does not require OpenSubtitles to be configured, so a translated or transcribed track still appears when that account is off.
func (*Service) Open ¶
Open resolves a sidecar id (or a full ExternalURL) to its SRT content. It returns the reader and the format ("srt").
func (*Service) OpenFormat ¶
OpenFormat resolves a sidecar id in the requested format ("srt" or "vtt").
func (*Service) OpenFormatFrom ¶ added in v0.3.0
OpenFormatFrom is OpenFormat applying a cached fit only for this release/source. An empty source serves the original sidecar.
func (*Service) RegisterPluginProvider ¶ added in v0.3.3
func (s *Service) RegisterPluginProvider(p PluginProvider)
func (*Service) RememberVideoHash ¶ added in v0.3.3
func (s *Service) RememberVideoHash(key core.RatingKey, releaseName, movieHash string, videoSize int64)
RememberVideoHash records an already-computed moviehash and byte size (for example from local file enrichment) so a later viewer-driven subtitle search can pass videoHash and videoSize without fetching the release on the search path.
func (*Service) RunAutomatic ¶
func (s *Service) RunAutomatic(ctx context.Context, budget *AutomaticBudget, target AutomaticTarget) error
RunAutomatic handles one eligible playback attempt. It is intentionally not wired to Enrich: preparation and metadata must remain cache-only. This method does not establish client hot-add support or schedule itself.
func (*Service) Search ¶
func (s *Service) Search(ctx context.Context, title *core.Title, lang, releaseName string) ([]Candidate, error)
Search returns ranked candidates for a title and language.
func (*Service) SearchOnDemand ¶ added in v0.2.21
func (s *Service) SearchOnDemand(ctx context.Context, title *core.Title, lang, releaseName string) ([]Candidate, error)
SearchOnDemand is the viewer-driven search behind Plex's subtitle "Search": ranked candidates from configured providers for one language (releaseName, when a session knows it, improves the ranking). Results are remembered for an hour so DownloadOnDemand only fetches files offered for the title.
func (*Service) SearchOnDemandWithHash ¶ added in v0.3.3
func (s *Service) SearchOnDemandWithHash(ctx context.Context, title *core.Title, lang, releaseName, movieHash string, videoSize int64) ([]Candidate, error)
SearchOnDemandWithHash is SearchOnDemand when the caller already has a movieHash and videoSize from cached ranges.
func (*Service) SearchWithHash ¶
func (s *Service) SearchWithHash(ctx context.Context, title *core.Title, lang, releaseName, movieHash string) ([]Candidate, error)
SearchWithHash prefers exact cached-source matches; empty hash uses release ranking.
func (*Service) Sidecars ¶
func (s *Service) Sidecars(ctx context.Context, title *core.Title, user *core.User, embedded []core.StreamInfo, prefs ...Preferences) ([]core.StreamInfo, error)
Sidecars returns external subtitle streams already cached for the user's preferred languages missing from the embedded tracks. It never searches or downloads; a file is fetched only when the viewer picks one (DownloadOnDemand). It returns (nil, nil) when the service is disabled or nothing cached is missing.
func (*Service) SidecarsForRelease ¶
func (s *Service) SidecarsForRelease(ctx context.Context, title *core.Title, user *core.User, embedded []core.StreamInfo, releaseName string, prefs ...Preferences) ([]core.StreamInfo, error)
SidecarsForRelease is Sidecars with the chosen release's name, so a selection remembered for that release is the one listed. It does not contact OpenSubtitles.
func (*Service) SidecarsWithHash ¶
func (s *Service) SidecarsWithHash(ctx context.Context, title *core.Title, user *core.User, embedded []core.StreamInfo, releaseName, movieHash string, prefs ...Preferences) ([]core.StreamInfo, error)
SidecarsWithHash uses an already-computed cached-source hash for exact release matching.
func (*Service) SourceText ¶ added in v0.3.0
SourceText returns one cached subtitle in a language other than except, preferring English. It reads only files already on disk.
func (*Service) StartCorrection ¶ added in v0.3.0
func (s *Service) StartCorrection(ctx context.Context, title *core.Title, id string, access ReleaseAccess)
StartCorrection measures the cached sidecar against the live session's source. It no-ops without a source handle (no prefetch). The first serve after a pick is uncorrected unless this measurement finishes before OpenFormatFrom's short wait.
func (*Service) StoreText ¶ added in v0.3.0
func (s *Service) StoreText(title *core.Title, lang, label, text string, fileID int64) (core.StreamInfo, error)
StoreText saves subtitle text the server produced (a translation or a transcript) and remembers it as the viewer's pick for that language. A later call with the same title, language and file ID returns the cached track and does not rewrite it. It never contacts a provider.
func (*Service) StoreUpload ¶ added in v0.3.0
func (s *Service) StoreUpload(title *core.Title, filename, format, language string, body []byte) (core.StreamInfo, error)
StoreUpload saves a viewer-uploaded sidecar as UTF-8 SRT+VTT. It is listed as an upload-sig track and does not overwrite another viewer's manual pick.
func (*Service) StreamByFileID ¶ added in v0.3.0
StreamByFileID returns a generated or picked sidecar already on disk. It does not download anything.
func (*Service) SyncHandler ¶
SyncHandler serves GET / of cached sidecars and POST /{id} as a manual timing adjust written into the correction store.
func (*Service) TimelineAligner ¶
TimelineAligner returns the service's (single) core.TimelineAligner. Wiring is one line each for markers and playback:
markers.New(markers.Options{..., Aligner: subs.TimelineAligner()})
playback.Deps{..., Aligner: subs.TimelineAligner()}
func (*Service) UnregisterPluginProvider ¶ added in v0.3.3
func (*Service) UpdateConfig ¶
func (s *Service) UpdateConfig(cfg config.OpenSubtitles)
UpdateConfig swaps the OpenSubtitles settings (e.g. from config.Store.Subscribe).
func (*Service) UpdateProviders ¶ added in v0.3.3
func (s *Service) UpdateProviders(ps []config.SubtitleProvider)
UpdateProviders swaps the subtitle provider order (config changes).
type SpeechWindow ¶ added in v0.3.0
SpeechWindow is one decoded audio slice used as a timing reference.
func DecodeAudioWindows ¶ added in v0.3.0
func DecodeAudioWindows(ctx context.Context, ffmpegPath, input string, duration time.Duration, windows int, span time.Duration) ([]SpeechWindow, error)
DecodeAudioWindows reads a few short windows of the first audio track with ffmpeg from a local file or HTTP URL. Skipped when ffmpeg is absent.
type TimingFit ¶ added in v0.3.0
type TimingFit struct {
Offset time.Duration
Rate float64
Confidence float64
Inliers int
Residual time.Duration
}
TimingFit is a constant offset plus a frame-rate scale that maps candidate cue times onto a reference clock:
ref ≈ candidate*Rate + Offset
func FitSpeechEnergy ¶ added in v0.3.0
func FitSpeechEnergy(cues []Cue, windows []SpeechWindow) (TimingFit, bool)
FitSpeechEnergy cross-correlates the subtitle on/off mask with a band-passed energy detector over a few short audio windows.
func FitSpeechEnergyOptions ¶ added in v0.3.0
func FitSpeechEnergyOptions(cues []Cue, windows []SpeechWindow, opt FitOptions) (TimingFit, bool)
FitSpeechEnergyOptions is FitSpeechEnergy with explicit thresholds.
func FitTiming ¶ added in v0.3.0
FitTiming aligns candidate cue start times to reference start times. Matching is language-independent: only the timing pattern is used.
func FitTimingOptions ¶ added in v0.3.0
func FitTimingOptions(candidate, reference []Cue, opt FitOptions) (TimingFit, bool)
FitTimingOptions is FitTiming with explicit thresholds (tests use a smaller cue count).