Pull mirror of github.com/XenaRathon/DubTitlerr (GitHub is the source of truth)
Find a file
XenaRathon 88094fd516
Merge pull request #37 from XenaRathon/chore/close-runtime-toggles
chore(backlog): close runtime-toggles, add runtime-kill-switches roadmap epic
2026-10-23 17:09:21 -04:00
.agents/rules docs(agents): merge maintainer rules into AGENTS.md, add Jules coordination, resync rule files 2026-10-19 16:41:39 -04:00
.claude docs(a1): add specsmith scaffold + A1 reflow-timing spec/plan/tasks 2026-06-30 13:19:10 -04:00
.clinerules docs(agents): merge maintainer rules into AGENTS.md, add Jules coordination, resync rule files 2026-10-19 16:41:39 -04:00
.cursor/rules docs(agents): merge maintainer rules into AGENTS.md, add Jules coordination, resync rule files 2026-10-19 16:41:39 -04:00
.github docs(agents): merge maintainer rules into AGENTS.md, add Jules coordination, resync rule files 2026-10-19 16:41:39 -04:00
.kilo/rules docs(agents): merge maintainer rules into AGENTS.md, add Jules coordination, resync rule files 2026-10-19 16:41:39 -04:00
.kilocode/rules docs(agents): merge maintainer rules into AGENTS.md, add Jules coordination, resync rule files 2026-10-19 16:41:39 -04:00
.kiro/steering docs(agents): merge maintainer rules into AGENTS.md, add Jules coordination, resync rule files 2026-10-19 16:41:39 -04:00
.procoder 20261023-close-runtime-toggles: closed 2026-10-23 17:03:14 -04:00
.qoder/rules docs(agents): merge maintainer rules into AGENTS.md, add Jules coordination, resync rule files 2026-10-19 16:41:39 -04:00
.roo/rules docs(agents): merge maintainer rules into AGENTS.md, add Jules coordination, resync rule files 2026-10-19 16:41:39 -04:00
.windsurf/rules docs(agents): merge maintainer rules into AGENTS.md, add Jules coordination, resync rule files 2026-10-19 16:41:39 -04:00
data fix: hyphen join, blocklist, sidecar permissions, and a threshold revert 2026-08-21 17:26:01 -04:00
deploy chore: pre-1.0 cleanup 2026-10-23 16:42:22 +00:00
docs 20261023-pre-1-0-cleanup-redo: keep main's X-Api-Key in design doc, one-line route signatures 2026-10-23 15:17:15 -04:00
glossaries fix(repair): declare unanchored repair per show, and refuse to overwrite prior repairs 2026-08-31 12:41:14 -04:00
scripts feat(common): split PIPELINE_VERSION into transcribe and text tiers 2026-08-24 17:39:26 -04:00
shell feat(settings): LIBRARY_SWEEP off runs only queued requests and merges only requested shows 2026-10-15 13:23:29 -04:00
skills/procoder docs(agents): merge maintainer rules into AGENTS.md, add Jules coordination, resync rule files 2026-10-19 16:41:39 -04:00
specs docs(specs): parallel-work rules for the web UI and integration specs 2026-10-13 00:53:07 -04:00
tests chore: pre-1.0 cleanup 2026-10-23 19:30:01 +00:00
tools fix: clear procoder blocking findings in 4 files 2026-10-22 23:36:29 +00:00
web Merge branch 'main' into feat/settings-takes-effect 2026-10-23 11:16:17 -04:00
.dockerignore feat(review): Svelte review workspace at /review with audio preview, stale-tab ids and password login 2026-10-11 12:30:09 -04:00
.env.example fix(review): treat an empty REVIEW_TOKEN as unset; REVIEW_AUTH=off is the only disable 2026-09-21 15:56:11 -04:00
.gitignore chore(gitignore): ignore *.egg-info/ build metadata 2026-10-20 14:49:24 -04:00
.prettierignore chore(todo): close Task 1 (source_episodes), and land the prettierignore fix that was dropped from the docs commit 2026-09-01 12:38:08 -04:00
.prettierrc chore: bring the tree under the procoder gate 2026-08-24 12:27:24 -04:00
acquire_cache.py fix(acquire_cache): a transient LLM failure is not an adjudication 2026-09-02 18:01:59 -04:00
adjudication_journal.py fix(journal): lookup does not serve an entry older than the age limit 2026-10-11 16:48:28 -04:00
AGENTS.md docs(agents): merge maintainer rules into AGENTS.md, add Jules coordination, resync rule files 2026-10-19 16:41:39 -04:00
anime_order.txt chore: priority anime_order (Tautulli watch history: One Pace, JoJo, Reborn, ...) 2026-06-30 17:48:45 -04:00
card_split.py fix(review_apply): one word-window helper, and the newest correction 2026-09-02 15:54:57 -04:00
CHANGELOG.md test: add test for 401 on legacy X-Review-Token in http suite 2026-10-20 19:55:41 +00:00
common.py fix(common): clamp negative input in ts_srt to zero 2026-10-20 18:27:21 +00:00
common_words.txt feat(c1): name_suspect() to route uncertain-name lines to the LLM 2026-06-30 15:02:14 -04:00
compose.yaml chore: pre-1.0 cleanup 2026-10-23 16:42:22 +00:00
conftest.py feat(auth): account login, logout and /api/me replace the token-handing login; conftest always isolates CONFIG_DIR (login plan Task 8) 2026-10-15 01:03:43 -04:00
container_run.sh feat(settings): POST /api/system/restart writes the soft-stop flag; /api/status carries a boot id; HEALTHCHECK follows REVIEW_PORT (settings plan Task 12) 2026-10-15 05:14:48 -04:00
decision_client.py refactor: promote tools/decision_client.py to a top-level production module 2026-10-08 23:51:26 -04:00
decisions.py fix(review): a correction of only invisible characters counts as blank (Sol F3 edge) 2026-10-09 19:46:58 -04:00
Dockerfile.builder feat(settings): POST /api/system/restart writes the soft-stop flag; /api/status carries a boot id; HEALTHCHECK follows REVIEW_PORT (settings plan Task 12) 2026-10-15 05:14:48 -04:00
dub_signs_merge.py perf: pre-compute sets and remove excessive formatting 2026-10-20 19:39:24 +00:00
gen_loop.sh feat(settings): LIBRARY_SWEEP off runs only queued requests and merges only requested shows 2026-10-15 13:23:29 -04:00
generate.py refactor: extract logic from partition_todo into a helper function 2026-10-19 23:31:52 +00:00
glossary.py fix(glossary): phonetic tier ignores Metaphone codes under 3 letters 2026-10-14 22:18:14 -04:00
glossary_acquire.py 20261023-pre-1-0-cleanup-redo: port best_title tests, prune window-unit tests, update stale helper comments per #11 2026-10-23 14:48:36 -04:00
glossary_decision.py refactor(decision): move DECISION_CLIENT_VERSION above the questions it versions 2026-10-11 16:53:40 -04:00
glossary_lock.py fix: review answers survive a busy glossary; redirect guard; transport retries only before the request is out; one-token respellings are never dropped 2026-10-09 13:53:57 -04:00
glossary_verify.py 20261023-pre-1-0-cleanup-redo: port best_title tests, prune window-unit tests, update stale helper comments per #11 2026-10-23 14:48:36 -04:00
hallucination.py fix(hallucination): drop runs of one-token loop cards unless the token is a glossary name 2026-10-14 14:36:13 -04:00
LICENSE relicense MIT -> GPL-3.0 (copyleft, matching all F.A.S.C. repos) 2026-06-20 09:13:43 -04:00
merge_pass.sh feat(settings): LIBRARY_SWEEP off runs only queued requests and merges only requested shows 2026-10-15 13:23:29 -04:00
mine_glossary.py perf: pre-compute sets and remove excessive formatting 2026-10-20 19:39:24 +00:00
mux.py chore: pre-1.0 cleanup 2026-10-23 18:48:49 +00:00
ordering.py feat(ordering): a canonical SxxExx episode key 2026-09-01 13:03:53 -04:00
plex_refresh.py chore: bring the tree under the procoder gate 2026-08-24 12:27:24 -04:00
punctuation.py feat(repair): auto-dismiss large-change rejections; name the rejecting guard; opt-in acceptance of punctuation/case-only edits 2026-10-09 12:56:22 -04:00
pyproject.toml release: prepare 0.3.0 -- version bump and changelog finalize 2026-10-16 15:56:19 -04:00
qc.py fix(qc): harden duration detector against edge cases and bump schema to 5 2026-10-14 18:25:29 -04:00
README.md docs: hosted repair endpoints and the three validated shows 2026-10-17 14:03:11 -04:00
recreate_srt.py chore: bring the tree under the procoder gate 2026-08-24 12:27:24 -04:00
reflow.py fix(reflow): the comma fix welded a decimal onto an ordinary word 2026-08-29 11:32:30 -04:00
repair.py chore: pre-1.0 cleanup 2026-10-23 16:42:22 +00:00
review_apply.py fix(review_apply): one word-window helper, and the newest correction 2026-09-02 15:54:57 -04:00
review_auth.py chore: remove deprecated X-Review-Token header and warning logs 2026-10-20 00:01:16 +00:00
review_glossary.py feat(web): per-show wiki override on the Glossary page (admin-only write, Test probe, private-host refusal) 2026-10-15 17:49:45 -04:00
review_requests.py perf: pre-compute sets and remove excessive formatting 2026-10-20 19:39:24 +00:00
review_server.py 20261023-pre-1-0-cleanup-redo: keep main's X-Api-Key in design doc, one-line route signatures 2026-10-23 15:17:15 -04:00
review_settings.py fix(settings): Test pins the checked IP with SNI intact, tries every address, never follows redirects (legacy path too); NAT64/6to4 link-local refused; connection URLs and bodies redacted; orphan keys dropped (review of settings Task 10) 2026-10-15 05:14:48 -04:00
review_settings_write.py fix(settings): every *_EXTRA_BODY stays out of effective reports; connections, stages and keys validated on load; URL query refused everywhere; merge export carries no gen-only secret; RESCAN_INTERVAL reaches the server (cross-cutting review) 2026-10-15 06:55:42 -04:00
review_status.py perf: pre-compute sets and remove excessive formatting 2026-10-20 19:39:24 +00:00
review_users.py fix(auth): reset-admin revokes sessions on corrupt recovery, validates first, moves aside under the lock (review of login Task 12) 2026-10-15 00:38:33 -04:00
review_webhooks.py perf: pre-compute sets and remove excessive formatting 2026-10-20 19:39:24 +00:00
SECURITY.md feat(review): Svelte review workspace at /review with audio preview, stale-tab ids and password login 2026-10-11 12:30:09 -04:00
settings_api.py fix(settings): a GET-to-PUT round trip keeps a connection's extra_body; local-use NAT64 refused (review of settings Tasks 10-12) 2026-10-15 05:38:13 -04:00
settings_connections.py fix(settings): a GET-to-PUT round trip keeps a connection's extra_body; local-use NAT64 refused (review of settings Tasks 10-12) 2026-10-15 05:38:13 -04:00
settings_migrate.py chore: refactor migrate function to reduce complexity 2026-10-19 23:32:42 +00:00
settings_registry.py feat(settings): LIBRARY_SWEEP off runs only queued requests and merges only requested shows 2026-10-15 13:23:29 -04:00
settings_store.py fix(settings): every *_EXTRA_BODY stays out of effective reports; connections, stages and keys validated on load; URL query refused everywhere; merge export carries no gen-only secret; RESCAN_INTERVAL reaches the server (cross-cutting review) 2026-10-15 06:55:42 -04:00
unresolved.py feat(builder): run requests get full show prep; ACQUIRE_APPLY applies by default and is a runtime setting 2026-10-14 19:34:53 -04:00
uv.lock fix(deps): bump certifi to 2026.10.19 for GHSA-6q48-6c7w-9gw2 2026-10-20 17:46:34 -04:00
watch_queue.py fix(watch_queue): resolve shows by tvdb id; ambiguous titles are misses, not guesses 2026-09-25 14:00:29 -04:00
wiki_scope.py fix(glossary): wiki_scope wiring review fixes: an incomplete refetch is missing, no crash without the module, S00, multi-episode files 2026-10-09 12:34:50 -04:00

DubTitlerr

Release Image CI License: GPL-3.0 Status: beta

DubTitlerr transcribes English dub audio into subtitles and merges on-screen signs and songs into the same track.

The problem

The English subtitles shipped with most anime translate the Japanese script, not the English dub, so watching dubbed with them on gives you two scripts at once. Meanwhile the translated on-screen signs and songs live on a separate track, and most players show only one subtitle track at a time. DubTitlerr produces one track that carries the dub dialogue with the signs and songs merged in.

Review inbox: a proposed repair next to the original transcript, with the dub audio one key away

Dashboard: pipeline status per episode Runs: queue a show or season and set the schedule window Glossary: per-show name fixes and the show's wiki

How it works

flowchart LR
    A[English dub audio] --> B[Transcribe<br/>Whisper]
    B --> C[Reflow<br/>into cards]
    C --> D[Fix names<br/>per-show glossary]
    D --> E[Repair<br/>LLM]
    E --> F[Hallucination<br/>gate]
    F --> G[Merge signs<br/>and songs]
    G --> H[Mux into file<br/>+ Plex refresh]
    E -. changed lines .-> R[Review queue<br/>in the web UI]
  • Transcribe the English dub audio with Whisper (large-v3-turbo by default), then reflow words into clean cards (at most 2 lines, 42 chars, about 17 cps, never shown before spoken).
  • Correct names against a per-show glossary (curated hard_fixes plus a guarded fuzzy match, mined from your files and wiki-verified).
  • Repair low-confidence and name-suspect lines with an LLM over any OpenAI-compatible endpoint (your own llama.cpp or Ollama with qwen3-4b-instruct by default, or a hosted API), anchored on the embedded fansub dialogue when present.
  • Drop hallucinated music, silence, and blocklist lines, collapse runaway repeats, and flag the uncertain for review.
  • Merge the on-screen signs and song lyrics into the same subtitle.
  • Mux the result into the file (mp4 episodes are remuxed to mkv) as a default Dubtitles track with the MKV fonts; English audio set default.
  • Re-run safely: a version-stamped .dubtitles.done sidecar makes the pipeline idempotent and incremental.

Features

  • Web UI accounts and roles: admins manage settings and keys, users review and submit verdicts.
  • Every pipeline setting listed under Settings in nine categories, with the first wave editable in place.
  • Connections: named LLM endpoints (llama.cpp, Ollama, OpenAI-compatible) with write-only keys, tested and assigned per stage.
  • Run requests with a schedule window on the Runs page, plus library sweep control.
  • Sonarr and Radarr webhooks queue new downloads automatically.
  • Review queue with audio previews, word diffs, staged batch saves, and shared-line review (classic page still at /).
  • Per-show glossary mined from your files and wiki-verified, with a per-show wiki override.
  • Per-episode Plex refresh as each episode finishes.

Quickstart

curl -LO https://raw.githubusercontent.com/XenaRathon/DubTitlerr/main/compose.yaml
curl -L -o .env https://raw.githubusercontent.com/XenaRathon/DubTitlerr/main/.env.example
# edit .env: MEDIA_ROOT, ANIME_ROOT, and pin DUBTITLERR_TAG=v0.3.0
docker compose up -d

Then open http://<host>:8842/review/setup to create the admin account. Repair also needs a model endpoint, either one you run or a hosted API; the full setup, from model server to your first muxed show, is in Your first show.

Requirements

  • An NVIDIA GPU with CUDA for transcription; 6 GB is enough for either Whisper model at the default int8.
  • Docker with the NVIDIA container runtime (install nvidia-container-toolkit on the host, or transcription falls back to CPU).
  • An OpenAI-compatible endpoint for repair: llama.cpp serving qwen3-4b-instruct is the default, or use a hosted API with a key (Repair with a hosted model). A hosted API receives the lines it repairs.
  • Everything else (ffmpeg, Python packages) is baked into the image, so there is nothing else to install.

Documentation

Page What is there
Home Start here: install path, per-episode pipeline, requirements.
Your first show Full walkthrough: one season from model server to muxed track.
How-to guides Recipes for one problem each (queue, repair backend, accounts, webhooks).
Reference Every variable, glossary field, sidecar, stage outcome, and route.
Why it works this way The reasoning behind the guards, queues, and defaults.
Choosing an ASR model Measured evidence behind the transcription defaults.
Agent guide Setup, troubleshooting, and contributing in agent-actionable form.

Status

Beta: validated end to end on One Pace, Sword Art Online and Digimon. Version stamps may be invalidated without notice during the beta, so pin your image tag (for example ghcr.io/xenarathon/dubtitlerr:v0.3.0) instead of :latest.

Roadmap

Planned work lives with the wiki: see the roadmap.

License

GPL-3.0; see LICENSE.

A note on how this was built

This is 100% vibe-coded slop. I wrote it with Claude driving. But I didn't just say "make the thing with no mistakes." The whole project has specs, epics and stories, adversarial reviews (had other models try to break it), and real test coverage. I've been project managing it like an actual engineering effort, not a prompt-and-pray. I'm sharing it because it fills a gap I couldn't find a solution for, and I thought it might be a useful accessibility tool for people who want dub-matched subtitles without waiting for an official SDH release. This isn't meant to replace human-transcribed SDH subtitle tracks. Those are always going to be better. It's for when they don't exist.