2 Reference
xenarathon edited this page 2026-09-04 14:48:46 -04:00

Reference

Information-oriented. Values, shapes and filenames, looked up rather than read.

Every default below is the value in the code, not the value in a compose example. Where a default differs between modules, that is stated rather than smoothed over.


Container

Image: ghcr.io/xenarathon/dubtitlerr

Tags: the git tag of a release (v0.1.0-beta), plus latest pointing at the most recent release. latest never tracks main.

Entrypoint: container_run.sh, which starts three loops in one container:

Loop Script Cadence Work
Generate gen_loop.sh continuous, then RESCAN_INTERVAL idle GPU: mine, acquire, verify, transcribe
Merge merge_pass.sh every MERGE_INTERVAL CPU + LLM: repair, merge signs, mux, Plex refresh
Review review_server.py restarts after REVIEW_RESTART s on exit HTTP review UI

Stage order within gen_loop.sh: watch_queue.pymine_glossary.pyglossary_acquire.pyglossary_verify.pygenerate.py

Stage order within merge_pass.sh: repair.pydub_signs_merge.pymux.pyplex_refresh.py

The container runs as root, so generate.py can chown into the media tree.


Per-episode files

All are written beside the video, sharing its basename.

Suffix Written by Read by Contents
.eng.dubtitles.srt generate.py, repair.py, review_apply.py dub_signs_merge.py, mux.py The dialogue transcript from the dub audio
.eng.dubtitles.ass dub_signs_merge.py, review_apply.py mux.py Dialogue plus signs, songs and credits
.dubtitles.conf.json generate.py, repair.py repair.py, glossary_acquire.py, mux.py Per-cue avg_logprob and no_speech_prob
.dubtitles.words.json generate.py generate.py Per-word confidences and audio duration; lets a text-tier re-run skip the GPU
.dubtitles.done common.py, mux.py generate.py, mux.py Completion stamp: muxed size, mtime, tier versions
.dubtitles.fail generate.py generate.py Poison marker after a hard crash. Delete it to retry
.dubtitles.crash.json generate.py Exception type, message and time from that crash
.dubtitles.qc.json generate.py mux.py Counters, cps quantiles, layout violations
.dubtitles.repair.csv repair.py Audit trail: original, repaired, reference, latency
.dubtitles.repair-summary.json repair.py mux.py Targets, repaired, skipped, latency, model, rules
.dubtitles.unresolved.jsonl unresolved.py unresolved.py, review_server.py Rejections awaiting human triage — the review queue
.dubtitles.mux.log mux.py Tracks dropped, defaults set

A .stale suffix is appended to prior-version output during a version upgrade, e.g. .eng.dubtitles.srt.stale.

Per show rather than per episode: .lastrun.json and the glossary's .acquire-cache.json.


Environment variables

Paths and roots

Var Default Meaning
MEDIA_ROOT /media Media mount
OUTPUT_ROOT (empty) Write sidecars elsewhere than beside the video
ANIME_ROOT /media/Anime Library Root the generate loop walks
MERGE_ROOTS /data/Media/Anime Library Roots the merge pass walks (colon-separated)
MUX_ROOTS /data/Media/Anime Library Roots mux.py walks
ANIME_ORDER /config/anime_order.txt Show order file
GLOSSARY_DIR /config/glossaries Per-show glossaries
DECISIONS_DIR /config/decisions Per-show verdict stores
WIKI_CACHE_DIR /config/wiki_cache Cached Fandom wiki responses
MODEL_DIR /subgen/models Whisper model directory
HARDLINK_ROOTS (unset) Roots to search when preserving download hardlinks

ANIME_ROOT and MERGE_ROOTS have different defaults. The generate loop defaults to /media/Anime Library and the merge pass to /data/Media/Anime Library. Set both explicitly; do not rely on either default lining up with the other.

Transcription

Var Default Meaning
WHISPER_MODEL large-v3-turbo Whisper model. large-v3 scores higher; see Choosing an ASR model
COMPUTE_TYPE int8 Precision. On Pascal cards float16 does not load at all — this is not a quality knob there
WHISPER_BEAM_SIZE 7 Beam width
REQUIRE_ENG 1 Skip episodes with no English audio track
SKIP_IF_SRT 1 Skip an episode that already has a dubtitle sidecar
SHOW_NAME (empty) Override the show name derived from the path
GLOSSARY_FILE (empty) Override the glossary resolved from the show
FFMPEG_TIMEOUT / FFPROBE_TIMEOUT 600 / 60 Seconds

Repair

Var Default Meaning
REPAIR_MODEL qwen3-4b-instruct Repair model
REPAIR_BACKEND llamacpp llamacpp or ollama
REPAIR_LLAMACPP_URL http://127.0.0.1:8090/v1/chat/completions llama.cpp chat endpoint. The server behind it must be started with --jinja, or chat_template_kwargs.enable_thinking=false is silently ignored and a thinking-capable model burns its whole budget on reasoning and returns empty content
OLLAMA_URL http://127.0.0.1:11434/api/generate Ollama endpoint
REPAIR_MODEL_SECONDARY (same as primary) Second-opinion model for name changes
REPAIR_UNANCHORED (unset — closed) Global override; prefer the per-show glossary field
DECISIONS_APPLY 1 Apply stored human verdicts during repair
LOGPROB_MIN -0.4 Below this average logprob, a line is a repair target
NSP_MAX 0.5 Above this no-speech probability, a line is skipped
LEN_RATIO_MIN / LEN_RATIO_MAX 0.6 / 1.5 Accepted length change
MAX_REF_BORROW 3 Words a repair may take from the reference
REPAIR_PHONETIC_MIN 0.75 Phonetic similarity floor for a name correction
REPAIR_TIMEOUT_CONNECT / _READ 10 / 120 Seconds

Punctuation restore

Var Default Meaning
RESTORE_PUNCTUATION 1 Enable the punctuation-restore stage
RESTORE_MODEL (falls back to REPAIR_MODEL) Model for this stage
RESTORE_BACKEND (falls back to REPAIR_BACKEND) Backend for this stage
RESTORE_MIN_RUN 2 Minimum run of unpunctuated cues to act on
RESTORE_MAX_TOKENS 2048 Generation cap

Glossary mining, acquisition and verification

Var Default Meaning
MINE_MIN_COUNT 3 Occurrences before a mined term is kept
ACQUIRE 1 Run the acquisition step (gen_loop.sh)
ACQUIRE_APPLY (unset — dry run) Write acquired names rather than only reporting
ACQUIRE_MIN_COUNT 3 Occurrences before a candidate is considered
ACQUIRE_MIN_SHARE 0.80 Share of occurrences that must agree
ACQUIRE_MIN_SIM 0.72 Similarity floor for a candidate match
ACQUIRE_UNSEEN_SIM 0.98 Similarity above which a term counts as already known
ACQUIRE_GROWTH_MAX 2 Cap on glossary growth per pass
ACQUIRE_NO_CACHE (unset) Bypass the acquire cache on dry runs
VERIFY_MODEL qwen3:8b Adjudication model for wiki verification
VERIFY_BACKEND (falls back to REPAIR_BACKEND) Backend for verification
VERIFY_WORKERS 4 Concurrent wiki lookups
WIKI_HTTP_TIMEOUT 20 Seconds
WIKI_CACHE_TTL 2592000 30 days, in seconds
WORDLIST_PATH /usr/share/dict/american-english The real-English-word gate

Mux

Var Default Meaning
KEEP_LANGS eng,en,dut,nld,nl,und, Audio and subtitle languages kept
SUB_LANGS eng,en,und, Subtitle languages considered as a reference
MIN_FREE_GB 5 Skip a mux below this free space rather than failing
DUR_TOL 2 Seconds of duration mismatch tolerated
DUB_SUFFIX .eng.dubtitles.srt Sidecar the merge step looks for
REVIEW_GATE_SHOWS (empty) Shows whose episodes wait for review before muxing
REVIEW_GATE_STALE_DAYS 7 A hold older than this is reported loudly and stays held. It buys a log line, never a release
MEDIA_UID / MEDIA_GID 1000 / 100 Ownership set on written files

Review server

Var Default Meaning
REVIEW_PORT 8842 Listen port
REVIEW_BIND 0.0.0.0 Listen address
REVIEW_TOKEN (unset — a token is generated) See Authentication below
REVIEW_MAX_CONCURRENT 16 Concurrent request cap
REVIEW_STEMS_TTL 30 Seconds an episode listing is cached
REVIEW_RESTART 15 Seconds before restarting the server after an exit

Ordering and watch queue

Var Default Meaning
SEASON_START 0 Global watch-order start season
SEASON_PRIORITY_FILE (unset) Per-show start seasons
WATCH_QUEUE_WINDOW_DAYS (unset — step skipped) Days of watch history to consider
WATCH_QUEUE_PIN (unset) Restrict the watch queue to named shows
WATCHSTATE_URL / WATCHSTATE_API_KEY (empty) WatchState source
PLEX_URL / PLEX_TOKEN / PLEX_SECTION (empty) / (empty) / 7 Plex refresh and watch source
PLEX_PATH (empty) Path prefix Plex sees, if it differs

Loop cadence

Var Default Meaning
MERGE_INTERVAL 600 Seconds between merge passes
MERGE_WINDOW (empty — always) Hours a merge sweep may run, HH:MM-HH:MM. May cross midnight. The end is exclusive. Set it when the repair backend is only up part of the day
RESCAN_INTERVAL 21600 Idle seconds after a full generate sweep
ACQUIRE_TIMEOUT 1800 Seconds
VERIFY_TIMEOUT 1200 Seconds
LLM_TIMEOUT_CONNECT / LLM_TIMEOUT_READ 10 / 120 Seconds

Glossary file

$GLOSSARY_DIR/<Show folder name>.json. Keys not listed here are ignored on load.

{
  "show": "One Piece",
  "wiki": "https://onepiece.fandom.com/api.php",
  "initial_prompt": "This is One Piece. Spell names correctly: Luffy, Zoro, ...",
  "unanchored_repair": false,
  "names": ["Luffy", "Zoro", "Spandam"],
  "phrases": ["Enies Lobby", "Water 7"],
  "hard_fixes": { "ruffy": "Luffy", "spondum": "Spandam" },
  "verified": ["Luffy", "Zoro"],
  "flagged": { "SomeName": "no-match" }
}
Key Type Meaning
show string Display name, used in prompts
wiki string Fandom API override when auto-resolution misses
initial_prompt string Passed to Whisper. Measured to make no difference to output; kept for compatibility
unanchored_repair bool true when your copies of this show carry no English subtitles for the Japanese audio
names list Proper nouns
phrases list Multi-word terms
hard_fixes object Exact replacements. A key containing a space is matched as a phrase, otherwise as a token. Keys are lowercased on load
verified list Verifier bookkeeping
flagged object Terms the verifier could not confirm

glossary_verify.py can be run by hand:

python3 glossary_verify.py "/config/glossaries/<Show>.json" [--wiki URL] [--force]

Decision store

$DECISIONS_DIR/<Show folder name>.json. One store per show, intended to be committed to git.

An entry is keyed on the normalised text pair, never on episode or line number:

{
  "orig": "dothamingo's coming",
  "proposed": "doflamingo's coming",
  "verdict": "correct",
  "text": "Doflamingo's coming.",
  "run": "review",
  "at": 1756600000.0
}

Verdicts: accept, reject, correct, force.

force admits a repair the mechanical gate refused. It overrides the judgement checks but never the card-fit check, because card timing is immutable.

Normalisation for matching: case and runs of whitespace are folded; the curly apostrophe ' is folded to '. Punctuation is otherwise kept — it is part of a line's identity.


Review server

Started by container_run.sh; takes no command-line arguments. Port comes from REVIEW_PORT.

Method Path Purpose
GET /, /index.html Review page. ?stem=<episode> filters to one episode
GET /shared Lines appearing in two or more episodes
GET /api/episodes Every episode with pending items
GET /api/episode One episode's queue, in decision order
GET /api/shared Repair pairs occurring in two or more episodes
POST /api/decide Record verdicts
POST /api/shared Record verdicts on shared lines
POST /api/apply Rewrite the subtitle and drop the stamp so the episode re-muxes

Authentication

The token is presented in an X-Review-Token header.

REVIEW_TOKEN Behaviour
Unset A token is generated, persisted 0600, and printed to the log once
Set to a value That value is the token
Set to the empty string Auth disabled. Only an explicit empty value does this

Write routes require the token. Read routes never do. The server runs in a root-owned process tree and its write routes rewrite subtitles and force re-muxes, so do not expose it to a network you do not control.

To retrieve a generated token:

docker exec <container> cat /config/review_token