ComfyUI Extension: ComfyUI-ForcedAlignTimings
Run ComfyUI workflows without the setup
No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.
Word-level forced alignment for the Synthy/iRead custom TTS provider that aligns generated speech against known input text and returns per-word timings.
README
ComfyUI-ForcedAlignTimings 🎯⏱️
Word-level forced alignment for the Synthy/iRead custom TTS provider
(boris_tts_*). Aligns generated speech against the known input text and
returns per-word timings — the raw material for the X-Voice-Markup
word_timestamps array (see SY-A-39.md).
Why forced-align (not ASR)
The text is known — we synthesized the audio from it. So we align audio to
that exact text instead of transcribing with Whisper and fuzzy-matching its
guesses. This is robust on the SY-A-39 stress cases (repeats test test test,
numbers в 2026 году).
Pipeline
CosyVoice (text -> audio)
-> ForcedAlignTimings (audio + text -> raw word timings) ← this node (GPU)
-> n8n Code node (raw timings + ORIGINAL text -> markup) ← UTF-16 / mapping
The node does not do UTF-16 offsets or normalization mapping — that is pure
string logic owned by the n8n Code node (1/tts-poc/map_timings.mjs).
Node: Forced Align Timings
| | |
|---|---|
| Inputs | audio (AUDIO), text (STRING), device (auto/cuda/cpu) |
| Outputs | timings_json (STRING), audio_ms (INT), word_count (INT) |
timings_json:
{
"sample_rate": 16000,
"audio_ms": 1234,
"align_text": "Hello, world!",
"words": [
{"value":"Hello","char_start":0,"char_end":5,"time":120,"end":480,"score":0.91},
{"value":"world","char_start":7,"char_end":12,"time":640,"end":980,"score":0.88}
]
}
char_start/char_end are Python codepoint offsets into align_text. The
n8n node converts them to UTF-16 offsets against the original request text.
Dependencies
torch+torchaudio— already incomfy_v5.15c.uroman(pip install uroman) — multilingual romanization for RU. Falls back to a builtin Cyrillic→Latin table if absent.- MMS_FA downloads a ~1 GB wav2vec2 aligner on first use — bake it into the image or mount a cache so the GPU box has no cold-start network dependency.
Smoke test on the 5090 box (BEFORE docker commit)
python nodes.py sample_ru.wav "Привет, мир"
# expect JSON with two words and increasing time values
Docker patch (comfy_v5.15c → next)
Follow the SmartGridCutter procedure in .agent/skills/docker-comfyui/SKILL.md:
docker cp this folder into /app/ComfyUI/custom_nodes/, pip install uroman,
verify curl .../object_info/ForcedAlignTimings, docker commit, docker save
→ tar → deploy on Hostkey/RunPod.
Run ComfyUI workflows without the setup
No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.