Amend DESIGN.md with a subtitles section #183

Closed
opened 2026-08-24 21:28:31 +01:00 by naps62-yolo · 1 comment
Owner

DESIGN.md §13 lists subtitles as phase 9 and says they are out of scope for
the document. Every other issue in this milestone cites a section that does not
exist yet. Write it.

Add §15 Subtitles to DESIGN.md, and update §13 item 9 to point at it
instead of saying "out of scope". The section records the decisions below,
which were settled with the operator during planning. It is the contract: an
issue that disagrees with it is wrong.

What §15 must state

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.

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 in §15 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.

Done when

DESIGN.md carries §15 with all of the above, §13 item 9 points at it, and the
crate list in §11 and CLAUDE.md both mention arr-subs.

`DESIGN.md` §13 lists subtitles as phase 9 and says they are out of scope for the document. Every other issue in this milestone cites a section that does not exist yet. Write it. Add **§15 Subtitles** to `DESIGN.md`, and update §13 item 9 to point at it instead of saying "out of scope". The section records the decisions below, which were settled with the operator during planning. It is the contract: an issue that disagrees with it is wrong. ## What §15 must state **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. **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 in §15 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. ## Done when `DESIGN.md` carries §15 with all of the above, §13 item 9 points at it, and the crate list in §11 and `CLAUDE.md` both mention `arr-subs`.
naps62-yolo added this to the Subtitles milestone 2026-08-24 21:28:31 +01:00
naps62-yolo added the area/infradifficulty/hardphase/9-subtitlestype/chore labels 2026-08-24 21:28:31 +01:00
naps62-yolo added a new dependency 2026-08-24 21:29:17 +01:00
naps62-yolo added a new dependency 2026-08-24 21:29:27 +01:00
naps62-yolo added a new dependency 2026-08-24 21:29:27 +01:00
Author
Owner

Added §15 Subtitles to DESIGN.md, recording every decision from this issue: the global two-language wanted set (pt-PT preferred / pt-BR accepted, plus English), embedded-track satisfaction with text-format extraction to sidecar SRT, the OpenSubtitles.com + Podnapisi provider trait, the moviehash-first ranking with named rejection rules, forced/SDH handling, immediate translation with pluggable feature-gated backends (OpenAI-compatible, DeepL, Google Translate, remote-command template), the no-upgrade rule, <basename>.<lang>[.mt].srt sidecar naming inside the §7.4 folder, alass sync with the implausibility guard, §10-style configuration split, per-provider/translator daily token buckets spent newest-import-first, no new notification classes, and the stated non-goals. §13 item 9 now points at §15 instead of "out of scope", and arr-subs is listed in §11 and in CLAUDE.md's repo layout.

Branch: subtitles/183-design-subs

just ci: skipped deliberately — the change is Markdown only (DESIGN.md, CLAUDE.md), nothing the gate checks is affected, and the machine is shared.

Findings

None. The issue's decisions fit the existing document cleanly; no conflicts with §2, §5.2, §5.4, §7.4, §9.3, §9.5 or §10 turned up while cross-reading them.

Added **§15 Subtitles** to `DESIGN.md`, recording every decision from this issue: the global two-language wanted set (pt-PT preferred / pt-BR accepted, plus English), embedded-track satisfaction with text-format extraction to sidecar SRT, the OpenSubtitles.com + Podnapisi provider trait, the moviehash-first ranking with named rejection rules, forced/SDH handling, immediate translation with pluggable feature-gated backends (OpenAI-compatible, DeepL, Google Translate, remote-command template), the no-upgrade rule, `<basename>.<lang>[.mt].srt` sidecar naming inside the §7.4 folder, alass sync with the implausibility guard, §10-style configuration split, per-provider/translator daily token buckets spent newest-import-first, no new notification classes, and the stated non-goals. §13 item 9 now points at §15 instead of "out of scope", and `arr-subs` is listed in §11 and in `CLAUDE.md`'s repo layout. Branch: `subtitles/183-design-subs` `just ci`: skipped deliberately — the change is Markdown only (`DESIGN.md`, `CLAUDE.md`), nothing the gate checks is affected, and the machine is shared. ## Findings None. The issue's decisions fit the existing document cleanly; no conflicts with §2, §5.2, §5.4, §7.4, §9.3, §9.5 or §10 turned up while cross-reading them.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: yolo/arr#183