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:
@@ -18,6 +18,7 @@ crates/
|
|||||||
├── arr-indexer/ Torznab via Prowlarr
|
├── arr-indexer/ Torznab via Prowlarr
|
||||||
├── arr-dl/ Transmission RPC
|
├── arr-dl/ Transmission RPC
|
||||||
├── arr-probe/ ffprobe wrapper
|
├── arr-probe/ ffprobe wrapper
|
||||||
|
├── arr-subs/ subtitle providers, translation, sync
|
||||||
├── arr-db/ sqlx + migrations
|
├── arr-db/ sqlx + migrations
|
||||||
├── arr-api/ axum + OpenAPI
|
├── arr-api/ axum + OpenAPI
|
||||||
├── arr-compat/ Radarr/Sonarr v3 shim for Jellyseerr
|
├── arr-compat/ Radarr/Sonarr v3 shim for Jellyseerr
|
||||||
|
|||||||
@@ -666,6 +666,7 @@ arr-meta TMDB client
|
|||||||
arr-indexer Torznab via Prowlarr
|
arr-indexer Torznab via Prowlarr
|
||||||
arr-dl Transmission RPC
|
arr-dl Transmission RPC
|
||||||
arr-probe ffprobe wrapper
|
arr-probe ffprobe wrapper
|
||||||
|
arr-subs subtitle providers, translation, sync
|
||||||
arr-db sqlx + migrations
|
arr-db sqlx + migrations
|
||||||
arr-api axum + OpenAPI
|
arr-api axum + OpenAPI
|
||||||
arr-compat Radarr/Sonarr v3 shim for Jellyseerr
|
arr-compat Radarr/Sonarr v3 shim for Jellyseerr
|
||||||
@@ -748,7 +749,7 @@ previous one.
|
|||||||
grabbing, derived status.
|
grabbing, derived status.
|
||||||
7. **Owners and notifications** — tags, per-person ntfy topics, filtered views.
|
7. **Owners and notifications** — tags, per-person ntfy topics, filtered views.
|
||||||
8. **Jellyseerr compat** — `arr-compat`.
|
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
|
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
|
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
|
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
|
on disk. Whether a re-grab is ever wanted remains unresolved and deliberately
|
||||||
deferred.
|
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