Pre-2026-10-19 Forgejo copy (Forgejo-only PRs + wiki). Live repo is the GitHub pull mirror DubTitlerr.
This repository has been archived on 2026-10-19. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
Find a file
XenaRathon b635dafc08
Some checks failed
CI / test (3.11) (push) Failing after 4s
CI / test (3.13) (push) Failing after 3s
CI / lint (push) Failing after 4s
CI / review-ui (push) Failing after 4s
CI / osv-scan (push) Failing after 4s
Merge pull request #10 from XenaRathon/docs/validation-and-hosted-repair
docs: hosted repair endpoints and the three validated shows
2026-10-17 14:08:30 -04:00
.agents/rules chore(agents): resync per-editor rule files with AGENTS.md 2026-09-23 19:11:41 -04:00
.claude docs(a1): add specsmith scaffold + A1 reflow-timing spec/plan/tasks 2026-06-30 13:19:10 -04:00
.clinerules chore(agents): resync per-editor rule files with AGENTS.md 2026-09-23 19:11:41 -04:00
.cursor/rules chore(agents): resync per-editor rule files with AGENTS.md 2026-09-23 19:11:41 -04:00
.github feat(review): Svelte review workspace at /review with audio preview, stale-tab ids and password login 2026-10-11 12:30:09 -04:00
.kilo/rules chore(agents): resync per-editor rule files with AGENTS.md 2026-09-23 19:11:41 -04:00
.kilocode/rules chore(agents): resync per-editor rule files with AGENTS.md 2026-09-23 19:11:41 -04:00
.kiro/steering chore(agents): resync per-editor rule files with AGENTS.md 2026-09-23 19:11:41 -04:00
.procoder fix(release-review): fail-closed mux records, shell-safe crash classification, CI gate 2026-09-24 21:57:15 -04:00
.qoder/rules chore(agents): resync per-editor rule files with AGENTS.md 2026-09-23 19:11:41 -04:00
.roo/rules chore(agents): resync per-editor rule files with AGENTS.md 2026-09-23 19:11:41 -04:00
.windsurf/rules chore(agents): resync per-editor rule files with AGENTS.md 2026-09-23 19:11:41 -04:00
data fix: hyphen join, blocklist, sidecar permissions, and a threshold revert 2026-08-21 17:26:01 -04:00
deploy fix(mux): unique per-run temp files and mid-mux source modification guard 2026-10-11 17:04:26 -04:00
docs docs: hosted repair endpoints and the three validated shows 2026-10-17 14:03:11 -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 chore(agents): resync per-editor rule files with AGENTS.md 2026-09-23 19:11:41 -04:00
specs docs(specs): parallel-work rules for the web UI and integration specs 2026-10-13 00:53:07 -04:00
tests 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
tools feat(glossary_consequences): default to production policy and pass through in score tooling 2026-10-11 17:56:51 -04:00
web test(web): fixture reset gives each test a fresh login throttle 2026-10-16 15:53:42 -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: git-ignore the adjudication journal sidecars 2026-10-11 16:50:49 -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
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 release: prepare 0.3.0 -- version bump and changelog finalize 2026-10-16 15:56:19 -04:00
common.py 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
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 docs: v0.3.0 release docs (login and roles, settings pages, LIBRARY_SWEEP, Glossary wiki field) 2026-10-15 21:13:51 -04: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 chore: bring the tree under the procoder gate 2026-08-24 12:27:24 -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(signs): scan long primary events separately so they do not widen the bisect window 2026-10-11 20:10:55 -04: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 feat(settings): the loops apply stored settings at their boundaries with a container-env fallback; restart-applied secrets reach only the review server at boot; ASR_DEVICE honoured (settings plan Task 7) 2026-10-15 04:08:17 -04:00
glossary.py fix(glossary): phonetic tier ignores Metaphone codes under 3 letters 2026-10-14 22:18:14 -04:00
glossary_acquire.py fix(acquire): apply never deletes curated hard fixes; falsy ACQUIRE_APPLY values mean dry run 2026-10-14 14:36:13 -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 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
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
IMPROVEMENTS.md merge(fix/published-episode-titles): complete v0.2.0 hardening pass 2026-09-24 12:11:55 -04:00
ISSUE-phonetic-name-guard.md feat(words): record decoder provenance 2026-09-22 13:30:45 -04:00
ISSUE-s32e02-timing.md docs: commit agent rules, beta feedback, promo copy, and a lesson 2026-09-21 15:58:55 -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 fix(mine): drop junk names that appear mostly in lowercase 2026-10-14 14:36:13 -04:00
mux.py fix(mux): compare size and mtime within 1s tolerance and drop inode/device checks 2026-10-11 17:56:51 -04: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 feat(settings): stage connections reach the loops; REPAIR_PROFILES import as connections with REPAIR_MODEL_SECONDARY; migration not blocked by a corrupt secrets.json (settings plan Task 11, Task 8 follow-ups) 2026-10-15 05:47:35 -04:00
REVIEW.md feat(words): record decoder provenance 2026-09-22 13:30:45 -04: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 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_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 fix(requests): derive_generate_only_re no longer requires show name in basename 2026-10-15 14:21:20 -04:00
review_server.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_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 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
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 feat(webhooks): Sonarr/Radarr webhook receiver that enqueues run requests 2026-10-13 00:52:54 -04: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 fix(settings): migrated connections keep the container's repair timeouts; profile import waits for a readable secrets.json (review of settings Task 11) 2026-10-15 06:09:22 -04: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 release: prepare 0.3.0 -- version bump and changelog finalize 2026-10-16 15:56:19 -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.