Add subtitle dictionaries, Polish number normalizer, ComfyUI lifecycle control

Text processing before TTS:

- dictionary.txt rewrites words and phrases (km/h, np., units). Rules compile
  into one alternation applied in a single pass, so replacements never chain
  and longest-literal wins. Case-sensitive, whole-word.
- A per-queue second dictionary, written by the GUI into the first video's job
  folder and merged with the shared one (--dictionary-extra).
- ALL-CAPS blocks become Title Case rather than sentence case: lowercasing
  destroyed proper nouns inside captions ("NASA - MARGO MADISON") and invited
  the normalizer to "correct" the name. Paired clips in output/clips/all-caps
  showed the TTS reads both casings the same.

normalizer/ — digits to spoken Polish, in its own venv because it needs
transformers>=5, which must not touch ComfyUI's pinned versions:

- Two backends. Plain integers go to num2words for the value and a general
  Polish LLM for the case; times, dates, decimals and identifiers go to a 0.6B
  fine-tune. Measured on 40 real subtitle lines, each is clearly better than
  the other on its half. 74% of numbered blocks take the first leg.
- Guards on every answer: runaway length, repetition loops, invalid bytes,
  leftover digits, a lost minus sign, a changed number, a lost word. A failure
  falls back to num2words, which is nominative but never wrong about the value.
- ComfyUI is asked to unload before normalizing; a normalizer that does not fit
  in VRAM is spilled to RAM silently at 5x the cost.

Backend lifecycle:

- ComfyUI is found by port through /proc, so a backend started by hand is
  managed too. SIGTERM escalates to SIGKILL, and a suspended process is killed
  outright since it never runs its handlers.
- A clip that kills the backend is retried three times, each with a different
  seed (the aborts are seed-dependent, not text-dependent), then skipped and
  reported so an overnight queue keeps moving.
- start_comfyui.sh execs python, so the script's PID is ComfyUI's.
- GUI gains ComfyUI and Queue dictionary buttons; quitting stops the backend.

--resume-from now reuses the job folder's tts_input.srt instead of reparsing
the subtitles, so block N still pairs with clip_NNNN.flac.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 21:55:31 +02:00
co-authored by Claude Opus 5
parent 7e2cee02e5
commit e201aadc3e
21 changed files with 3759 additions and 52 deletions
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
# Create the isolated environment for the Polish number normalizer.
#
# ./setup.sh venv + dependencies
# ./setup.sh --model also download the model (needs HF_TOKEN)
# ./setup.sh --cpu CPU-only torch, for a machine without an NVIDIA GPU
#
# Kept separate from the ComfyUI environment on purpose: this needs
# transformers >= 5, and the TTS install must not be disturbed.
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VENV="$HERE/venv"
MODEL_DIR="$HERE/model"
MODEL_REPO="Folx/qwen3-0.6b-pl-text-normalization"
TORCH_INDEX="https://download.pytorch.org/whl/cu128" # same wheels as ComfyUI
WANT_MODEL=0
CPU_ONLY=0
for arg in "$@"; do
case "$arg" in
--model) WANT_MODEL=1 ;;
--cpu) CPU_ONLY=1 ;;
-h|--help) sed -n '2,10p' "${BASH_SOURCE[0]}" | sed 's/^# \?//'; exit 0 ;;
*) echo "Unknown option: $arg" >&2; exit 2 ;;
esac
done
say() { printf '\n\033[1;36m==> %s\033[0m\n' "$1"; }
say "Creating $VENV"
python3 -m venv "$VENV"
"$VENV/bin/pip" install --upgrade pip --quiet
say "Installing torch"
if [ "$CPU_ONLY" = 1 ]; then
"$VENV/bin/pip" install torch --index-url https://download.pytorch.org/whl/cpu
else
"$VENV/bin/pip" install torch --index-url "$TORCH_INDEX"
fi
say "Installing transformers, accelerate, num2words"
"$VENV/bin/pip" install "transformers>=5.0.0" "accelerate>=0.26.0" "num2words>=0.5.13"
if [ "$WANT_MODEL" = 1 ]; then
say "Downloading $MODEL_REPO"
"$VENV/bin/pip" install --quiet "huggingface_hub[cli]"
# Either an HF_TOKEN in the environment or a stored `hf auth login` works;
# the CLI picks up both on its own. Only say something when there is
# neither, because the failure then is a confusing "access denied".
if [ -z "${HF_TOKEN:-}" ] && ! "$VENV/bin/hf" auth whoami >/dev/null 2>&1; then
cat >&2 <<EOF
No Hugging Face credentials. The repo is gated (approval is automatic, but it
has to be your account that asks):
1. Open https://huggingface.co/$MODEL_REPO while signed in and accept the terms
2. Either $VENV/bin/hf auth login
or HF_TOKEN=hf_... $0 --model
EOF
exit 1
fi
"$VENV/bin/hf" download "$MODEL_REPO" --local-dir "$MODEL_DIR"
fi
say "Checking the installation"
"$VENV/bin/python" - <<'PY'
import sys
try:
import torch, transformers
print(f" torch {torch.__version__} cuda_available={torch.cuda.is_available()}")
if torch.cuda.is_available():
print(f" device: {torch.cuda.get_device_name(0)}")
print(f" transformers {transformers.__version__}")
if int(transformers.__version__.split('.')[0]) < 5:
print(" WARNING: transformers < 5 produces wrong output with this model",
file=sys.stderr)
except ImportError as exc:
print(f" {exc}", file=sys.stderr); sys.exit(1)
PY
"$VENV/bin/python" "$HERE/normalizer.py" --status
if [ ! -f "$MODEL_DIR/config.json" ]; then
cat <<EOF
The environment is ready, but the model is not downloaded yet, so the
normalizer will run on the degraded num2words fallback.
1. Accept the terms at https://huggingface.co/$MODEL_REPO
2. HF_TOKEN=hf_... $HERE/setup.sh --model
EOF
else
say "Ready. Try: $VENV/bin/python $HERE/normalizer.py 'Wygrał 1247 złotych.'"
fi