api

package
v0.1.147 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: GPL-2.0, GPL-3.0 Imports: 17 Imported by: 0

Documentation

Overview

Package api defines the internal contracts between subflux components. All cross-component calls go through these interfaces, enabling testability (mock any component) and swappability (e.g. SQLite → PostgreSQL).

This package imports only stdlib. Implementation packages import api, never the reverse.

This file contains consumer contracts: interfaces that consuming code depends on (Store, AuthStore, ConfigProvider, SearchEngine). Implementation/provider contracts live in interfaces_provider.go.

Index

Constants

View Source
const (
	CodeBadRequest         = "bad_request"
	CodeUnauthorized       = "unauthorized"
	CodeForbidden          = "forbidden"
	CodeNotFound           = "not_found"
	CodeMethodNotAllowed   = "method_not_allowed"
	CodeConflict           = "conflict"
	CodePayloadTooLarge    = "payload_too_large"
	CodeRateLimited        = "rate_limited"
	CodeBadGateway         = "bad_gateway"
	CodeServiceUnavailable = "service_unavailable"
	CodeInternalError      = "internal_error"
)

Generic codes (status mapped 1:1).

View Source
const (
	CodeAuthInvalidCredentials = "auth_invalid_credentials"
	CodeAuthAccountDisabled    = "auth_account_disabled"
	CodeAuthAccountNotSetup    = "auth_account_not_setup"
	CodeAuthPasswordTooShort   = "auth_password_too_short"
	CodeAuthPasswordBreached   = "auth_password_breached"
	CodeAuthSessionInvalid     = "auth_session_invalid"
	CodeAuthSessionRequired    = "auth_session_required"
	CodeAuthRoleRequired       = "auth_role_required"
	CodeAuthAPIKeyInvalid      = "auth_apikey_invalid"
	CodeAuthAPIKeyDisabled     = "auth_apikey_disabled"
	CodeAuthCSRF               = "auth_csrf"
)

Auth codes.

View Source
const (
	CodeWebAuthnSessionInvalid    = "webauthn_session_invalid"
	CodeWebAuthnRegisterFailed    = "webauthn_register_failed"
	CodeWebAuthnAssertionFailed   = "webauthn_assertion_failed"
	CodeWebAuthnUnsupportedOrigin = "webauthn_unsupported_origin"
)

WebAuthn codes.

View Source
const (
	CodeOIDCStateInvalid          = "oidc_state_invalid"
	CodeOIDCNonceInvalid          = "oidc_nonce_invalid"
	CodeOIDCExchangeFailed        = "oidc_exchange_failed"
	CodeOIDCUserInfoFailed        = "oidc_userinfo_failed"
	CodeOIDCAccountNotProvisioned = "oidc_account_not_provisioned"
)

OIDC codes.

View Source
const (
	CodeSetupAlreadyComplete = "setup_already_complete"
	CodeSetupPasswordInvalid = "setup_password_invalid"
)

Setup codes.

View Source
const (
	CodeConfigInvalid        = "config_invalid"
	CodeConfigUnreachableArr = "config_unreachable_arr"
	CodeConfigYAMLParse      = "config_yaml_parse"
	CodeConfigTooLarge       = "config_too_large"
	CodeConfigReloadFailed   = "config_reload_failed"
)

Config codes.

View Source
const (
	CodeScanInProgress         = "scan_in_progress"
	CodeScanNoTargets          = "scan_no_targets"
	CodeSearchInProgress       = "search_in_progress"
	CodeSearchProviderDisabled = "search_provider_disabled"
	CodeSearchNoResults        = "search_no_results"
	CodeDownloadFailed         = "download_failed"
	CodeUnlockNotHeld          = "unlock_not_held"
)

Scan / search / manual ops codes.

View Source
const (
	CodePathNotAllowed        = "path_not_allowed"
	CodeMediaNotFound         = "media_not_found"
	CodeSubtitleNotFound      = "subtitle_not_found"
	CodePreviewUnavailable    = "preview_unavailable"
	CodeSyncUnsupportedFormat = "sync_unsupported_format"
	CodeSyncNoReference       = "sync_no_reference"
	CodeSyncLowConfidence     = "sync_low_confidence"
)

File / preview / sync codes.

View Source
const (
	CodeQueryInvalidFilter = "query_invalid_filter"
	CodeQueryLimitExceeded = "query_limit_exceeded"
)

Query codes.

View Source
const (
	CodeProviderTimedOut      = "provider_timed_out"
	CodeProviderNotConfigured = "provider_not_configured"
	CodeArrUnreachable        = "arr_unreachable"
)

Provider / arr action codes.

View Source
const (

	// KeyStatus is the canonical JSON key for operation result status responses.
	// Used by server, scanning, and confighandlers packages.
	KeyStatus = "status"

	// ErrMsgSonarrNotConfigured is the canonical error message when Sonarr is nil.
	ErrMsgSonarrNotConfigured = "sonarr not configured"

	// ErrMsgRadarrNotConfigured is the canonical error message when Radarr is nil.
	ErrMsgRadarrNotConfigured = "radarr not configured"

	// HeaderXAPIKey is the canonical HTTP header name for API key authentication.
	// Used by both inbound (subflux API key verification) and outbound (Sonarr/Radarr) requests.
	HeaderXAPIKey = "X-Api-Key" //nolint:gosec // G101 false positive: header name, not a credential
)

Canonical JSON response keys and sentinel error messages shared across every helper below. Extracted to silence goconst warnings and to make a wire-format rename land in one place.

View Source
const (
	VariantStandard Variant = "standard"
	DefaultVariant          = VariantStandard
	VariantHI       Variant = "hi"
	VariantForced   Variant = "forced"

	// VariantAliasSDH is the alternative name for HI subtitles used in external
	// subtitle filenames and provider metadata.
	VariantAliasSDH = "sdh"
	// VariantAliasForeign is the alternative name for forced subtitles used in
	// external subtitle filenames and provider metadata.
	VariantAliasForeign = "foreign"
)

Subtitle variant identifiers. Shared across api, search, server, and cli so the auto and manual download paths agree on a single vocabulary.

View Source
const DefaultDownloadMaxAttempts = 3

DefaultDownloadMaxAttempts is the fallback number of download attempts per search when the config value is zero.

View Source
const DefaultManualProviderTimeout = 30 * time.Second

DefaultManualProviderTimeout is the per-provider search timeout for CLI and manual searches. Shared between clisearch and manualops to prevent silent divergence.

View Source
const DefaultProviderConcurrency = 4

DefaultProviderConcurrency is the default maximum number of parallel provider searches when the config value is zero.

View Source
const DefaultProviderPriority = 99

DefaultProviderPriority is used when no priority is configured for a provider.

View Source
const DefaultSyncMinConfidence = 0.6

DefaultSyncMinConfidence is the default minimum confidence for auto-sync to be applied. Used by config and syncing packages.

View Source
const EmbeddedSettingIgnoreASS = "ignore_ass"

EmbeddedSettingIgnoreASS is the setting key controlling ASS/SSA subtitle filtering.

View Source
const EmbeddedSettingIgnorePGS = "ignore_pgs"

EmbeddedSettingIgnorePGS is the setting key controlling PGS subtitle filtering.

View Source
const EmbeddedSettingIgnoreVobSub = "ignore_vobsub"

EmbeddedSettingIgnoreVobSub is the setting key controlling VobSub subtitle filtering.

View Source
const MaxSafeFileBytes = 10 << 20

MaxSafeFileBytes is the maximum file size (10 MB) for config files, subtitle files, and other user-supplied payloads. Prevents OOM from oversized or malicious inputs. Shared across config, subsync, and provider packages.

View Source
const SubtitleExtSRT = ".srt"

SubtitleExtSRT is the default subtitle file extension.

Variables

View Source
var DefaultScores = Scores{
	Hash:             100,
	Source:           28,
	ReleaseGroup:     23,
	StreamingService: 14,
	VideoCodec:       10,
	HDR:              8,
	Edition:          15,
	SeasonPack:       15,
}

DefaultScores for release attribute matching.

Movie (98): source=28, release_group=23, streaming_service=14, edition=15, video_codec=10, hdr=8. Episode (98): source=28, release_group=23, streaming_service=14, season_pack=15, video_codec=10, hdr=8.

Hash match = 100 directly, bypasses attribute scoring.

View Source
var ErrBinaryData = errors.New("binary archive data, not a subtitle")

ErrBinaryData indicates the downloaded data is a binary archive that could not be extracted, not a subtitle file.

View Source
var SubtitleExtsOnDisk = map[string]bool{
	".srt": true,
	".ass": true,
	".ssa": true,
	".sub": true,
}

SubtitleExtsOnDisk is the set of subtitle extensions recognized as standalone files on disk (from Sonarr/Radarr libraries). This excludes .vtt which only appears inside archives.

Functions

func AudioLanguages added in v0.1.106

func AudioLanguages(mi *arrapi.MediaInfo) []string

AudioLanguages parses a file's MediaInfo audio-languages string into deduplicated ISO 639-1 codes. Returns nil when mi is nil or empty.

func BadGatewayC

func BadGatewayC(w http.ResponseWriter, r *http.Request, code, msg string)

BadGatewayC writes a 502 with the given code and message.

func BadRequestC

func BadRequestC(w http.ResponseWriter, r *http.Request, code, msg string)

BadRequestC writes a 400 with the given code and message.

func BuildEpisodeID

func BuildEpisodeID(tvdbID int, imdbID string, season, episode int) string

BuildEpisodeID returns the canonical media ID for an episode. Prefer TVDB (Sonarr's canonical source), fall back to IMDB.

func BuildMediaID

func BuildMediaID(req *SearchRequest) string

BuildMediaID creates a stable identifier for a media item from a search request. Movies use TMDB ID (canonical for Radarr), episodes use TVDB ID (canonical for Sonarr). IMDB is only used as a last resort fallback.

func BuildMovieID

func BuildMovieID(tmdbID int, imdbID string) string

BuildMovieID returns the canonical media ID for a movie. Prefer TMDB (Radarr's canonical source), fall back to IMDB.

func BuildSeriesPrefix

func BuildSeriesPrefix(tvdbID int, imdbID string) string

BuildSeriesPrefix returns the media ID prefix for all episodes of a series. Used by coverage and missing-count queries to match all episodes via LIKE.

func ConflictC

func ConflictC(w http.ResponseWriter, r *http.Request, code, msg string)

ConflictC writes a 409 with the given code and message.

func CountNonTextBytes

func CountNonTextBytes(data []byte) int

CountNonTextBytes returns the number of bytes in data that are not printable text (control characters below TAB, or between CR and SPACE, excluding ESC). Used by ValidateSubtitleData and archive extraction.

func ForbiddenC

func ForbiddenC(w http.ResponseWriter, r *http.Request, code, msg string)

ForbiddenC writes a 403 with the given code and message.

func HasExcludeTag

func HasExcludeTag(tags []int, excludeIDs map[int]struct{}) bool

HasExcludeTag reports whether any of the item's tags are in the exclude set.

func InternalErrorC

func InternalErrorC(w http.ResponseWriter, r *http.Request, err error, code string, logAttrs ...any)

InternalErrorC writes a 500 with the given code and a generic message, and logs the raw error at ERROR level. The raw error is never echoed to the client to avoid leaking internal details.

func IsValidMediaPrefix

func IsValidMediaPrefix(prefix string) bool

IsValidMediaPrefix checks that a prefix parameter matches expected media ID formats. Prevents arbitrary prefix queries that could produce confusing results if the app is exposed without auth.

func JSONError

func JSONError(w http.ResponseWriter, err error, code int)

JSONError writes {"error": err.Error()} at the given status code.

SECURITY: err.Error() is echoed verbatim to the client. Only use when the error text is author-controlled or explicitly user-safe (e.g. errors.New("short, static message")). For wrapped internal errors, use InternalError(w, err, ...) which logs the raw error and returns a generic message. For user-facing validation failures, prefer BadRequest(w, r, code, msg) with an author-controlled message.

func JSONErrorWithCode

func JSONErrorWithCode(w http.ResponseWriter, r *http.Request, status int, code, msg string)

JSONErrorWithCode is JSONError + an error-code envelope. Use when the error string is safe to surface verbatim AND a stable machine- readable code is meaningful for the client. Delegates to webhttp.WriteError so the envelope and its request-id population match writeError.

func LangNameToISO

func LangNameToISO(name string) string

LangNameToISO converts a language name (as returned by Sonarr/Radarr) to an ISO 639-1 code. Accepts full names ("english") or 2-letter ASCII codes. Returns empty string for unrecognized input.

func ManualSubtitlePath

func ManualSubtitlePath(videoPath, lang string, n int, hi, forced bool) string

ManualSubtitlePath computes a numbered manual subtitle path. e.g., movie.fr.1.srt, movie.fr.2.srt for regular subs; movie.fr.hi.1.srt or movie.fr.forced.1.srt when the user deliberately downloaded an HI or forced variant. The variant tag appears before the number so parseExternalSubPath continues to recognize it on the next scan.

func MethodNotAllowedC

func MethodNotAllowedC(w http.ResponseWriter, r *http.Request, code string)

MethodNotAllowedC writes a 405 with the given code.

func NewSessionHashContext

func NewSessionHashContext(ctx context.Context, sessHash string) context.Context

NewSessionHashContext returns a new context carrying the session token hash for the current request. Only requireAuth populates this; API-key callers have an empty session hash.

func NewUserContext

func NewUserContext(ctx context.Context, u *auth.User) context.Context

NewUserContext returns a new context with the given user stored in it.

func NotFoundC

func NotFoundC(w http.ResponseWriter, r *http.Request, code, msg string)

NotFoundC writes a 404 with the given code and message.

func Ok

func Ok(w http.ResponseWriter)

Ok writes a 200 {"ok": true} response — the standard "action succeeded" reply for endpoints that don't return data. Delegates to webhttp.Ok so the body matches the {"ok":true} every webhttp consumer emits.

func OriginalLangCode added in v0.1.106

func OriginalLangCode(lang *arrapi.Language) string

OriginalLangCode returns the ISO 639-1 code for an arr original-language reference, or "" when the reference is nil or the name is unmapped.

func ParseAudioLangs

func ParseAudioLangs(raw string) []string

ParseAudioLangs splits a comma/slash-separated audio languages string into deduplicated ISO 639-1 codes.

func PayloadTooLargeC

func PayloadTooLargeC(w http.ResponseWriter, r *http.Request, code, msg string)

PayloadTooLargeC writes a 413 with the given code and message.

func SeasonEpisodeFileCount added in v0.1.106

func SeasonEpisodeFileCount(s *arrapi.Series, seasonNum int) int

SeasonEpisodeFileCount returns the number of episode files for a specific season, using Sonarr's per-season statistics. Returns 0 if the season is not found or statistics are unavailable.

func ServiceUnavailableC

func ServiceUnavailableC(w http.ResponseWriter, r *http.Request, code, msg string)

ServiceUnavailableC writes a 503 with the given code and message.

func SessionHashFromContext

func SessionHashFromContext(ctx context.Context) string

SessionHashFromContext returns the session token hash for the current request, or "" if the request was authenticated via API key (no session). Handlers that need to touch the current session (delete on logout, exclude from bulk session invalidation) read it here instead of re-parsing the cookie.

func SubtitlePath

func SubtitlePath(videoPath, lang string, hi, forced bool) string

SubtitlePath computes the subtitle file path for a video.

func TooManyRequestsC

func TooManyRequestsC(w http.ResponseWriter, r *http.Request, code, msg string)

TooManyRequestsC writes a 429 with the given code and message. The caller is responsible for setting the Retry-After header when appropriate.

func UnauthorizedC

func UnauthorizedC(w http.ResponseWriter, r *http.Request, code, msg string)

UnauthorizedC writes a 401 with the given code and message.

func UserFromContext

func UserFromContext(ctx context.Context) *auth.User

UserFromContext extracts the authenticated user from the request context. Returns nil if no user is present.

func ValidateSubtitleData

func ValidateSubtitleData(data []byte) error

ValidateSubtitleData checks whether data looks like subtitle text rather than a binary archive. Returns ErrBinaryData if the data matches a known archive magic signature or has too many non-text bytes.

func WithRequestID

func WithRequestID(ctx context.Context, id string) context.Context

WithRequestID attaches the given id to ctx under webhttp's request-id key. Production requests receive the id transparently from webhttp.Logging; tests use this to inject a known id.

func WriteJSON

func WriteJSON(w http.ResponseWriter, v any)

WriteJSON encodes v as JSON with status 200.

func WriteJSONStatus

func WriteJSONStatus(w http.ResponseWriter, code int, v any)

WriteJSONStatus encodes v as JSON with the given status code. The encode failure is logged at Debug (deliberately quiet: the status line is already on the wire and an app JSON payload that fails to encode is exceptional) rather than delegating to webhttp.WriteJSONStatus, which logs at Warn.

Types

type AdaptiveConfig

type AdaptiveConfig struct {
	InitialDelay      time.Duration
	MaxDelay          time.Duration
	BackoffMultiplier float64
	MaxAttempts       int
	Enabled           bool
}

AdaptiveConfig controls adaptive search backoff.

type AdminUserCreatedResponse

type AdminUserCreatedResponse struct {
	Username string    `json:"username"`
	Email    string    `json:"email"`
	Role     auth.Role `json:"role"`
	ID       int64     `json:"id"`
}

AdminUserCreatedResponse is the JSON response after admin creates a user.

type ArrConfig

type ArrConfig struct {
	URL       string
	APIKey    string
	PublicURL string
}

ArrConfig holds Sonarr or Radarr connection details.

type ArrConfigProvider

type ArrConfigProvider interface {
	SonarrConfig() ArrConfig
	RadarrConfig() ArrConfig
}

ArrConfigProvider provides Sonarr/Radarr connection configuration.

type AudioRuleJSON

type AudioRuleJSON struct {
	Audio     string             `json:"audio"`
	Subtitles []SubtitleTargJSON `json:"subtitles"`
}

AudioRuleJSON is a JSON-friendly audio rule.

type AudioSyncResult

type AudioSyncResult struct {
	Method     string
	Cues       []SubtitleCue
	Offset     int64   // milliseconds
	Confidence float64 // 0.0 to 1.0
	Applied    bool    // true if sync was applied and should be saved
}

AudioSyncResult holds the output of an audio-based sync operation.

type AuthConfigProvider

type AuthConfigProvider interface {
	AuthEnabled() bool
	BasicAuthEnabled() bool
	OIDCEnabled() bool
	OIDCConfig() auth.OIDCConfig
	SessionIdleTimeout() time.Duration
	SessionAbsoluteTimeout() time.Duration
	CheckBreachedPasswords() bool
	WebAuthnRPID() string
}

AuthConfigProvider provides authentication configuration.

type AuthError

type AuthError struct{ Msg string }

AuthError indicates invalid or expired credentials.

func (*AuthError) Error

func (e *AuthError) Error() string

type BackoffEntry

type BackoffEntry struct {
	LastTried time.Time  `json:"last_tried"`
	NextRetry time.Time  `json:"next_retry"`
	MediaType MediaType  `json:"media_type"`
	MediaID   string     `json:"media_id"`
	Language  string     `json:"language"`
	Provider  ProviderID `json:"provider"`
	Failures  int        `json:"failures"`
}

BackoffEntry represents an item in adaptive search backoff.

type BackoffParams

type BackoffParams struct {
	InitialDelay time.Duration
	MaxDelay     time.Duration
	Multiplier   float64
}

BackoffParams groups adaptive backoff configuration for RecordNoResult.

type BackoffStore

type BackoffStore interface {
	RecordNoResult(ctx context.Context, mediaType MediaType, mediaID, language string, providerName ProviderID, bp BackoffParams) error
	BackedOffProviders(ctx context.Context, mediaType MediaType, mediaID, language string, maxAttempts int) ([]ProviderID, error)
}

BackoffStore groups the adaptive-backoff persistence methods.

type CacheClearer

type CacheClearer interface {
	ClearCache()
}

CacheClearer is an optional interface for providers that cache download data (e.g. season pack zips). Called after scan completion to free memory. Providers implementing this get compile-time verification via var _ api.CacheClearer = (*Provider)(nil).

type CleanupResult

type CleanupResult struct {
	// Paths are subtitle file paths whose DB entries were removed.
	// The caller is responsible for validating and deleting files from disk.
	Paths []string
}

CleanupResult holds the outcome of a media cleanup operation. Used by both CleanupForMediaUpgrade and ReconcileState.

type ConfigDrift

type ConfigDrift struct {
	// RemovedLanguages are language codes that were in the old config
	// but not in the new one. Their search_attempts should be cleared.
	RemovedLanguages []string

	// RemovedProviders are provider names that were enabled in the old
	// config but disabled or removed in the new one. Their
	// search_attempts should be cleared.
	RemovedProviders []ProviderID

	// AdaptiveDisabled is true when adaptive search was enabled in the
	// old config but disabled in the new one. All search_attempts
	// should be cleared.
	AdaptiveDisabled bool
}

ConfigDrift describes DB cleanup actions needed after a config change.

func DetectDrift

func DetectDrift(
	oldLangs, newLangs []string,
	oldProviders, newProviders []ProviderID,
	oldAdaptiveEnabled, newAdaptiveEnabled bool,
) ConfigDrift

DetectDrift compares old and new config state to determine what DB cleanup is needed. Parameters are the extracted sets from each config. Duplicate entries in old slices are deduplicated; each removed item appears at most once in the result.

func (*ConfigDrift) Empty

func (d *ConfigDrift) Empty() bool

Empty returns true if no cleanup is needed.

type ConfigLoader

type ConfigLoader func(data []byte) (ConfigProvider, error)

ConfigLoader parses and validates config from raw YAML bytes.

type ConfigProvider

ConfigProvider gives read access to configuration. It composes the focused sub-interfaces defined in config_iface.go. Consumers should accept the narrowest sub-interface that satisfies their needs; ConfigProvider is for composition roots that need everything.

type CoverageStore

type CoverageStore interface {
	RecordSubtitleFiles(ctx context.Context, mediaType MediaType, mediaID string, files []SubtitleFile) (bool, error)
	UpsertSubtitleFile(ctx context.Context, mediaType MediaType, mediaID string, f *SubtitleFile) error
	GetSubtitleFiles(ctx context.Context, mediaType MediaType, mediaIDPrefix string) ([]SubtitleEntry, error)
	DeleteSubtitleFile(ctx context.Context, mediaType MediaType, mediaID, language string, variant Variant, source SubtitleSource, path string) error
	RecordScanState(ctx context.Context, rec *ScanRecord) error
	GetScanStates(ctx context.Context, mediaType MediaType, mediaIDPrefix string) ([]ScanStateRow, error)
	RecentlyScanned(ctx context.Context, cutoff time.Time) (map[string]bool, error)
	TotalSubtitleFiles(ctx context.Context) (int, error)
	LastScanTime(ctx context.Context) (string, error)
}

CoverageStore groups subtitle file tracking and scan state methods.

type DownloadMeta

type DownloadMeta struct {
	Title      string
	ImdbID     string
	ReleaseTag string
	VideoPath  string // Path to the video file (for reconciliation and upgrades).
	Season     int
	Episode    int
	Manual     bool // True if user manually selected this subtitle.
}

DownloadMeta holds optional metadata for a subtitle state record.

type DownloadRecord

type DownloadRecord struct {
	Meta         *DownloadMeta
	MediaType    MediaType
	MediaID      string
	Language     string
	Variant      Variant // subtitle variant (standard/hi/forced); empty is normalized to standard
	ProviderName ProviderID
	ReleaseName  string
	Path         string
	Score        int
}

DownloadRecord groups the parameters for Store.SaveDownload. Using a named struct (rather than positional string arguments) makes call sites self-documenting and prevents silent swaps of same-typed fields like Language/ProviderName.

type DownloadStore

type DownloadStore interface {
	SaveDownload(ctx context.Context, rec *DownloadRecord) error
	DownloadedRefs(ctx context.Context, mediaType MediaType, mediaID, language string) ([]DownloadedRef, error)
	CurrentScore(ctx context.Context, mediaType MediaType, mediaID, language string, variant Variant) (score int, mediaImported time.Time, found bool, err error)
}

DownloadStore groups subtitle download record persistence.

subtitle_state rows are keyed by the (media_type, media_id, language, variant) quad, so CurrentScore is answered per variant: an fr/forced download never shadows the fr/standard score. DownloadedRefs deliberately stays language-scoped (all variants): it feeds the manual-search popup's "on disk" markers, and the popup lists every variant of the language.

type DownloadedRef

type DownloadedRef struct {
	ReleaseName string
	Provider    ProviderID
}

DownloadedRef identifies a previously-downloaded subtitle by its release name and provider. Returned by Store.DownloadedRefs to mark matching entries in the manual search popup as already on disk.

type EmbeddedTrack

type EmbeddedTrack struct {
	Codec           string
	Lang            string
	Name            string
	Index           int
	Forced          bool
	HearingImpaired bool
}

EmbeddedTrack represents a subtitle track detected inside a video container.

type HistoryStore

type HistoryStore interface {
	HistoryMediaIDs(ctx context.Context, mediaType MediaType, mediaIDPrefix string) ([]string, error)
}

HistoryStore groups download-history lookup methods.

type KeyGenerated added in v0.1.29

type KeyGenerated struct {
	CreatedAt time.Time `json:"created_at"`
	Key       string    `json:"key"`
	KeyPrefix string    `json:"key_prefix"`
	KeySuffix string    `json:"key_suffix"`
	Label     string    `json:"label"`
	ID        int64     `json:"id"`
}

KeyGenerated is the JSON response after generating an API key.

type LanguageResolver

type LanguageResolver interface {
	ResolveTargetsWithFallback(originalLang string, audioLangs []string) []SubtitleTarget
	LanguageCodes() []string
	ProvidersForTarget(t *SubtitleTarget, allProviders []ProviderID) []ProviderID
	MinScoreForTarget(t *SubtitleTarget, mediaType MediaType) int
}

LanguageResolver resolves subtitle targets from audio language context.

type LanguageRulesJSON

type LanguageRulesJSON struct {
	Rules   []AudioRuleJSON    `json:"rules,omitempty"`
	Default []SubtitleTargJSON `json:"default,omitempty"`
}

LanguageRulesJSON is the language rules in a JSON-serializable format for the settings UI.

type LogFormat

type LogFormat string

LogFormat is a typed string for log output formats.

type LogLevel

type LogLevel string

LogLevel is a typed string for log verbosity levels.

type LoginSuccess added in v0.1.29

type LoginSuccess struct {
	Redirect string     `json:"redirect"`
	User     MeResponse `json:"user"`
}

LoginSuccess is the JSON response after successful login.

type MaintStore

type MaintStore interface {
	DeleteStateByPaths(ctx context.Context, paths []string) (CleanupResult, error)
	CleanupDrift(ctx context.Context, drift ConfigDrift) error
	ReconcileState(ctx context.Context) (ReconcileResult, error)
}

MaintStore groups maintenance and cleanup methods.

type ManualLockEntry

type ManualLockEntry struct {
	MediaType MediaType `json:"media_type"`
	MediaID   string    `json:"media_id"`
	Language  string    `json:"language"`
	Variant   Variant   `json:"variant"`
	Count     int       `json:"count"`
}

ManualLockEntry represents a manually locked media+language+variant quad.

type ManualLockStore

type ManualLockStore interface {
	// IsManuallyLocked reports whether the quad has a manual row. An empty
	// variant asks whether ANY variant of the language is locked.
	IsManuallyLocked(ctx context.Context, mediaType MediaType, mediaID, language string, variant Variant) (bool, error)
	// ClearManualLock clears the quad's lock. An empty variant clears the
	// locks of ALL variants of the language.
	ClearManualLock(ctx context.Context, mediaType MediaType, mediaID, language string, variant Variant) error
	// ManualDownloadCount counts the quad's manual rows (exact variant).
	ManualDownloadCount(ctx context.Context, mediaType MediaType, mediaID, language string, variant Variant) (int, error)
	// ManualSubtitlePaths returns the manual rows' file paths. An empty
	// variant returns the paths of ALL variants of the language.
	ManualSubtitlePaths(ctx context.Context, mediaType MediaType, mediaID, language string, variant Variant) ([]string, error)
	// NextManualNumber returns the next manual ordinal for the quad (exact
	// variant): movie.fr.1.srt and movie.fr.forced.1.srt count independently.
	NextManualNumber(ctx context.Context, mediaType MediaType, mediaID, language string, variant Variant) int
}

ManualLockStore groups manual override lock persistence. Locks live on the (media_type, media_id, language, variant) quad: a manual forced download locks only the forced target, leaving standard/hi automation untouched.

Methods documented as accepting an empty variant treat "" as "any/all variants of the language"; the rest require an exact variant.

type MatchMethod

type MatchMethod string

MatchMethod is a typed string identifying how a subtitle was matched to the requested media. Use the exported constants rather than raw string literals. Follows the same pattern as MediaType and AuthMethod.

const (
	MatchByHash     MatchMethod = "hash"
	MatchByIMDB     MatchMethod = "imdb"
	MatchByTitle    MatchMethod = "title"
	MatchByTVDB     MatchMethod = "tvdb"
	MatchByTMDB     MatchMethod = "tmdb"
	MatchByEmbedded MatchMethod = "embedded"
)

Match method constants. Canonical source for all packages.

func (MatchMethod) String

func (m MatchMethod) String() string

String implements fmt.Stringer.

type MatchSet

type MatchSet struct {
	Hash             bool
	Source           bool
	ReleaseGroup     bool
	StreamingService bool
	VideoCodec       bool
	HDR              bool
	Edition          bool
	SeasonPack       bool
	SeriesIMDB       bool
	IMDB             bool
}

MatchSet tracks which attributes matched between a subtitle and a video. Each field corresponds to a release attribute; struct fields make invalid keys impossible at compile time.

func (MatchSet) Keys

func (m MatchSet) Keys() []string

Keys returns the names of matched attributes (for debug logging and JSON breakdown).

type MeResponse added in v0.1.29

type MeResponse struct {
	Username    string    `json:"username"`
	Role        auth.Role `json:"role"`
	ID          int64     `json:"id"`
	HasPasskeys bool      `json:"has_passkeys"`
	OIDCLinked  bool      `json:"oidc_linked"`
	HasPassword bool      `json:"has_password"`
	CanLinkOIDC bool      `json:"can_link_oidc"`
}

MeResponse is the JSON response for GET /api/auth/me.

type MediaType

type MediaType string

MediaType is a typed string identifying the kind of media being searched. Use the exported constants rather than raw string literals.

const (
	MediaTypeMovie   MediaType = "movie"
	MediaTypeEpisode MediaType = "episode"
)

Media type constants used by SearchRequest.MediaType and related helpers. Canonical source for all packages; import from here instead of declaring local unexported copies.

func (MediaType) String

func (mt MediaType) String() string

String implements fmt.Stringer.

func (MediaType) Valid

func (mt MediaType) Valid() bool

Valid reports whether mt is a recognized media type.

type PasskeyRegistered added in v0.1.29

type PasskeyRegistered struct {
	CreatedAt time.Time `json:"created_at"`
	Name      string    `json:"name"`
	Transport string    `json:"transport"`
	ID        int64     `json:"id"`
}

PasskeyRegistered is the JSON response after successful passkey registration.

type PathValidator

type PathValidator interface {
	MediaRoots() []string
	ValidatePath(ctx context.Context, path string) error
	RemoveUnderRoot(ctx context.Context, path string) error
}

PathValidator provides media path validation.

type PollKey

type PollKey string

PollKey identifies an arr-source poll-timestamp row in the store. Using a typed string instead of bare string prevents typo-induced silent failures (e.g. "Sonarr" capitalization or "sonar" typo would silently insert a new row and force history re-fetch).

const (
	PollKeySonarr PollKey = "sonarr"
	PollKeyRadarr PollKey = "radarr"
)

Canonical poll-key values. New arr sources should add a constant here.

func (PollKey) Valid

func (k PollKey) Valid() bool

Valid returns true if the PollKey is one of the canonical values.

type PollStore

type PollStore interface {
	GetPollTimestamp(ctx context.Context, key PollKey) (time.Time, error)
	SetPollTimestamp(ctx context.Context, key PollKey, t time.Time) error
}

PollStore groups arr webhook/poll timestamp persistence.

type PostProcessConfig

type PostProcessConfig struct {
	StripHI          bool `json:"strip_hi"`
	StripTags        bool `json:"strip_tags"`
	NormalizeUTF8    bool `json:"normalize_utf8"`
	CleanWhitespace  bool `json:"clean_whitespace"`
	NormalizeEndings bool `json:"normalize_endings"`
	RemoveEmpty      bool `json:"remove_empty"`
}

PostProcessConfig controls subtitle post-processing.

type Provider

type Provider interface {
	// Name returns the provider identifier (e.g. "opensubtitles", "yifysubtitles").
	Name() ProviderID

	// Search finds subtitles matching the request.
	Search(ctx context.Context, req *SearchRequest) ([]Subtitle, error)

	// Download fetches the subtitle content for the given search result.
	Download(ctx context.Context, sub *Subtitle) ([]byte, error)
}

Provider is the interface all subtitle providers must implement.

type ProviderCfg

type ProviderCfg struct {
	Settings map[string]any
	Priority int // Lower = higher trust. 0 means unset (defaults to 99).
	Enabled  bool
}

ProviderCfg is a generic provider configuration block.

type ProviderConfigProvider

type ProviderConfigProvider interface {
	ProviderConfigs() map[ProviderID]ProviderCfg
	ProviderPriority(name ProviderID) int
}

ProviderConfigProvider provides provider settings.

type ProviderID

type ProviderID string

ProviderID is a typed string identifying a subtitle provider. Use this instead of raw strings when passing provider names to metrics, backoff, and other subsystems that key on provider identity.

const (
	ProviderNameEmbedded      ProviderID = "embedded"
	ProviderNameMock          ProviderID = "mock"
	ProviderNameHDBits        ProviderID = "hdbits"
	ProviderNameOpenSubtitles ProviderID = "opensubtitles"
	ProviderNameSubSource     ProviderID = "subsource"
	ProviderNameAnimeTosho    ProviderID = "animetosho"
	ProviderNameYifySubtitles ProviderID = "yifysubtitles"
	ProviderNameSubDL         ProviderID = "subdl"
	ProviderNameBetaSeries    ProviderID = "betaseries"
	ProviderNameGestdown      ProviderID = "gestdown"
)

Provider name constants (canonical SSOT). All packages MUST reference these instead of declaring local string literals.

func (ProviderID) String

func (p ProviderID) String() string

String implements fmt.Stringer.

type ProviderRegistry

type ProviderRegistry interface {
	// LoadAll instantiates providers from the given config map, skipping
	// unconfigured entries. Returns an error if a configured provider fails.
	LoadAll(ctx context.Context, configs map[ProviderID]ProviderCfg) ([]Provider, error)
	// ProviderNames returns all registered provider names in priority order.
	ProviderNames() []ProviderID
	// Schema returns the UI label and settings fields for a named provider.
	Schema(name ProviderID) (label string, fields []ProviderSchemaField)
}

ProviderRegistry manages provider factories and schema metadata.

type ProviderSchema

type ProviderSchema struct {
	Name          string        `json:"name"`
	Label         string        `json:"label"`
	Settings      []SchemaField `json:"settings,omitempty"`
	AlwaysEnabled bool          `json:"always_enabled,omitempty"`
}

ProviderSchema describes a single provider's settings fields.

func BuildProviderSchemas

func BuildProviderSchemas(reg ProviderRegistry, exclude ...string) []ProviderSchema

BuildProviderSchemas converts the registry's provider metadata into ProviderSchema entries for the UI. Names in exclude are omitted.

type ProviderSchemaField

type ProviderSchemaField struct {
	Key     string `json:"key"`
	Label   string `json:"label"`
	Type    string `json:"type"` // text, secret, bool
	Default string `json:"default,omitempty"`
	Help    string `json:"help,omitempty"`
	Secret  bool   `json:"secret,omitempty"`
}

ProviderSchemaField describes a single provider setting for the UI schema.

type ProviderStatus added in v0.1.29

type ProviderStatus struct {
	LastError         string        `json:"last_error,omitempty"`
	CooldownRemaining time.Duration `json:"cooldown_remaining,omitempty"`
	RecentFailures    int           `json:"recent_failures"`
	Threshold         int           `json:"threshold"`
	TimedOut          bool          `json:"timed_out"`
}

ProviderStatus is the state of a single provider's timeout.

type ProviderTimeoutManager

type ProviderTimeoutManager interface {
	ProviderTimeouts() (status map[ProviderID]ProviderStatus, enabled bool)
	ResetTimeouts()
}

ProviderTimeoutManager manages provider timeout state.

type ProvidersResponse

type ProvidersResponse struct {
	Providers map[ProviderID]ProviderStatus `json:"providers"`
	Enabled   bool                          `json:"enabled"`
}

ProvidersResponse is the JSON response for GET /api/providers/timeout.

type QueryStore

type QueryStore interface {
	GetState(ctx context.Context, q *StateQuery) ([]StateEntry, error)
	GetBackoffItems(ctx context.Context) ([]BackoffEntry, error)
	GetBackoffByPrefix(ctx context.Context, mediaType MediaType, mediaIDPrefix string) ([]BackoffEntry, error)
	GetManualLocks(ctx context.Context) ([]ManualLockEntry, error)
	Stats(ctx context.Context) (downloads, attempts int, err error)
}

QueryStore groups read-only state inspection methods.

type RadarrClient added in v0.1.106

type RadarrClient interface {
	Ping(ctx context.Context) error
	GetMovies(ctx context.Context) ([]arrapi.Movie, error)
	GetMovieByID(ctx context.Context, id int) (arrapi.Movie, error)
	GetHistorySince(ctx context.Context, since time.Time, eventTypes ...arrapi.EventType) ([]arrapi.HistoryRecord, error)
	GetWantedMovies(ctx context.Context, excludeTagIDs map[int]struct{}, fn func(arrapi.Movie) error) error
	ResolveExcludeTagIDs(ctx context.Context, tagNames []string, logMissing bool) map[int]struct{}
	RescanMovie(ctx context.Context, movieID int) error
}

RadarrClient is the Radarr-side surface subflux consumes: library reads, per-item lookups, import-history polling, wanted-movie iteration, exclude-tag resolution, and a post-download rescan.

type RateLimitError

type RateLimitError struct {
	Msg        string
	RetryAfter time.Duration
}

RateLimitError indicates the provider's rate limit was exceeded. RetryAfter, when non-zero, is the hint from the upstream's Retry-After header (delta-seconds or HTTP-date resolved to a positive duration). Consumers may use it to schedule the next attempt; a zero value means no hint was provided.

func (*RateLimitError) Error

func (e *RateLimitError) Error() string

type ReconcileResult

type ReconcileResult struct {
	// Deleted contains subtitle paths removed because the video is gone.
	Deleted CleanupResult

	// ResetCount is the number of entries reset for re-search because
	// the subtitle file was missing but the video still exists.
	ResetCount int64
}

ReconcileResult holds the outcome of a state reconciliation pass.

type ScanOutcome

type ScanOutcome string

ScanOutcome is a typed string for scan result classification. Canonical source; scanning/ and server/ should import from here.

const (
	ScanFound    ScanOutcome = "found"
	ScanSkipped  ScanOutcome = "skipped"
	ScanNoResult ScanOutcome = "none"
	// ScanBackedOff means every language target that needed a search had all
	// of its providers in adaptive backoff, so no provider was queried at
	// all. Distinct from ScanNoResult ("we looked, nothing found") and
	// ScanSkipped ("nothing needed a search"): "we already know there is
	// probably nothing, so we didn't look". Never recorded in the season
	// early-termination tracker and counted in its own stats bucket.
	ScanBackedOff ScanOutcome = "backed_off"
)

Scan outcome constants.

type ScanRecord

type ScanRecord struct {
	MediaType MediaType
	MediaID   string
	Title     string
	AudioLang string
	Season    int
	Episode   int
}

ScanRecord groups the parameters for CoverageStore.RecordScanState.

type ScanStateRow

type ScanStateRow struct {
	ScannedAt string `json:"scanned_at"`
	MediaID   string `json:"media_id"`
	Title     string `json:"title"`
	AudioLang string `json:"audio_lang"`
	Season    int    `json:"season,omitempty"`
	Episode   int    `json:"episode,omitempty"`
}

ScanStateRow records when a media item was last scanned.

type ScanStats

type ScanStats struct {
	// Pre-scan totals (from API).
	TotalSeries       int
	TotalEpisodeFiles int // Sum of episodeFileCount across all series.
	TotalMovies       int
	TotalMovieFiles   int // Movies with hasFile=true.

	// Post-scan outcomes for episodes. EpisodesSearched counts every episode
	// the scan loop processed (it drives progress reporting); the other
	// fields are its per-outcome buckets.
	EpisodesSearched  int // Episodes processed by the scan loop.
	EpisodesSkipped   int // Episodes with existing subs (no search needed).
	EpisodesFound     int // Episodes where a subtitle was downloaded.
	EpisodesNoResult  int // Episodes searched but no subtitle found.
	EpisodesBackedOff int // Episodes where every needed provider was in adaptive backoff (no query ran).
	SeriesSkipped     int // Series skipped by show-level pre-check.

	// Post-scan outcomes for movies.
	MoviesSearched  int
	MoviesSkipped   int
	MoviesFound     int
	MoviesNoResult  int
	MoviesBackedOff int
}

ScanStats tracks scan progress and outcomes for logging.

type SchemaField

type SchemaField struct {
	Key         string         `json:"key"`
	Label       string         `json:"label"`
	Type        string         `json:"type"`
	Default     string         `json:"default,omitempty"`
	Help        string         `json:"help,omitempty"`
	Placeholder string         `json:"placeholder,omitempty"`
	Min         string         `json:"min,omitempty"`
	Max         string         `json:"max,omitempty"`
	ShowWhen    string         `json:"show_when,omitempty"`
	Requires    string         `json:"requires,omitempty"`
	Group       string         `json:"group,omitempty"`
	Fields      []SchemaField  `json:"fields,omitempty"`
	Options     []SchemaOption `json:"options,omitempty"`
	Secret      bool           `json:"secret,omitempty"`
	Required    bool           `json:"required,omitempty"`
}

SchemaField describes a single configuration field for the UI.

type SchemaFunc

type SchemaFunc func(providers []ProviderSchema) []SchemaSection

SchemaFunc returns the full configuration schema for the UI.

type SchemaOption

type SchemaOption struct {
	Value string `json:"value"`
	Label string `json:"label"`
}

SchemaOption is a value+label pair for select fields.

type SchemaSection

type SchemaSection struct {
	Key              string           `json:"key"`
	Title            string           `json:"title"`
	Type             string           `json:"type"`
	Help             string           `json:"help,omitempty"`
	RequiredGroup    string           `json:"required_group,omitempty"`
	EnableKey        string           `json:"enable_key,omitempty"`
	Fields           []SchemaField    `json:"fields,omitempty"`
	ProviderTemplate []SchemaField    `json:"provider_template,omitempty"`
	Providers        []ProviderSchema `json:"providers,omitempty"`
}

SchemaSection describes a top-level config section.

type ScorePreview added in v0.1.29

type ScorePreview struct {
	Tier        ScoreTier `json:"tier"`
	Score       int       `json:"score"`
	ScoreNoHash int       `json:"score_no_hash"`
}

ScorePreview is the JSON response for POST /api/score/preview.

type ScoreResult

type ScoreResult struct {
	Tier        ScoreTier // Quality tier label (excellent/good/acceptable/minimal/none).
	Score       int       // Final score including hash match bonus.
	ScoreNoHash int       // Score from release attributes only, excluding hash match.
}

ScoreResult holds the output of a score simulation.

type ScoreSimulator

type ScoreSimulator interface {
	SimulateScore(mediaType MediaType, videoRelease, subRelease string, matchedBy MatchMethod) ScoreResult
	ScoreSubtitles(req *SearchRequest, results []Subtitle) []ScoredResult
}

ScoreSimulator provides subtitle scoring capabilities.

type ScoreTier

type ScoreTier string

ScoreTier is a typed string for release quality tier labels.

const (
	TierExcellent  ScoreTier = "excellent"  // 80+
	TierGood       ScoreTier = "good"       // 50+
	TierAcceptable ScoreTier = "acceptable" // 20+
	TierMinimal    ScoreTier = "minimal"    // 1+
	TierNone       ScoreTier = "none"       // 0
)

Tier constants for release quality scoring.

type ScoredResult

type ScoredResult struct {
	Matches map[string]int // Per-category score contributions.
	Sub     Subtitle
	Score   int
}

ScoredResult is a scored subtitle for use by the manual search and CLI APIs.

type Scorer

type Scorer interface {
	// Score computes a quality score for a subtitle match. Returns the full
	// score (including hash bonus) and the release-attribute-only score.
	Score(video *VideoInfo, sub SubtitleInfo, matches MatchSet) (score, scoreNoHash int)
	// ScoreToTier maps a numeric score to a human-readable tier label
	// (excellent/good/acceptable/minimal/none) based on media type thresholds.
	ScoreToTier(score int, mediaType MediaType) ScoreTier
}

Scorer evaluates subtitle matches against video metadata.

type Scores

type Scores struct {
	Hash             int `json:"hash" yaml:"hash"`
	Source           int `json:"source" yaml:"source"`
	ReleaseGroup     int `json:"release_group" yaml:"release_group"`
	StreamingService int `json:"streaming_service" yaml:"streaming_service"`
	VideoCodec       int `json:"video_codec" yaml:"video_codec"`
	HDR              int `json:"hdr" yaml:"hdr"`
	Edition          int `json:"edition" yaml:"edition"`         // movies only
	SeasonPack       int `json:"season_pack" yaml:"season_pack"` // episodes only
}

Scores defines the weight for each release attribute match type.

Identity fields (series, season, episode, title, year) are not scored here; they are enforced by the provider query (identity gate). Resolution is excluded; its signal is captured by source + release_group.

Movie-only: Edition. For episodes, edition=0 and season_pack takes its place. Applicable non-hash weights sum to 98 for each media type. Hash match bypasses attribute scoring entirely (score = 100).

type ScoringConfig

type ScoringConfig interface {
	Scores() Scores
}

ScoringConfig provides scoring weight configuration.

type SearchConfig

type SearchConfig struct {
	ExcludeArrTags         []string
	ScanInterval           time.Duration
	ProviderTimeout        time.Duration
	ScanDelay              time.Duration
	MinScore               int
	UpgradeWindowDays      int
	DownloadMaxAttempts    int // Max download attempts per search (0 = default DefaultDownloadMaxAttempts)
	MaxProviderConcurrency int // Max parallel provider searches (0 = default DefaultProviderConcurrency)
	MaxSSEClients          int // Max concurrent SSE connections (0 = default 32)
	UpgradeEnabled         bool
}

SearchConfig controls search behavior.

type SearchConfigProvider

type SearchConfigProvider interface {
	Search() SearchConfig
	Adaptive() AdaptiveConfig
	PostProcessConfig() PostProcessConfig
	SyncConfig() SyncConfig
}

SearchConfigProvider provides search behavior configuration.

type SearchEngine

SearchEngine composes all search sub-interfaces for composition roots.

type SearchRequest

type SearchRequest struct {
	MediaType         MediaType // "episode" or "movie"
	ImdbID            string    // IMDB ID; fallback for providers without TMDB/TVDB support
	Title             string
	AlternativeTitles []string // from Sonarr/Radarr alternateTitles
	EpisodeTitle      string   // Episode title from Sonarr (for identity validation across numbering schemes)
	ReleaseName       string
	VideoPath         string   // Absolute path to the video file (for embedded provider and sync)
	VideoHash         string   // OpenSubtitles hash
	AudioLang         string   // resolved audio language (for coverage tracking)
	Languages         []string // ISO 639-1 codes to search for
	TmdbID            int      // Movie TMDB ID from Radarr; 0 for episodes
	VideoSize         int64    // file size for hash-based search
	Year              int
	Season            int // 0 for movies
	Episode           int // 0 for movies
	TvdbID            int // Series TVDB ID from Sonarr; 0 for movies
	MaxResults        int // 0 = provider default (typically one page)

	// Alternate episode numbering from Sonarr (0 = not available).
	// Scene numbers come from TheXEM; absolute numbers from TVDB.
	SceneSeason     int
	SceneEpisode    int
	AbsoluteEpisode int

	// ForceUpgrade makes the search engine treat all external subtitles
	// as eligible for upgrade, regardless of the upgrade_enabled config.
	// Set by manual scan triggers (series/season search buttons).
	ForceUpgrade bool
}

SearchRequest holds the parameters for a subtitle search across providers.

func (*SearchRequest) MediaLabel

func (r *SearchRequest) MediaLabel() string

MediaLabel returns a human-readable label for log messages. Movies: "Inception (2010)", episodes: "Bleach (2004) - S09E15".

type SearchResult

type SearchResult struct {
	Paths           []string // Subtitle files downloaded.
	FoundLangs      []string // Language codes that had at least one subtitle downloaded.
	SearchedLangs   []string // Language codes whose group actually ran (not skipped); superset of FoundLangs.
	Searched        int      // Languages where providers were actually queried.
	Skipped         int      // Languages skipped (subs already exist, not eligible for upgrade).
	BackedOff       int      // Languages that needed a search but had every provider in adaptive backoff (no query ran).
	CoverageChanged bool     // True if RecordSubtitleFiles detected changes on disk.
}

SearchResult holds the outcome of a SearchTargets call.

type SearchTarget

type SearchTarget struct {
	MinScore  *int     `json:"min_score,omitempty"`
	Code      string   `json:"code"`
	Variant   string   `json:"variant"`
	Providers []string `json:"providers,omitempty"`
	Exclude   []string `json:"exclude,omitempty"`
}

SearchTarget describes a single subtitle search target in the API response.

type SearchTargets added in v0.1.29

type SearchTargets struct {
	OrigLang   string         `json:"orig_lang"`
	AudioLangs []string       `json:"audio_langs"`
	Targets    []SearchTarget `json:"targets"`
}

SearchTargets is the JSON response for GET /api/search/targets.

type ServerConfig

type ServerConfig interface {
	ServerPort() int
	PollInterval() time.Duration
	LoggingLevel() LogLevel
	LoggingFormat() LogFormat
}

ServerConfig provides server runtime configuration.

type SetupStatus added in v0.1.29

type SetupStatus struct {
	SetupRequired bool `json:"setup_required"`
	ConfigValid   bool `json:"config_valid"`
}

SetupStatus is the JSON response for GET /api/auth/setup.

type ShowSubtitleCounter

type ShowSubtitleCounter interface {
	// CountShowSubtitles returns the total number of subtitles available for
	// a show in the given language. The request should have ImdbID set and
	// Season/Episode set to 0.
	CountShowSubtitles(ctx context.Context, imdbID, lang string) (int, error)
}

ShowSubtitleCounter can count total subtitles for a show+language without specifying season/episode. Used for show-level pre-checks: if a show has very few subtitles relative to its episode count, skip the entire series. Only providers with show-level query support implement this (OpenSubtitles).

type SignalData added in v0.1.29

type SignalData struct {
	RPID          string   `json:"rp_id"`
	UserID        string   `json:"user_id"`
	Name          string   `json:"name"`
	DisplayName   string   `json:"display_name"`
	CredentialIDs []string `json:"credential_ids"`
}

SignalData is the JSON response for GET /api/auth/webauthn/signal-data.

type SonarrClient added in v0.1.106

type SonarrClient interface {
	Ping(ctx context.Context) error
	GetSeries(ctx context.Context) ([]arrapi.Series, error)
	GetEpisodes(ctx context.Context, seriesID int) ([]arrapi.Episode, error)
	GetSeriesByID(ctx context.Context, id int) (arrapi.Series, error)
	GetEpisodeByID(ctx context.Context, id int) (arrapi.Episode, error)
	GetHistorySince(ctx context.Context, since time.Time, eventTypes ...arrapi.EventType) ([]arrapi.HistoryRecord, error)
	GetWantedEpisodes(ctx context.Context, excludeTagIDs map[int]struct{}, fn func(arrapi.Series, arrapi.Episode) error) error
	ResolveExcludeTagIDs(ctx context.Context, tagNames []string, logMissing bool) map[int]struct{}
	RescanSeries(ctx context.Context, seriesID int) error
}

SonarrClient is the Sonarr-side surface subflux consumes: library reads, per-item lookups, import-history polling, wanted-episode iteration, exclude-tag resolution, and a post-download rescan.

type StateEntry

type StateEntry struct {
	MediaImported time.Time  `json:"media_imported"`
	Title         string     `json:"title"`
	MediaID       string     `json:"media_id"`
	Language      string     `json:"language"`
	Variant       Variant    `json:"variant"`
	Provider      ProviderID `json:"provider"`
	Path          string     `json:"path"`
	ReleaseName   string     `json:"release_name"`
	ImdbID        string     `json:"imdb_id,omitempty"`
	MediaType     MediaType  `json:"media_type"`
	ID            int64      `json:"id"`
	Score         int        `json:"score"`
	Season        int        `json:"season,omitempty"`
	Episode       int        `json:"episode,omitempty"`
	Manual        bool       `json:"manual"`
}

StateEntry represents a subtitle state record for API responses.

type StateQuery

type StateQuery struct {
	MediaType MediaType
	Language  string
	Provider  ProviderID
	Search    string
	Limit     int
	Offset    int
}

StateQuery groups the filter parameters for QueryStore.GetState. Using a named struct (rather than positional string/int arguments) makes call sites self-documenting and prevents silent swaps of same-typed fields like Language/Provider/Search.

type Stats added in v0.1.29

type Stats struct {
	LastScan            string `json:"last_scan"`
	Downloads           int    `json:"downloads"`
	Attempts            int64  `json:"attempts"`
	ScanIntervalSeconds int    `json:"scan_interval_seconds"`
	TotalSubs           int    `json:"total_subs"`
	TotalSeries         int    `json:"total_series"`
	TotalMovies         int    `json:"total_movies"`
	MissingSubs         int    `json:"missing_subs"`
	Partial             bool   `json:"partial"`
}

Stats is the JSON response for GET /api/stats.

type Store

Store persists search state and subtitle state. All methods accept context.Context for cancellation and timeout propagation.

Methods are grouped by domain concern into composable sub-interfaces defined in store_iface.go. Consumers should accept the narrowest sub-interface that satisfies their needs.

type Subtitle

type Subtitle struct {
	Provider    ProviderID
	ID          string // provider-specific identifier
	Language    string // ISO 639-1 code
	ReleaseName string
	DownloadURL string
	MatchedBy   MatchMethod // how this subtitle was matched (hash, title, imdb, etc.)
	Title       string      // show/movie title from provider (for identity validation)
	Year        int         // year from provider (0 if unknown)
	Season      int         // season from provider (0 if unknown or movie)
	Episode     int         // episode from provider (0 if unknown or movie)
	HearingImp  bool
	Forced      bool
}

Subtitle is a search result returned by a provider.

type SubtitleCue

type SubtitleCue struct {
	Text  string
	Start time.Duration
	End   time.Duration
}

SubtitleCue represents a single subtitle entry with timing.

type SubtitleEntry added in v0.1.29

type SubtitleEntry struct {
	MediaID   string `json:"media_id"`
	Language  string `json:"language"`
	Variant   string `json:"variant"`
	Source    string `json:"source"`
	Codec     string `json:"codec,omitempty"`
	Path      string `json:"path,omitempty"`
	VideoPath string `json:"video_path,omitempty"`
	Score     int    `json:"score,omitempty"`
	OffsetMs  int64  `json:"offset_ms,omitempty"`
}

SubtitleEntry is the JSON shape returned by coverage queries.

type SubtitleFile

type SubtitleFile struct {
	Language string         // ISO 639-1
	Variant  Variant        // "standard", "hi", "forced"
	Source   SubtitleSource // SourceEmbedded or SourceExternal
	Codec    string         // e.g. "subrip", "ass"; empty for external
	Path     string         // file path for external; empty for embedded
}

SubtitleFile represents a discovered subtitle (embedded or external) for a media file.

type SubtitleInfo

type SubtitleInfo struct {
	HashVerifiable bool
}

SubtitleInfo describes a subtitle result for scoring purposes.

type SubtitlePostProcessor

type SubtitlePostProcessor interface {
	SyncAndPostProcess(ctx context.Context, data []byte, videoPath, lang string, variant Variant) (synced []byte, offsetMs int64)
	HashFile(ctx context.Context, path string) (hash string, size int64, err error)
}

SubtitlePostProcessor handles post-download subtitle processing.

type SubtitleProcessor

type SubtitleProcessor interface {
	// NormalizeEncoding converts subtitle data to UTF-8 from detected encoding.
	NormalizeEncoding(data []byte) []byte
	// ParseSRT parses SRT subtitle data into individual cues.
	ParseSRT(data []byte) ([]SubtitleCue, error)
	// WriteSRT serializes cues back to SRT format.
	WriteSRT(cues []SubtitleCue) ([]byte, error)
	// ShiftCues applies a timing offset to all cues.
	ShiftCues(cues []SubtitleCue, offset time.Duration) []SubtitleCue
	// SyncFromAudio runs audio-based sync on subtitle data against the video.
	SyncFromAudio(ctx context.Context, data []byte, videoPath, subtitlePath string) AudioSyncResult
}

SubtitleProcessor provides low-level SRT manipulation operations. Used by sync handlers to avoid importing the subsync package directly.

type SubtitleSearcher

type SubtitleSearcher interface {
	SearchTargets(ctx context.Context, req *SearchRequest, videoPath string, targets []SubtitleTarget) (SearchResult, error)
}

SubtitleSearcher orchestrates subtitle search across providers.

type SubtitleSource

type SubtitleSource string

SubtitleSource identifies where a subtitle was found.

const (
	// SourceEmbedded indicates a subtitle embedded in the video container.
	SourceEmbedded SubtitleSource = "embedded"
	// SourceExternal indicates a subtitle in a separate file on disk.
	SourceExternal SubtitleSource = "external"
)

type SubtitleTargJSON

type SubtitleTargJSON struct {
	MinScore  *int     `json:"min_score,omitempty"`
	Code      string   `json:"code"`
	Variant   string   `json:"variant,omitempty"`
	Variants  []string `json:"variants,omitempty"`
	Providers []string `json:"providers,omitempty"`
	Exclude   []string `json:"exclude,omitempty"`
}

SubtitleTargJSON is a JSON-friendly subtitle target.

type SubtitleTarget

type SubtitleTarget struct {
	MinScore  *int
	Code      string
	Variant   Variant
	Variants  []string
	Providers []ProviderID
	Exclude   []ProviderID
}

SubtitleTarget defines a single subtitle to search for.

func (*SubtitleTarget) EffectiveVariant

func (t *SubtitleTarget) EffectiveVariant() Variant

EffectiveVariant returns the variant, defaulting to DefaultVariant.

type SyncConfig

type SyncConfig struct {
	// SyncSubtitles enables automatic timing sync against embedded
	// reference subtitles when a new subtitle is downloaded.
	SyncSubtitles bool `json:"sync_subtitles"`
	// AudioSyncFallback enables audio-based sync as a fallback when
	// no embedded reference subtitle is available or sync fails.
	AudioSyncFallback bool `json:"audio_sync_fallback"`
	// SyncMinConfidence is the minimum confidence threshold (0.0–1.0) for
	// accepting an automatic sync result. Defaults to DefaultSyncMinConfidence when zero.
	SyncMinConfidence float64 `json:"sync_min_confidence,omitempty"`
}

SyncConfig controls subtitle synchronization on import.

type SyncOffsetStore

type SyncOffsetStore interface {
	SetSyncOffset(ctx context.Context, path string, offsetMs int64) error
	GetSyncOffset(ctx context.Context, path string) (int64, error)
}

SyncOffsetStore groups subtitle timing adjustment persistence.

type Transient

type Transient interface {
	IsTransient() bool
}

Transient is implemented by errors that can classify themselves as retryable (transient server/network failures) vs permanent. Used by retry logic to decide whether to retry without importing concrete error packages.

type UIConfigProvider

type UIConfigProvider interface {
	LanguageRulesForUI() LanguageRulesJSON
}

UIConfigProvider provides UI-specific configuration.

type Variant

type Variant string

Variant is a typed string enum for subtitle variant identifiers. Valid variants: standard (default), hi (hearing impaired), forced.

func VariantFromFlags

func VariantFromFlags(hi, forced bool) Variant

VariantFromFlags derives the variant from HI/forced flags on a picked subtitle. HI wins over forced when both happen to be set.

type VideoInfo

type VideoInfo struct {
	ReleaseGroup string    // Full release name for parsing.
	MediaType    MediaType // "episode" or "movie"
}

VideoInfo describes the video file for scoring purposes. Only MediaType and ReleaseGroup are used by the scorer and match builder.

type WebAuthnUnknownCredentialResponse

type WebAuthnUnknownCredentialResponse struct {
	Error  string `json:"error"`
	Signal string `json:"signal"`
}

WebAuthnUnknownCredentialResponse signals an unknown credential to the client.

Jump to

Keyboard shortcuts

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