Reference

Docs

The deep-reference page — every setting, the recommended way to run a big translation job, how the engine cascade actually behaves, and what to check when something goes wrong. For getting started, see Install and Engine Setup.

Getting a library fully translated efficiently is a multi-pass process, not a single run — trying to do everything in one pass either wastes local GPU/CPU time on what's usually a temporary cloud issue, or leaves recoverable items sitting as permanent failures.

1. Make sure Bazarr can see your existing subtitles

A subtitle muxed into the video file itself (not a separate .srt on disk) is invisible to Subtitlarr until Bazarr extracts it — Subtitlarr only ever reads/writes through Bazarr's API, never the filesystem directly. In Bazarr:

Neither toggle retroactively extracts anything by itself — Bazarr still needs to actually scan your library after enabling them, either by waiting for its own scheduled task to run (can take up to a full day depending on your Bazarr settings) or triggering it manually. Skipping this, or not giving Bazarr time to run it, means Subtitlarr silently has fewer usable source languages to translate from than your library actually has.

2–4. Translate in passes, escalate to local last

2

Run the cascade with local engines fenced off

Put a separator right after your cloud/free-tier instances (see Recommended cascade shape) and let it translate everything it can. Items that hit a genuine content block with no non-Gemini fallback in reach — or any other failure — end up failed and stay there. Leave them alone for now rather than chasing each one down individually.

3

Once the queue is drained, re-run just the failures

On the Queue page, filter to the Failed chip and run that filtered batch. A second pass over just the failures often clears a meaningful chunk of them on its own — a rate limit expires, a transient error doesn't repeat, quota resets overnight. Targeting the Failed filter specifically means only those items are touched, not a full re-run of everything already done.

A batch that keeps landing back in failed against the same cloud engine is usually hitting a genuine content block, not a transient error — repeatedly re-submitting the same flagged content risks that provider's own abuse/ToS enforcement against the account behind the key, not just another failed item. If a batch is content you expect to keep tripping the filter, don't loop it against the same key indefinitely — use a throwaway/secondary account for that key rather than one tied to anything you'd mind losing, and consider routing genuinely repeat-blocked content to a local engine instead (see step 4).
4

Only then, move your local engine above the separator

Drag it up in the Translation Engine page for whatever's still failed. This is deliberate, not automatic: with a non-Gemini engine actually in reach, content-blocked batches get bisected instead of failing outright — Gemini keeps handling everything except the specific isolated cues that trip its filter, and only that small leftover chunk goes to the local engine. Doing this from the start would mean paying local inference time on every single content-blocked item; doing it last means only the genuine leftovers ever reach it.

5. Run the Language Check job

Even a "successful" translation can silently come back in the wrong language — the LLM echoing the source text instead of translating it. Subtitlarr's own structural checks can't catch this, since the response is well-formed, just wrong. The Jobs page's Language Check audits recently-completed items against their actual detected output language and resets any mismatch back to pending for a real retry.

It needs a check engine picked on the Jobs page first, and isn't scheduled by default unless you've set LANGUAGE_CHECK_CRON — otherwise it's manual-only, so remember to run it (or schedule it) after a big batch, not just once and forget it.

How the cascade works

Subtitlarr doesn't use a single fixed engine — you build an ordered list of one or more configured instances (Ollama, llama.cpp, Gemini, NVIDIA NIM, OpenRouter, Groq). Every item is tried against the first instance; if that fails (rate limit, error, content block), it falls through to the next instance in order, and so on. A separator row can fence off a group so nothing below it is ever tried as an automatic fallback — the run simply stops with the remaining items left pending once everything above the separator is exhausted.

Content-block bisection

Gemini's own safety filter (PROHIBITED_CONTENT/SAFETY) can reject a translation batch outright — most likely on R-rated or intense material. Rather than treating the whole batch as a failure and handing all of it to a weaker fallback engine, Subtitlarr bisects the blocked batch and retries each half against the same Gemini instance, so only the actual offending cues end up isolated — the rest of the batch stays on Gemini at full quality.

Splitting stops once an isolated chunk shrinks to 10 cues, or once the item's bisection budget (extra requests spent narrowing down blocks, capped per item) runs out — whichever comes first. Either way, only that small leftover chunk falls back, and it skips straight past every other configured Gemini instance (they'd just trip the same filter again) to the first non-Gemini engine in the cascade, re-chunked to that engine's own configured batch size rather than arriving oversized.

Bisection only ever engages if a non-Gemini engine is actually reachable in the cascade (not fenced off behind a separator, not rate-limited, not disabled). If every remaining cascade entry is also Gemini, a content-blocked batch fails immediately instead — there's no point bisecting toward another Gemini instance that will almost certainly trip the same filter on the same text.

Net effect: a normal batch costs exactly one request, same as always. A batch with a handful of blocked lines costs a few extra Gemini requests to isolate them, and only those lines go to the fallback engine. A batch blocked densely throughout (e.g. an R-rated film flagged every few lines) hits the per-item budget quickly and the remainder falls back in bulk, re-chunked properly — so one heavily-flagged movie can't consume a disproportionate share of your daily Gemini quota chasing a lost cause.

Rate limits & cooldown

3 consecutive failures against an engine instance trip a 24-hour cooldown — the instance is skipped without even attempting a live round-trip for the rest of that window. This clears automatically after 24 hours, or immediately via a Test Connection on that instance, or the Jobs page's manual "clear all rate limits" action.

All settings reference

None of this needs to be touched at container start — every one of these is also editable from the web UI afterward (Settings / Language Rules / Bazarr Connection pages). This table exists purely as a reference for what each setting does and its default. Translation engine setup lives entirely in the Translation Engine page — see Engine Setup — not in this table.

VariablePurposeDefault
BAZARR_BASE_URLBazarr root URLset from the UI
BAZARR_API_KEYBazarr API keyset from the UI
SCHEDULE_CRON5-field cron expression for the main scheduled translation job10 3 * * *
AGE_THRESHOLD_DAYSDays a subtitle must be missing before a scheduled run will translate it14
DAILY_TRANSLATION_LIMITMax items translated per day by scheduled/full runs (0 = unlimited); per-item re-runs bypass this100
PAUSE_BETWEEN_ITEMS_SECONDSRest between translations so the GPU isn't pegged non-stop30
QUEUE_UPLOADS_ENABLEDHold translated subtitles locally instead of uploading immediately — see belowfalse
PUSH_UPLOADS_CRONOptional cron to auto-push queued uploads (only meaningful with QUEUE_UPLOADS_ENABLED); blank = manual only15 5 * * *
SYNC_MEDIA_CRONOptional cron to auto-refresh Bazarr's wanted list; blank = manual only0 3 * * *
SYNC_SUBS_CRONOptional cron to auto pre-fetch source subtitle content; blank = manual only5 3 * * *
LANGUAGE_CHECK_CRONOptional cron to audit recently-completed items for the wrong output language and reset any mismatch to pending; needs a check engine picked on the Jobs page first; blank = manual only0 5 * * *
BACKUP_CRONDaily snapshot of the whole database to /data/backups/; blank disables it30 2 * * *
BACKUP_KEEP_COUNTHow many daily/manual snapshots to retain before pruning the oldest20
DB_PATHSQLite file path inside the container/data/subtitlarr.db
RUN_CONCURRENCYReserved for future use1
LOG_LEVELLogging verbosityINFO

PORT is accepted but currently has no effect — the container always listens on 7777 internally (map it to any host port you like via Docker's own port mapping).

Queue uploads, explained

If your Bazarr host — or its storage, e.g. a NAS array — spins its disks down when idle, every immediate upload wakes it. With QUEUE_UPLOADS_ENABLED=false (the default), a completed translation uploads to Bazarr right away; over a long run translating many items, that's one wake-up per item.

Setting it true holds each finished translation locally as translated_pending_upload instead, and nothing touches Bazarr's storage until you push the whole batch at once — manually from the Jobs page, or automatically via PUSH_UPLOADS_CRON. That turns N wake-ups into one.

Your setupRecommended
Disks/storage never spin downEither setting works — false is simplest
Disks spin down, but are already kept awake by something elsefalse — no benefit to queuing
Disks spin down and nothing else keeps them awaketrue — push the batch once you're ready, or schedule it

Troubleshooting

Translations come back mostly untranslated, or with low cue recovery

This is usually a batch-size problem, not a translation-quality one. The batch token budget (set per engine instance) is not the same thing as that model's context window — a batch that technically fits inside the context window doesn't mean a smaller/weaker local model can reliably format that much structured output. Small models lose structural reliability on long responses well before hitting their real context limit. Try lowering the batch token budget on that engine instance before assuming anything else is wrong.

An item is stuck as "translating" after a restart

There's no checkpointing mid-item — if the app process is killed while an item is actively translating, it's automatically reset back to pending the next time the app starts, and gets fully retried later. Nothing is lost, but whatever GPU/API time was already spent on that attempt is.

A run stopped early with items still pending

Check the engine cascade — this is expected behavior when every instance above a separator becomes rate-limited or disabled mid-run, not a bug. The run stops rather than silently falling through to local engines fenced off behind a separator. Pick up the leftovers with a manual run, or wait for cooldowns/quota to reset.

A completed subtitle is in the wrong language

Run the Language Check job — this exact scenario (a well-formed response that's simply still in the source language) is what it exists to catch, since structural checks alone can't detect it.

Known limitations