Lyrics to LRC
The node that actually times your song
- audio
- lrc
Anyone can write an .lrc file. Writing one whose timestamps land on the actual sung line is the hard part, and this is the node that does it. Lyrics in, timed LRC text out - one output, and everything else in the pack exists to feed it or save what it produces.
Three ways to place a line, best first
The node picks automatically, and it's worth understanding which one you're getting because the quality difference is enormous.
1. Forced alignment (good). Wire the final AUDIO into the audio input and install WhisperX. The node writes the audio to a temp WAV, hands WhisperX your exact transcript, and gets back real word-level timestamps. It doesn't transcribe - it aligns your words to the audio, which is why the words can't come out wrong. It then checks its own work: if the alignment collapses a run of lines into the last second (the classic sign of a truncated render), or timestamps go backwards, or more than 15% of lines got nothing, it retries with wide per-line windows and falls back rather than shipping nonsense.
2. Score timing (decent). Feed a structure string and the lines are placed by the music instead of being spread evenly. Two formats are accepted: SheetSage2 output as start<TAB>end<TAB>section rows, or a YuE2 ABC score containing % intro / % verse / % chorus markers. The node parses the tempo, bar counts and vocal note spans, fits the written tempo to the rendered audio length, and matches each [verse]/[chorus] block to its span. This is what the bundled FL-YuE2 example wires up, and it's noticeably better than even timing.
3. Even timing (better than nothing). No audio, no score: lines get spread across the duration, with a short intro and outro skipped so the first line isn't jammed into second zero (YuE2 vocals typically come in around 6% in).
Inputs and outputs
Required:
lyrics- the words. Wire Lyrics Clean's output here rather than typing a second copy in.seconds- the duration to spread over when nothing better is available. It's ignored the momentaudiois connected, because the real duration gets read off the waveform.
The optional ones that matter:
audio- the finished song. This is the switch that upgrades you from even timing to real alignment.structure- the SheetSage2 or ABC score described above.title- written as the[ti:]tag, defaultGenerated Song. Wire Song Filename'ssafe_titlehere so the tag matches the filename.artist- optional, written asar:. Blank by default, and left out of the file entirely when empty.timing_scale- 0.7 to 1.4, default 1. Push it above 1 when the lyrics run early, below 1 when they run late. This is your global stretch.offset_seconds- ±30s, shifts everything. Reach for this first for a constant half-second lag.language- the ISO code WhisperX aligns against,enby default.prefer_alignment- on by default. Turn it off to skip WhisperX entirely and go to score/even timing even with audio connected.word_timing- on by default. With WhisperX this writes enhanced LRC, a<mm:ss.xx>tag before every word, which lets the pack's player fill a line word by word karaoke-style. Turn it off for players that render the tags as literal text.
Output is one string, lrc, and it's the complete file: [ti:] (and [ar:] if you set one), [length:], a [by:SongLRC …] line recording which method timed it, then [mm:ss.xx]line for each line. Feed it to Save Matching LRC or Save LRC (auto), and to Music Player (SongLRC) if you want to check it by ear.
Install
Manager → search ComfyUI-SongLRC, or:
cd ComfyUI/custom_nodes
git clone https://github.com/TheAwaken1/ComfyUI-SongLRC
pip install -r ComfyUI-SongLRC/requirements.txt
The pack itself needs only numpy and soundfile. WhisperX is separate and optional, and there's a right way to install it:
pip install whisperx --no-deps
pip install faster-whisper ctranslate2 nltk pandas pyannote.audio omegaconf
python -c "import torch; print(torch.cuda.is_available())"
The --no-deps flag is not optional. A plain pip install whisperx will happily drag in a CPU-only PyTorch build and replace the CUDA one your ComfyUI is running on, which is a known way to wreck a working install. If that torch.cuda.is_available() prints False, reinstall the CUDA torch build you had and don't run the plain install again.
When the timing is off
If everything is uniformly early or late, that's offset_seconds or timing_scale, not a broken model. Lines drifting further apart as the song goes on is a tempo problem - also timing_scale territory, and often a sign that score timing was used on a render whose max_duration truncated the composition.
And if you get plain even timing with no explanation, read the console. The node logs which path ran, and "whisperx not installed" is the most common reason you got the fallback instead of real alignment.
Inputs (11)
| Name | Type | Default | Description |
|---|---|---|---|
| lyrics | STRING | — | |
| seconds | FLOAT | 120.00–600 | Ignored when AUDIO is connected; duration is read from the audio. |
| audioopt | AUDIO | — | |
| structureopt | STRING | SheetSage2 structure (start<TAB>end<TAB>section) OR a YuE2 ABC score with % intro/% verse/% chorus. Times lyrics to the score instead of spreading them from t=0. | |
| titleopt | STRING | Generated Song | — |
| artistopt | STRING | Optional. Written as the LRC ar: tag. | |
| languageopt | STRING | en | — |
| offset_secondsopt | FLOAT | 0.0-30–30 | — |
| timing_scaleopt | FLOAT | 1.000.7–1.4 | Stretch timestamps if lyrics feel early/fast (>1) or late/slow (<1). |
| prefer_alignmentopt | BOOLEAN | true | — |
| word_timingopt | BOOLEAN | true | With WhisperX alignment, also write a <mm:ss.xx> tag before every word (enhanced LRC) so the player fills each line word by word, karaoke style. Turn off for players that show the tags as text. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| lrc | STRING | — |