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 no subflux implementation packages — only stdlib and shared external libraries (cplieger/arrapi, cplieger/auth, cplieger/webhttp). 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
- Variables
- func AudioLanguages(mi *arrapi.MediaInfo) []string
- func BadGatewayC(w http.ResponseWriter, r *http.Request, code ErrorCode, msg string)
- func BadRequestC(w http.ResponseWriter, r *http.Request, code ErrorCode, msg string)
- func BuildEpisodeID(tvdbID int, imdbID string, season, episode int) string
- func BuildMediaID(req *SearchRequest) string
- func BuildMovieID(tmdbID int, imdbID string) string
- func BuildSeasonIDPrefix(tvdbID int, imdbID string, season int) string
- func BuildSeriesPrefix(tvdbID int, imdbID string) string
- func ConflictC(w http.ResponseWriter, r *http.Request, code ErrorCode, msg string)
- func CountNonTextBytes(data []byte) int
- func ForbiddenC(w http.ResponseWriter, r *http.Request, code ErrorCode, msg string)
- func HasExcludeTag(tags []int, excludeIDs map[int]struct{}) bool
- func InternalErrorC(w http.ResponseWriter, r *http.Request, err error, code ErrorCode, ...)
- func IsValidMediaPrefix(prefix string) bool
- func JSONError(w http.ResponseWriter, err error, code int)
- func JSONErrorWithCode(w http.ResponseWriter, r *http.Request, status int, code ErrorCode, msg string)
- func LangNameToISO(name string) string
- func ManualOrdinal(path string) int
- func ManualSubtitlePath(videoPath, lang string, n int, hi, forced bool) string
- func MethodNotAllowedC(w http.ResponseWriter, r *http.Request, code ErrorCode)
- func NewSessionHashContext(ctx context.Context, sessHash string) context.Context
- func NewUserContext(ctx context.Context, u *auth.User) context.Context
- func NotFoundC(w http.ResponseWriter, r *http.Request, code ErrorCode, msg string)
- func Ok(w http.ResponseWriter)
- func OriginalLangCode(lang *arrapi.Language) string
- func ParseAudioLangs(raw string) []string
- func PayloadTooLargeC(w http.ResponseWriter, r *http.Request, code ErrorCode, msg string)
- func SeasonEpisodeFileCount(s *arrapi.Series, seasonNum int) int
- func ServiceUnavailableC(w http.ResponseWriter, r *http.Request, code ErrorCode, msg string)
- func SessionHashFromContext(ctx context.Context) string
- func SubtitlePath(videoPath, lang string, hi, forced bool) string
- func TooManyRequestsC(w http.ResponseWriter, r *http.Request, code ErrorCode, msg string)
- func UnauthorizedC(w http.ResponseWriter, r *http.Request, code ErrorCode, msg string)
- func UserFromContext(ctx context.Context) *auth.User
- func ValidateSubtitleData(data []byte) error
- func WithRequestID(ctx context.Context, id string) context.Context
- func WriteJSON(w http.ResponseWriter, v any)
- func WriteJSONStatus(w http.ResponseWriter, code int, v any)
- type AdaptiveConfig
- type AdminUserCreatedResponse
- type ArrConfig
- type ArrConfigProvider
- type AudioRuleJSON
- type AudioSyncResult
- type AuthConfigProvider
- type AuthError
- type BackoffEntry
- type BackoffParams
- type BackoffStore
- type CacheClearer
- type CleanupResult
- type ConfigDrift
- type ConfigLoader
- type ConfigProvider
- type CoverageStore
- type DownloadMeta
- type DownloadRecord
- type DownloadStore
- type DownloadedRef
- type EmbeddedPolicy
- type EmbeddedTrack
- type ErrorCode
- type HistoryStore
- type KeyGenerated
- type LangOutcome
- type LangOutcomeKind
- type LanguageResolver
- type LanguageRulesJSON
- type LogFormat
- type LogLevel
- type LoginSuccess
- type MaintStore
- type ManualLockEntry
- type ManualLockStore
- type MatchMethod
- type MatchSet
- type MeResponse
- type MediaType
- type PasskeyRegistered
- type PathValidator
- type PollKey
- type PollStore
- type PostProcessConfig
- type Provider
- type ProviderCfg
- type ProviderConfigProvider
- type ProviderID
- type ProviderRegistry
- type ProviderSchema
- type ProviderSchemaField
- type ProviderStatus
- type ProviderTimeoutManager
- type ProvidersResponse
- type QueryStore
- type RadarrClient
- type RateLimitError
- type ReconcileResult
- type ScanOutcome
- type ScanRecord
- type ScanStateRow
- type ScanStats
- type SchemaField
- type SchemaFunc
- type SchemaOption
- type SchemaSection
- type ScorePreview
- type ScoreResult
- type ScoreSimulator
- type ScoreTier
- type ScoredResult
- type Scorer
- type Scores
- type ScoringConfig
- type SearchConfig
- type SearchConfigProvider
- type SearchEngine
- type SearchRequest
- type SearchResult
- type SearchTarget
- type SearchTargets
- type ServerConfig
- type SetupStatus
- type ShowSubtitleCounter
- type SignalData
- type SonarrClient
- type StateEntry
- type StateQuery
- type Stats
- type StatusResponse
- type Store
- type Subtitle
- type SubtitleCue
- type SubtitleEntry
- type SubtitleFile
- type SubtitleInfo
- type SubtitlePostProcessor
- type SubtitleProcessor
- type SubtitleSearcher
- type SubtitleSource
- type SubtitleTargJSON
- type SubtitleTarget
- type SyncConfig
- type SyncOffsetStore
- type UIConfigProvider
- type Variant
- type VideoInfo
- type WebAuthnUnknownCredentialResponse
Constants ¶
const ( // KeyStatus is the canonical JSON key for operation result status // responses (StatusResponse is the typed carrier). // 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.
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.
const DefaultDownloadMaxAttempts = 3
DefaultDownloadMaxAttempts is the fallback number of download attempts per search when the config value is zero.
const DefaultManualProviderTimeout = 30 * time.Second
DefaultManualProviderTimeout is the per-provider search timeout for manual searches (the /api/search path, which also serves the remote CLI's search subcommand).
const DefaultProviderConcurrency = 4
DefaultProviderConcurrency is the default maximum number of parallel provider searches when the config value is zero.
const DefaultProviderPriority = 99
DefaultProviderPriority is used when no priority is configured for a provider.
const DefaultSyncMinConfidence = 0.6
DefaultSyncMinConfidence is the default minimum confidence for auto-sync to be applied. Used by config and syncing packages.
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.
const SubtitleExtSRT = ".srt"
SubtitleExtSRT is the extension every subflux writer emits. The capability-scoped extension authority lives in internal/subtitleext (api stays stdlib-only); its coverage test pins this constant to the writerOutput view so the two cannot drift.
Variables ¶
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.
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.
Functions ¶
func AudioLanguages ¶ added in v0.1.106
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 ¶
BadGatewayC writes a 502 with the given code and message.
func BadRequestC ¶
BadRequestC writes a 400 with the given code and message.
func BuildEpisodeID ¶
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 ¶
BuildMovieID returns the canonical media ID for a movie. Prefer TMDB (Radarr's canonical source), fall back to IMDB.
func BuildSeasonIDPrefix ¶ added in v0.1.148
BuildSeasonIDPrefix returns the media ID prefix shared by every episode of one season, mirroring BuildEpisodeID's format through the episode separator (e.g. "tvdb-123-s05e"). BuildEpisodeID(tvdb, imdb, season, N) has this prefix for every N in 0-99, so backoff and state rows for a season are one prefix scan. Returns "" when neither ID is available (matching BuildEpisodeID's unidentified-media fallback shape only per season would be meaninglessly broad).
func BuildSeriesPrefix ¶
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 CountNonTextBytes ¶
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 ¶
ForbiddenC writes a 403 with the given code and message.
func HasExcludeTag ¶
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 ErrorCode, 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 ¶
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 ErrorCode, 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 ¶
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 ManualOrdinal ¶ added in v0.1.148
ManualOrdinal parses the manual sibling number out of a subtitle path produced by ManualSubtitlePath: the all-digit dot segment immediately before the extension (movie.fr.2.srt -> 2, movie.fr.forced.1.srt -> 1). An unnumbered path (the auto file, movie.fr.srt) returns 0. This is the inverse of ManualSubtitlePath's numbering and the ordinal component of the wire FileRef: together with (media_type, media_id, language, variant, source) it uniquely addresses one stored subtitle file, so manual numbered siblings sharing a quad stay distinguishable without a client-supplied path.
func ManualSubtitlePath ¶
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 ErrorCode)
MethodNotAllowedC writes a 405 with the given code.
func NewSessionHashContext ¶
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 ¶
NewUserContext returns a new context with the given user stored in it.
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
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 ¶
ParseAudioLangs splits a comma/slash-separated audio languages string into deduplicated ISO 639-1 codes.
func PayloadTooLargeC ¶
PayloadTooLargeC writes a 413 with the given code and message.
func SeasonEpisodeFileCount ¶ added in v0.1.106
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 ¶
ServiceUnavailableC writes a 503 with the given code and message.
func SessionHashFromContext ¶
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 ¶
SubtitlePath computes the subtitle file path for a video.
func TooManyRequestsC ¶
TooManyRequestsC writes a 429 with the given code and message. The caller is responsible for setting the Retry-After header when appropriate.
func UnauthorizedC ¶
UnauthorizedC writes a 401 with the given code and message.
func UserFromContext ¶
UserFromContext extracts the authenticated user from the request context. Returns nil if no user is present.
func ValidateSubtitleData ¶
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 ¶
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 ArrConfigProvider ¶
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.
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 ¶
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 ¶
type ConfigProvider interface {
ScoringConfig
LanguageResolver
ArrConfigProvider
ProviderConfigProvider
ServerConfig
PathValidator
SearchConfigProvider
AuthConfigProvider
UIConfigProvider
}
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)
// Scan-cycle mark (duration-aware resume): set when a full scan begins,
// cleared on normal completion. A dangling mark means the previous cycle
// was interrupted; ScanCycleStart returns the zero time when absent.
ScanCycleStart(ctx context.Context) (time.Time, error)
SetScanCycleStart(ctx context.Context, t time.Time) error
ClearScanCycleStart(ctx context.Context) 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 EmbeddedPolicy ¶ added in v0.1.148
type EmbeddedPolicy struct {
// IgnorePGS excludes PGS bitmap subs (Blu-ray) from target satisfaction.
IgnorePGS bool
// IgnoreVobSub excludes VobSub bitmap subs (DVD) from target satisfaction.
IgnoreVobSub bool
// IgnoreASS excludes ASS/SSA styled subs (anime) from target satisfaction.
IgnoreASS bool
}
EmbeddedPolicy is the typed search policy for embedded subtitle codecs, resolved from the top-level embedded_subtitles config section. The detector always returns every normalized track; this policy decides which codecs count as "present but not usable" for target satisfaction (they stay visible in coverage but trigger external search).
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 ErrorCode ¶ added in v0.1.148
type ErrorCode string
ErrorCode is the machine-readable code carried in the JSON error envelope. A defined type (not an alias) so the wire generator discovers the catalog as a TS string-union; one-off codes still pass as string literals (untyped constants convert).
const ( CodeBadRequest ErrorCode = "bad_request" CodeForbidden ErrorCode = "forbidden" CodeNotFound ErrorCode = "not_found" CodeMethodNotAllowed ErrorCode = "method_not_allowed" CodeConflict ErrorCode = "conflict" CodePayloadTooLarge ErrorCode = "payload_too_large" CodeRateLimited ErrorCode = "rate_limited" CodeBadGateway ErrorCode = "bad_gateway" CodeInternalError ErrorCode = "internal_error" )
Generic codes (status mapped 1:1).
const ( CodeAuthInvalidCredentials ErrorCode = "auth_invalid_credentials" CodeAuthAccountDisabled ErrorCode = "auth_account_disabled" CodeAuthAccountNotSetup ErrorCode = "auth_account_not_setup" CodeAuthPasswordTooShort ErrorCode = "auth_password_too_short" CodeAuthPasswordBreached ErrorCode = "auth_password_breached" CodeAuthSessionInvalid ErrorCode = "auth_session_invalid" CodeAuthSessionRequired ErrorCode = "auth_session_required" CodeAuthRoleRequired ErrorCode = "auth_role_required" CodeAuthAPIKeyInvalid ErrorCode = "auth_apikey_invalid" CodeAuthAPIKeyDisabled ErrorCode = "auth_apikey_disabled" CodeAuthCSRF ErrorCode = "auth_csrf" )
Auth codes.
const ( CodeWebAuthnSessionInvalid ErrorCode = "webauthn_session_invalid" CodeWebAuthnRegisterFailed ErrorCode = "webauthn_register_failed" CodeWebAuthnAssertionFailed ErrorCode = "webauthn_assertion_failed" CodeWebAuthnUnsupportedOrigin ErrorCode = "webauthn_unsupported_origin" )
WebAuthn codes.
const ( CodeOIDCStateInvalid ErrorCode = "oidc_state_invalid" CodeOIDCNonceInvalid ErrorCode = "oidc_nonce_invalid" CodeOIDCExchangeFailed ErrorCode = "oidc_exchange_failed" CodeOIDCUserInfoFailed ErrorCode = "oidc_userinfo_failed" CodeOIDCAccountNotProvisioned ErrorCode = "oidc_account_not_provisioned" )
OIDC codes.
const ( CodeSetupAlreadyComplete ErrorCode = "setup_already_complete" CodeSetupPasswordInvalid ErrorCode = "setup_password_invalid" )
Setup codes.
const ( CodeConfigInvalid ErrorCode = "config_invalid" CodeConfigUnreachableArr ErrorCode = "config_unreachable_arr" CodeConfigYAMLParse ErrorCode = "config_yaml_parse" CodeConfigTooLarge ErrorCode = "config_too_large" CodeConfigReloadFailed ErrorCode = "config_reload_failed" )
Config codes.
const ( CodeScanInProgress ErrorCode = "scan_in_progress" CodeScanNoTargets ErrorCode = "scan_no_targets" CodeSearchInProgress ErrorCode = "search_in_progress" CodeSearchProviderDisabled ErrorCode = "search_provider_disabled" CodeSearchNoResults ErrorCode = "search_no_results" CodeDownloadFailed ErrorCode = "download_failed" CodeUnlockNotHeld ErrorCode = "unlock_not_held" )
Scan / search / manual ops codes.
const ( CodePathNotAllowed ErrorCode = "path_not_allowed" CodeMediaNotFound ErrorCode = "media_not_found" CodeSubtitleNotFound ErrorCode = "subtitle_not_found" CodeSyncUnsupportedFormat ErrorCode = "sync_unsupported_format" CodeSyncNoReference ErrorCode = "sync_no_reference" CodeSyncLowConfidence ErrorCode = "sync_low_confidence" // CodeSubtitleExtensionNotAllowed is the 409 a delete answers when the // target's extension lacks the delete capability in the subtitle // extension authority (a server-derived stored-state disagreement, not // caller authorization — hence 409, not 403). CodeSubtitleExtensionNotAllowed ErrorCode = "subtitle_extension_not_allowed" )
File / preview / sync codes.
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 LangOutcome ¶ added in v0.1.148
type LangOutcome struct {
Lang string // ISO 639-1 language code
Kind LangOutcomeKind // what happened for this language group
Paths []string // subtitle files downloaded for this language
Searched int // variant targets processed against provider results
Skipped int // variant targets skipped within the group
// Queried counts the provider queries the group's single sweep actually
// issued (successful or erroring; providers skipped by the health
// timeout never sent a request and don't count). Zero for skipped and
// backed-off groups. Distinct from Searched, which counts VARIANT
// targets processed against results and stays positive even when the
// sweep issued no query (every eligible provider timed out).
Queried int
}
LangOutcome is the typed per-language result of a SearchTargets call. It replaces the former parallel FoundLangs/SearchedLangs slices and aggregate counters: each language carries its own classification and evidence, so consumers (season tracker, scan stats, coverage events) read one structure instead of reconstructing sets from slice pairs.
func (*LangOutcome) Found ¶ added in v0.1.148
func (o *LangOutcome) Found() bool
Found reports whether at least one subtitle was downloaded for the language.
type LangOutcomeKind ¶ added in v0.1.148
type LangOutcomeKind string
LangOutcomeKind classifies what happened for one language group in a SearchTargets call. Exactly one kind applies per language.
const ( // LangSearched: providers were actually queried for this language. LangSearched LangOutcomeKind = "searched" // LangSkipped: nothing needed a search (covered on disk, manually // locked, or not eligible for upgrade). LangSkipped LangOutcomeKind = "skipped" // LangBackedOff: the language needed a search but every provider was in // adaptive backoff, so no query ran. Distinct from both other kinds; it // must never feed no-result evidence into the season tracker. LangBackedOff LangOutcomeKind = "backed_off" )
Language-group outcome kinds.
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 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" )
Match method constants. Canonical source for all packages.
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.
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.
Media type constants used by SearchRequest.MediaType and related helpers. Canonical source for all packages; import from here instead of declaring local unexported copies.
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).
Canonical poll-key values. New arr sources should add a constant here.
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 ( 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.
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"`
}
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 ¶
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
// Searched records whether provider work actually ran to completion for
// this stamp. False for inventory-only visits (scan skip paths record
// coverage without searching); the resume set and staleness displays can
// distinguish "we looked at the disk" from "we searched providers".
Searched bool
}
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"`
// Searched is false for inventory-only stamps (scan skip paths that
// recorded coverage without provider work).
Searched bool `json:"searched"`
}
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 ¶
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 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 set. Returns the
// full score (including the hash bonus) and the release-attribute-only
// score. A verifiable hash match short-circuits to the hash weight alone.
Score(sub SubtitleInfo, matches MatchSet) (score, scoreNoHash int)
// ScoreToTier maps a numeric score to a human-readable tier label via
// one global threshold table: excellent >= 80, good >= 50,
// acceptable >= 20, minimal >= 1, else none. Thresholds do not vary by
// media type.
ScoreToTier(score int) ScoreTier
}
Scorer turns a subtitle's release-attribute match set into a quality score.
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
// EmbeddedPolicy returns the typed embedded subtitle codec policy
// from the top-level embedded_subtitles config section.
EmbeddedPolicy() EmbeddedPolicy
}
SearchConfigProvider provides search behavior configuration.
type SearchEngine ¶
type SearchEngine interface {
SubtitleSearcher
ScoreSimulator
ProviderTimeoutManager
SubtitlePostProcessor
}
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 track detection 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 {
Langs []LangOutcome // one entry per language group, in target order
CoverageChanged bool // true if RecordSubtitleFiles detected changes on disk
}
SearchResult holds the outcome of a SearchTargets call: one typed entry per language group plus the coverage-inventory flag.
func (*SearchResult) Paths ¶
func (r *SearchResult) Paths() []string
Paths returns every subtitle file downloaded across all language groups.
func (*SearchResult) ProviderQueried ¶ added in v0.1.148
func (r *SearchResult) ProviderQueried() bool
ProviderQueried reports whether at least one provider query was actually issued during the call, across all language groups. This is the inter-item pacing signal: the scan delay exists to space real provider traffic, so an item that touched no provider (fully covered, locked, in adaptive backoff, or with every eligible provider health-timed-out) must not pay it. Deliberately not keyed on TargetsSearched(): that counts variant targets and stays positive even when the sweep sent zero requests.
func (*SearchResult) TargetsBackedOff ¶ added in v0.1.148
func (r *SearchResult) TargetsBackedOff() int
TargetsBackedOff returns the number of language groups that needed a search but had every provider in adaptive backoff.
func (*SearchResult) TargetsSearched ¶ added in v0.1.148
func (r *SearchResult) TargetsSearched() int
TargetsSearched returns the number of variant targets that ran against provider results across all language groups.
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 StatusResponse ¶ added in v0.1.148
type StatusResponse struct {
Status string `json:"status"`
}
StatusResponse is the canonical {"status": "..."} operation-result body used by action endpoints (scan triggers, resets, logout).
type Store ¶
type Store interface {
BackoffStore
DownloadStore
ManualLockStore
QueryStore
HistoryStore
CoverageStore
SyncOffsetStore
MaintStore
PollStore
Close(ctx context.Context) error
}
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 ¶
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:"-"`
VideoPath string `json:"-"`
Score int `json:"score,omitempty"`
Ordinal int `json:"ordinal,omitempty"`
OffsetMs int64 `json:"offset_ms,omitempty"`
}
SubtitleEntry is a subtitle-file row from coverage queries. On the wire it carries NO filesystem paths (S7: clients address files by typed reference, never by path); Path and VideoPath stay populated for in-process consumers (file listing size stat, deletion, reconciliation) but are json-omitted. Ordinal is the manual-sibling number parsed from the row's filename (api.ManualOrdinal) — together with (media_type, media_id, language, variant, source) it forms the wire FileRef the client echoes back to address this exact file.
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)
// InventoryCoverage records the on-disk/embedded subtitle inventory for a
// media item WITHOUT any provider work, stamping its scan state as
// inventoried-not-searched. Used by scan skip paths (season early stop,
// show-level skip) so coverage stays truthful for items the scanner
// deliberately does not search.
InventoryCoverage(ctx context.Context, req *SearchRequest, videoPath string) (coverageChanged bool)
}
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 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 ¶
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 match builder.
type WebAuthnUnknownCredentialResponse ¶
type WebAuthnUnknownCredentialResponse struct {
Error string `json:"error"`
Signal string `json:"signal"`
}
WebAuthnUnknownCredentialResponse signals an unknown credential to the client.
Source Files
¶
- arr_types.go
- auth_response_types.go
- config_iface.go
- context.go
- drift.go
- error_codes.go
- http.go
- http_errors.go
- interfaces.go
- interfaces_provider.go
- lang_resolve.go
- log_once.go
- media_id.go
- media_paths.go
- provider_schema.go
- request_id.go
- store_iface.go
- subtitle_validate.go
- types.go
- types_coverage.go
- types_response.go
- types_scoring.go
- types_search.go
- types_store.go