MMS Audio-Text Aligner (SRT)
Karaoke-grade subtitles from a transcript you already have
- audio
- srt
- output_path
The fastest way to turn "I have the transcript" into "I have the timestamps"
Most subtitle workflows in ComfyUI go one direction: Whisper hears audio, guesses the text, and timestamps its own guess. This node goes the other way. You already have the transcript - a script, translated lyrics, captions that drifted, a dubbed line - and you want to know exactly when each word is spoken. That's forced alignment, and this is the rare node that does it in the graph and hands you a ready-to-use .srt.
It's the difference between someone summarizing your audio and someone placing a ruler on it. If your text and audio say the same thing, you get word-level start/end times, and from those you build subtitles. Perfect for karaoke-style captions, translated subs, or re-timing a Whisper transcript that's close but off by half a second.
How it works
Under the hood it's Meta's facebook/mms-1b-fl102, a 1B-parameter speech model that covers 102 languages (trained on the cleaner FLEURS dataset). Each language gets its own adapter weights and native tokenizer - no romanization needed for most scripts.
The pipeline: your audio is resampled to 16 kHz mono in memory, the transcript's language is detected with lingua (when language=auto), the model runs one forward pass to get CTC emissions, and torchaudio.functional.forced_align does the actual alignment math. Token spans get merged into word boundaries, then grouped into SRT cues. All in RAM - no temp WAV files dumped on your disk.
The big catch is upfront, not in the pipeline: the ~3.2 GB model downloads to ComfyUI/models/mms/facebook__mms-1b-fl102/ on your first run, not at install. Plan for a slow first execution; every run after uses the cache.
The inputs that matter
The node has a lot of knobs, but a beginner really sets these:
audio- from any node that outputs ComfyUI'sAUDIOtype, normally LoadAudio.text- the transcript, multiline. Punctuation and newlines here are usable as segmentation cues.language-autodetects from the text. If detection fails, pick your language from the dropdown or enter an ISO 639-3 code viacustom.segmentation_mode-single_wordgives one word per cue (karaoke),max_chars+punctuation+newlinesis the sensible default for reading subs.gap_ms- bridges sub-300 ms silences between cues so single-word subtitles don't flicker. Set 0 to disable.output_name- base filename, no extension;.srtis appended automatically.
Everything else is optional tuning: max_chars_per_line, remove_punctuation (keeps apostrophes and hyphens, so "don't" survives), split_count to split into name_1.srt, name_2.srt…, romanize as a fallback, and precision (bf16 default - fp8 is experimental and wants PyTorch 2.1+).
Two outputs: srt (the full subtitle text, always populated, wire it to a Preview Text node to check without writing a file) and output_path (newline-separated list of files written to ComfyUI's output folder).
Installing it
ComfyUI Manager: search "MMS-Aligner". Or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/hnvcam/ComfyUI-MMS-Aligner
cd ComfyUI-MMS-Aligner
pip install -r requirements.txt
Then restart and the node appears under audio/alignment as "MMS Audio-Text Aligner (SRT)". torch and torchaudio come from ComfyUI; transformers, huggingface_hub, uroman, and lingua-language-detector are pure Python - no compiler, no MSVC, which is exactly what the author promises. One genuine gotcha: the README's own install snippet still says idealweek/ComfyUI-MMS-Aligner (an earlier repo name - the live remote is hnvcam). If you copy-paste the stale URL, you'll be chasing a repo that moved. Clone the hnvcam one.
Also note faster-whisper in requirements.txt is commented as optional - you only need it for chunk_with_whisper=True, which splits long audio (roughly 15–20+ minutes on consumer GPUs) after MMS OOMs on it.
Troubleshooting
- "Could not auto-detect language" - your text is too short or mixed. Just set
languageexplicitly. - "Transcript produced no alignable tokens" - the text has characters outside the language's tokenizer vocab. Try
romanize=True, or double-check your ISO 639-3 code. - Slow first run - it's the 3.2 GB download. Not a bug; it caches.
- Your language isn't in FL102's 102 languages - the README is blunt that this node won't work for you. Check before you sink a download into it.
- Out of memory on long clips - drop
precisiontofp16, or turn onchunk_with_whisperso a rough Whisper pass finds safe split points and each chunk aligns independently.
If you've got a clean transcript and you're tired of Whisper's timestamps drifting off the audio, this is the node you're actually looking for.
Inputs (15)
| Name | Type | Default | Description |
|---|---|---|---|
| audio | AUDIO | Audio to align. Connect any node that outputs the ComfyUI AUDIO type (e.g. LoadAudio). Will be resampled to 16 kHz mono in memory. | |
| text | STRING | Transcript to align with the audio. May contain punctuation and newlines, both of which can be used as segmentation cues for the SRT output. | |
| language | COMBO | auto | Language of the transcript. `auto` detects from the text. Pick `custom` to enter any ISO 639-3 code in `custom_language_code`. The FL102 model supports 102 languages — the chosen language loads its specific adapter weights and tokenizer. |
| custom_language_code | STRING | ISO 639-3 code (e.g. `vie`, `eng`, `cmn`). Used only when `language` is set to `custom`. | |
| romanize | BOOLEAN | false | Romanize the transcript before alignment using uroman. Generally NOT needed — the FL102 tokenizer is trained on each language's native script. Useful as a fallback if you see 'no alignable tokens' errors with text that contains unusual characters. |
| segmentation_mode | COMBO | max_chars+punctuation+newlines | How to split words into SRT subtitle lines: - single_word: one word per cue (karaoke-style) - max_chars: pack greedily up to `max_chars_per_line` - punctuation: break at . ! ? , ; : - newlines: break at line breaks in the input text - combinations apply all selected rules |
| max_chars_per_line | INT | 420–1000 | Maximum characters per SRT line. 0 disables the limit (relevant only when `segmentation_mode` includes `max_chars`). |
| remove_punctuation | BOOLEAN | false | Strip punctuation (. , ! ? ; : " ( ) [ ] { } — – … « » “ ” ‘ ’) from the SRT output text. Apostrophes and hyphens are kept (so "don't" and "well-known" remain intact). Punctuation still drives segmentation when `segmentation_mode` includes it; only the displayed text is cleaned. |
| output_name | STRING | subtitles | Base file name (no extension) written to ComfyUI's output directory. The `.srt` extension is added automatically. When `split_count` > 1, files are named `<output_name>_<index>.srt`. |
| gap_ms | INT | 3000–5000 | Bridge small gaps between consecutive cues. If the gap between two cues is smaller than this many milliseconds, the previous cue's end is snapped to the next cue's start (prevents subtitle flicker during fluent speech, especially in `single_word` mode). Larger gaps are preserved as natural pauses. Set to 0 to disable. |
| split_count | INT | 11–5 | Number of .srt files to split the output across. 1 (default) writes a single file. Higher values divide cues into near-equal chunks named `<output_name>_1.srt`, `<output_name>_2.srt`, ... |
| precision | COMBO | bf16 | Compute precision for the MMS model: - bf16 (default): half the VRAM of fp32, wide dynamic range, accurate on Ampere+ GPUs - fp16: half the VRAM of fp32, may underflow on very long audio - fp8: experimental — quantizes Linear weights to float8_e4m3fn, requires PyTorch 2.1+ - fp32: full precision, highest VRAM usage |
| chunk_with_whisper | BOOLEAN | false | Split long audio into smaller chunks using faster-whisper word-level timestamps to find safe split points. Required if MMS OOMs even at fp16/bf16 (typically audio > 15-20 min on consumer GPUs). Adds a Whisper pass before alignment, then merges chunk SRTs. |
| whisper_model_size | COMBO | base | Faster-whisper model size used for chunk-point detection (only when chunk_with_whisper=True). `base` is usually plenty — we only need rough word timings to find anchor points. Larger models give more reliable matches in noisy or tonal-language audio at the cost of VRAM and speed. |
| chunk_max_seconds | INT | 36060–1800 | Maximum length per chunk in seconds (only when chunk_with_whisper=True). 360s (6 min) is conservative and fits comfortably on 16 GB GPUs at bf16. Increase if your GPU has more VRAM and you want fewer chunks. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| srt | STRING | — |
| output_path | STRING | — |