-
Notifications
You must be signed in to change notification settings - Fork 0
How To Guides
Recipes. Each solves a single problem and assumes a working install — if you do not have one, start with Your first show.
Problem: your copies have an English dub but no English subtitle track for the Japanese audio. The repair stage has nothing to anchor against, refuses every line, and mangled names stay on screen.
Check whether this is you. List an episode's subtitle tracks:
ffprobe -v error -select_streams s \
-show_entries stream=index:stream_tags=language,title \
-of default=noprint_wrappers=1 "Show - S01E01.mkv"An English track carrying full dialogue means you are anchored and should not do this. A Signs and Songs track only counts as no reference.
Seasons of one show can differ. Check the season you are about to process.
Do it. Add one key to that show's glossary file:
{
"show": "Show Name",
"unanchored_repair": true,
"names": ["..."]
}The next repair pass sends glossary-only prompts for unanchored lines instead of skipping them.
Know the trade. Glossary-only repair can fabricate names — measured turning Oimo into
Zoro. Review the results rather than turning this on and walking away. Reasoning:
Why it works this way.
There is also a global REPAIR_UNANCHORED variable. Do not use it. It is recorded in no
committed file, which is precisely how a season's corrections were once silently reverted to
raw speech recognition.
Problem: the default order spends hours on seasons you watched years ago before reaching the arc you are on.
Choose the shows and their order in /config/anime_order.txt, one folder name per line.
# comments and blanks are ignored:
One Pace (1999)
Cowboy Bebop (1998)
# Trigun (1998) <- queued but paused
Jump to where you actually are with /config/season_priority.txt:
# Show folder name : start season
One Pace (1999):20
Seasons at or after the start go first, ascending; earlier ones wrap around after. The same episodes, resequenced so the next arc lands soonest.
This file has no default path. Point at it explicitly:
SEASON_PRIORITY_FILE: /config/season_priority.txtWithout that variable the log says watch-order disabled and the file is never read.
SEASON_START is a global fallback for when you have no per-show file.
When you move further along, bump the number and restart the container.
Delete its sidecars and completion stamp:
rm "Show - S01E05".dubtitles.done \
"Show - S01E05".eng.dubtitles.srt \
"Show - S01E05".eng.dubtitles.ass \
"Show - S01E05".dubtitles.conf.jsonThe next sweep re-transcribes it.
To redo only the text, not the audio pass, keep .dubtitles.words.json. Word-level
confidences live there, so the text tier replays without the GPU.
This does not remove the already-muxed track. To strip it from the video first:
mkvmerge -o out.mkv --subtitle-tracks '!<id>' in.mkvProblem: one episode is skipped every sweep.
A hard crash leaves a poison marker so the pipeline does not loop on it:
ls "Show - S01E05".dubtitles.fail
cat "Show - S01E05".dubtitles.crash.jsonThe crash file names the exception. When you have dealt with it — or want to try anyway — delete the marker:
rm "Show - S01E05".dubtitles.failProblem: you do not want unreviewed output written into videos for a show you care about.
REVIEW_GATE_SHOWS: "One Pace (1999):Cowboy Bebop (1998)"Colon-separated directory names. Those episodes are transcribed and repaired as usual but are not muxed until their review queue is settled.
A hold older than REVIEW_GATE_STALE_DAYS (default 7) is reported loudly and stays held.
The timer buys a warning, never a release — auto-releasing unreviewed output is the thing
the gate exists to prevent.
Empty by default: an install that has not opted in behaves exactly as before.
Problem: the pipeline runs, the log shows repair targets, and every line comes back unchanged. No errors.
Check the model is actually replying:
curl -s http://127.0.0.1:8090/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"messages":[{"role":"user","content":"Reply with the word ok"}],"max_tokens":8}' \
| python3 -m json.toolLook at choices[0].message. Two failures look identical from the pipeline's side:
| What you see | Cause | Fix |
|---|---|---|
content empty, reasoning_content full |
The model is thinking and never answers |
--jinja and --chat-template-kwargs '{"enable_thinking":false}'
|
content empty, nothing else |
The chat template was never applied | --jinja |
--jinja is required for chat_template_kwargs (specifically enable_thinking:false,
which repair.py always sends) to take effect at all. Without it the server loads, answers
/health with 200, and a thinking-capable model — qwen3-4b-instruct, the default,
included — spends its whole budget on reasoning and returns empty content forever.
Do not use /health as a readiness check. It reports 200 while weights are still
loading. Ask for a real completion instead, as above.
Problem: you want Whisper and the repair model resident at once on one card.
This project runs Q6_K. On-disk weights, measured 2026-09-02:
| Quantisation | Size on disk |
|---|---|
Q8_0 |
4.0 GB |
Q6_K |
3.1 GB |
Q5_K_M |
2.7 GB |
Q4_K_M |
2.4 GB |
On a 6 GB card, Q8_0 leaves roughly 2 GB — tight alongside Whisper, and not necessarily enough depending on your Whisper model and compute type. Q6_K buys back most of a gigabyte, which is why it is the one this project settled on.
Options, cheapest first:
- A smaller quantisation. Q5_K_M or Q4_K_M go further still. The quality cost below Q6_K is not measured here; the table above is sizes, not quality.
- Two cards. Whisper on one, the repair model on the other. Nothing assumes they share a device — the repair stage talks to an HTTP endpoint.
-
Sequential. Run the transcription sweep and the merge sweep at different times, one
model resident at a time.
generate.pyloads Whisper lazily and its process exits between shows, so this costs wall-clock rather than quality.
Check what is actually resident before blaming the pipeline for an OOM:
nvidia-smi --query-gpu=memory.used,memory.total --format=csvIf another process holds the card, Whisper can CUDA-OOM. The pipeline exits cleanly and the loop restarts with a fresh context, so it self-heals once VRAM frees — but do not run two transcriptions on one small card at once.
Problem: the review server's write routes rewrite subtitles and force re-muxes, from a root-owned process.
The default is already safe. With REVIEW_TOKEN unset a token is generated, persisted
0600, and printed once:
docker exec <container> cat /config/review_tokenWrite routes require it in an X-Review-Token header. Read routes never require it — a
GET can enumerate your library.
To set your own:
REVIEW_TOKEN: "a-long-random-string"To disable authentication entirely, set it to the empty string. Only an explicit empty value does this, and it is a decision about your own network:
REVIEW_TOKEN: ""Do not expose port 8842 to the internet either way.
Problem: curated glossaries and reviewed seasons are trapped on one machine.
GLOSSARY_DIR and DECISIONS_DIR are plain directories of JSON, one file per show, designed
to be committed to git. A git pull on the host is what makes them current — the container
reads whatever is in the mount.
cd ~/dubtitlerr/glossaries && git init && git add . && git commit -m "glossaries"The review token is deliberately stored beside DECISIONS_DIR, never inside it, so a
credential never rides along with a directory you publish.
Community glossaries live in their own repository with a pull-request path for contributions.
Include:
- The log lines around the failure. Unvalidated-configuration warnings are formatted to be pasted directly.
-
.dubtitles.repair-summary.json— it names the model, the rules and the counts. -
.dubtitles.crash.json, if there is one. - Whether the show is anchored or unanchored, plus the
ffprobetrack listing from the first recipe on this page.
Do not include the video.