docs(arr): add DESIGN.md §15 subtitles contract
Records the subtitle decisions settled during Subtitles milestone planning: global wanted set (pt + en), embedded-track extraction, providers, ranking, translation backends, sidecar naming, alass sync, budgets, and stated non-goals. Points §13 item 9 at §15 and adds arr-subs to the crate lists. Closes-Issue: #183 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -666,6 +666,7 @@ arr-meta TMDB client
|
||||
arr-indexer Torznab via Prowlarr
|
||||
arr-dl Transmission RPC
|
||||
arr-probe ffprobe wrapper
|
||||
arr-subs subtitle providers, translation, sync
|
||||
arr-db sqlx + migrations
|
||||
arr-api axum + OpenAPI
|
||||
arr-compat Radarr/Sonarr v3 shim for Jellyseerr
|
||||
@@ -748,7 +749,7 @@ previous one.
|
||||
grabbing, derived status.
|
||||
7. **Owners and notifications** — tags, per-person ntfy topics, filtered views.
|
||||
8. **Jellyseerr compat** — `arr-compat`.
|
||||
9. **Subtitles** — replaces Bazarr. Out of scope for this document.
|
||||
9. **Subtitles** — replaces Bazarr. See §15.
|
||||
|
||||
Movies before TV because TV adds season packs, air-date calendars and
|
||||
per-episode state on top of an otherwise identical pipeline. Doing it second
|
||||
@@ -768,3 +769,88 @@ means that pipeline is already proven.
|
||||
RSS lane obeys the same guard — it skips packs for any season with episodes
|
||||
on disk. Whether a re-grab is ever wanted remains unresolved and deliberately
|
||||
deferred.
|
||||
|
||||
## 15. Subtitles
|
||||
|
||||
Replaces Bazarr. Phase 9 in §13; the `arr-subs` crate in §11.
|
||||
|
||||
**Wanted set.** Global, not per root. Two languages are separately wanted for
|
||||
every media file: Portuguese — pt-PT preferred, pt-BR accepted — and English.
|
||||
A file is satisfied for a language when a subtitle in it exists, embedded or as
|
||||
a sidecar. This is deliberately unlike §5.2's audio rules, which attach to a
|
||||
root: subtitles carry no blacklist there and none here. pt-BR subtitles are
|
||||
always fine.
|
||||
|
||||
**Embedded tracks.** An embedded subtitle track satisfies its language.
|
||||
Text-format tracks (`subrip`, `ass`, `mov_text`) are additionally extracted to
|
||||
a sidecar SRT, because an extracted track is a legal translation source.
|
||||
Image-format tracks (PGS on BluRay, VobSub on DVD) carry bitmaps, not text:
|
||||
they satisfy viewing but can never feed a translator, and arr does not OCR
|
||||
them. `arr-probe` already reports subtitle tracks with resolved languages; the
|
||||
format is the new fact it must carry.
|
||||
|
||||
**Providers.** OpenSubtitles.com and Podnapisi, behind one trait.
|
||||
|
||||
**Ranking.** A `moviehash` match wins outright. Then an exact release-name
|
||||
match, then same release group or same source, then uploader rating and
|
||||
download count as tiebreakers. Every rejected candidate names the rule that
|
||||
killed it, so the manual view described in §9.3 works unchanged for subtitles.
|
||||
|
||||
**Forced and SDH.** A forced track covers only foreign-language lines and
|
||||
on-screen signs; it never satisfies a want and arr never goes looking for one.
|
||||
An SDH track is complete and satisfies, ranked below a plain subtitle.
|
||||
|
||||
**Translation.** When no provider has a wanted language, arr translates
|
||||
immediately — there is no waiting window. The source is an existing subtitle:
|
||||
a downloaded one, or one extracted from a text-format embedded track. Being
|
||||
able to translate from an embedded track is a deliberate improvement on
|
||||
Bazarr, which cannot.
|
||||
|
||||
**Translation backends.** Pluggable, each behind its own cargo feature: an
|
||||
OpenAI-compatible HTTP endpoint, DeepL, Google Translate, and a generic remote
|
||||
command driven by a configured template (`ssh box claude -p` is one instance
|
||||
of that template, not a backend of its own). The engine in use is a database
|
||||
setting, so switching does not need a rebuild when the feature is compiled in.
|
||||
Subtitles are sent in batches of cues; a reply whose cue count or numbering
|
||||
does not match the batch is rejected. Timing data never leaves arr.
|
||||
|
||||
**No upgrade loop.** Once a language is satisfied — by a machine translation
|
||||
too — arr stops working on it. A real subtitle appearing later does not
|
||||
replace anything. Replacement is a manual action from the UI. This is §5.4's
|
||||
rule applied to subtitles.
|
||||
|
||||
**On disk.** Sidecars live next to the video inside the §7.4 title folder,
|
||||
named `<video basename>.<lang>.srt`, e.g.
|
||||
`… - [2160p][WEB-DL][HDR10].pt-PT.srt`. A machine translation carries an extra
|
||||
`.mt` segment: `… [HDR10].pt-PT.mt.srt`. That keeps §7.4's audit-by-`ls`
|
||||
property — with the app stopped, the filename says which subtitles are
|
||||
machine-made. Folder-level delete stays atomic because sidecars are inside the
|
||||
folder.
|
||||
|
||||
**Sync.** `alass` runs on every fetched and every translated subtitle. It is a
|
||||
single small binary invoked like `ffprobe`, so it costs nothing at rest. It
|
||||
reports no confidence value, so its output is accepted unless it is
|
||||
implausible — a shift beyond 60 seconds, or cues lost — in which case the
|
||||
unsynced original is kept and the file is flagged.
|
||||
|
||||
**Configuration.** Provider credentials and translator API keys are bootstrap
|
||||
config or environment, per §10. Wanted languages, chosen engine, per-provider
|
||||
enable and the daily budgets are database rows edited from `/settings` without
|
||||
a restart.
|
||||
|
||||
**Budgets.** A token bucket per provider and per translator, with a configured
|
||||
daily allowance. The reconcile loop spends it newest-import-first, so enabling
|
||||
this on an existing library drains the backlog over days instead of hitting
|
||||
every rate limit at once. Being at the cap is a visible queue state, not an
|
||||
error.
|
||||
|
||||
**Notifications.** No new event classes. §9.5 stands: subtitle fetches never
|
||||
notify, and a provider or translator being unreachable folds into the existing
|
||||
"Broken" message to the operator alone.
|
||||
|
||||
**Non-goals**, stated here so they do not creep back: OCR of image-based
|
||||
tracks, transcribing audio when no subtitle exists anywhere, adopting subtitle
|
||||
files already on disk that arr did not write (§2 already says the service
|
||||
knows only what it put there — which does mean arr may fetch a second copy
|
||||
alongside one Bazarr left), and any background loop that upgrades a subtitle
|
||||
in place.
|
||||
|
||||
Reference in New Issue
Block a user