Nodes/ComfyUI Eclipse/Render Lyric Captions
ComfyUI Node

Render Lyric Captions

A real lyric video from your own song, rendered locally

By r-vage·Created 12 months ago·Updated 14 days ago· 38
Render Lyric Captions
  • audio
  • vocals
  • background
  • video
  • srt
  • report
  • timing_json
  • cleaned_lyrics
◄lyrics—►
◄languageen►
◄deviceauto►
◄font_fileRoboto-Regular.ttf►
◄font_size80►
◄modefloating-words►
◄width720►
◄height1280►
◄fps24.00►
◄positionbottom_center►
◄margin_x40►
◄margin_y40►
◄text_color#ffffff►
◄highlight_color#ffff00►
◄outline_color#616161►
◄outline_width3►
◄background_color#000000►
◄caption_transparency0►
◄enable_glowfalse►
◄glow_intensity5►
◄glow_range25►
◄glow_blur15►
◄glow_inner_color#2ec0ff►
◄glow_outer_color#006eff►
◄trim_start—►
◄duration—►
◄timing_adjustment0.00►
◄circle_radius30.0►
◄float_distance1.5►
◄fade_in0.50►
◄fade_out0.50►
◄min_display0.70►
◄max_words4►
◄max_simultaneous5►
◄seed42►
◄rotation_axisTurning sign►
◄corrected_timing—►
◄unmatched_passagesomit►

Almost every lyric video you've seen made by someone in this community was finished in CapCut, because ComfyUI had nothing to say about captions. This is the first node I've seen that keeps the whole job in the graph: song in, lyrics in, and back comes a VIDEO with the words animated over the original soundtrack, plus an SRT and a timing JSON you can hand-edit.

What it's for

People generating music locally with ACE-Step keep hitting the same wall: the track is fine, the vocals are rough, and they want it on video with words. The old workflow was render the clips in ComfyUI, escape to an editor, hand-time the text. This node removes the escape - generate or load the song, audition it with Load Audio, render captions, ship it through Save Video. It isn't a karaoke machine and it won't invent a performance. It takes audio, aligns your lyrics against what's actually sung, and burns text into frames.

How it works

Under the hood it makes one recognition pass over the full song (local Whisper large-v3 via stable-ts, the same stack as the pack's Transcribe Audio node) to find where your supplied lines are sung, then forced-aligns your words inside those matched windows. Original wording is matched in sequence, including repeated sections, and recognized text is never substituted for what you wrote.

Connect vocals - a full-song stem, time-zero aligned, duration within 50 ms - and it uses that for detection and alignment; rendering always uses the original mix, so you keep the soundtrack. On the author's own qualification runs, separation improved vocal detection without improving lyric matching, so don't treat it as a fix-all.

Two consequences follow:

  • Lines it can't match are omitted from the captions, not shoved into an instrumental gap on a guess. The report lists them, and its coverage percentage is a line-coverage figure, not a timing-accuracy score.
  • Nothing is interpolated. A partially timed line stays visible across its recognized span, but only words with real timings highlight.

Inputs and outputs that matter

Required and easy to miss: audio and lyrics are both full-song inputs, and lyrics is socket-only - wire a String or Multiline Text node into it, there's no widget to type into. Same for trim_start and duration: FLOAT sockets with no default, so they must be wired. duration = 0 renders the remainder of the song, and the two of them trim audio and captions together.

The rest you'll actually touch: language, device (cpu int8, cuda float16, auto picks), mode (whole-line, active-word, floating-words, floating-lines, rotating-words, rotating-lines), font_file and font_size, the colour fields, enable_glow with its five controls, and caption_transparency to fade text without deleting it. Animation controls - fade_in, fade_out, min_display, max_words, max_simultaneous, seed, circle_radius, float_distance - only appear for the modes that use them.

Optional corrected_timing is the power tool: paste reviewed timing JSON in and inference is skipped entirely.

Outputs are video, srt, report, timing_json, cleaned_lyrics. Wire video into Preview Video [Eclipse] to eyeball it, then into Save Video [Eclipse] to write the file. srt is clip-relative; timing_json is always full-song and doubles as corrected-timing input:

{"version": 1, "duration": 210.4, "language": "en",
 "lines": [{"text": "Hello world!", "start": 2.0, "end": 4.0,
   "words": [{"text": "Hello", "char_start": 0, "char_end": 5, "start": 2.0, "end": 2.8}]}]}

Character offsets count Python characters and must slice the line exactly, or the edit is rejected.

Install

Same pack, but this node has two real prerequisites: the Whisper files and fonts.

cd ComfyUI/custom_nodes
git clone https://github.com/r-vage/ComfyUI_Eclipse
python -m pip install -r ComfyUI_Eclipse/requirements.txt   # stable-ts, faster-whisper, fonttools

mkdir -p ComfyUI/models/whisper/large-v3
huggingface-cli download Systran/faster-whisper-large-v3 \
  --revision edaa852ec7e145841d8ffdb056a99866b5f0a478 \
  --local-dir ComfyUI/models/whisper/large-v3

Everything is verified against that pinned revision, and nothing downloads automatically. Fonts live in ComfyUI/models/fonts/ (TTF/OTF); if that folder is empty, Eclipse copies in its bundled fonts on first use - which is why the default is Roboto Regular unless you install something nicer.

Troubleshooting

"Missing large-v3 files" or "Unverified large-v3 artifact" means the model folder isn't the pinned conversion. Don't mix and match files from different repos.

A missing-value error you didn't cause: trim_start and duration have no widgets by design. Wire two FLOAT nodes.

Captions correct but sparse. Check the report before blaming the renderer - unmatched lines are excluded by design, and text_match_coverage tells you what fraction of lines were anchored. Sustained notes, dense production, long intros and wrong lyrics all degrade this.

Missing glyphs, or Arabic/CJK rendering badly. Pick a font that covers the script; Pillow needs RAQM for shaping and RTL, and the node errors before encoding rather than producing boxes.

Don't reach for GetVideoComponents on the result. Four minutes at 720×1280/24 fps is roughly 59 GiB of float32 frames. The renderer is file-backed and streams straight into Save Video; keep it that way.

The genuinely useful trick from the author's docs: put an Any Multi-Switch Mixed node after the renderer - captioned VIDEO in slot one, a still image in slot two - feeding Save Video. Mute the renderer and you save the still with the audio instead.

Category🌒 Eclipse/ Video

Inputs (41)

NameTypeDefaultDescription
audioAUDIO—
lyricsSTRINGFull-song plain lyrics or YuE2/MiniMax song JSON.
languageCOMBOen101 options: Auto, en, zh, de, es, ru, +95
deviceCOMBOauto3 options: auto, cpu, cuda
font_fileCOMBORoboto-Regular.ttf7 options: Allura-Regular.ttf, FreeMono.ttf, Oswald-Bold.ttf, Roboto-Regular.ttf, ShareTechMono-Regular.ttf, comic.ttf, +1
font_sizeINT808–512—
modeCOMBOfloating-wordsWords modes group fast words into phrases; lines modes use complete lines. Rotating captions turn in place at a speed set by lyric timing.
widthINT72016–4096—
heightINT128016–4096—
fpsFLOAT24.001–120—
positionCOMBObottom_center9 options: top_left, top_center, top_right, center_left, center_center, center_right, +3
margin_xINT400–2048—
margin_yINT400–2048—
text_colorSTRING#ffffff—
highlight_colorSTRING#ffff00—
outline_colorSTRING#616161—
outline_widthINT30–32—
background_colorSTRING#000000—
caption_transparencyFLOAT00–100Fade text, outline, highlight and glow together. 100% hides captions; background and audio are unchanged.
enable_glowBOOLEANfalse—
glow_intensityINT52–20Glow buildup iterations (more = denser glow).
glow_rangeINT251–500Maximum glow expansion distance in pixels.
glow_blurINT150–500Blur radius applied to each glow step.
glow_inner_colorSTRING#2ec0ff—
glow_outer_colorSTRING#006eff—
trim_startFLOAT0–86400Trim the full soundtrack and captions together, in seconds from the start of the song.
durationFLOAT0–86400Connect seconds explicitly. 0 renders the remainder of the song; the end is clamped to available samples.
timing_adjustmentFLOAT0.00-86400–86400Positive values delay captions.
circle_radiusFLOAT30.00–50Floating caption centers stay inside this circle, centered on the video. Radius is a percentage of the shorter canvas dimension.
float_distanceFLOAT1.50–20Maximum gentle movement as a percentage of the shorter canvas dimension.
fade_inFLOAT0.500–2Fade-in seconds, added before the minimum readable hold. Shortened when floating slots or the next rotating caption need room.
fade_outFLOAT0.500–2Fade-out seconds after the sung span and minimum readable hold. Floating items can overlap while slots remain; rotating items finish by the next onset.
min_displayFLOAT0.700–3Minimum seconds at full animation opacity, excluding fades. Also groups fast words. Reduced when floating slots or the next rotating caption need room; sung onsets never move.
max_wordsINT41–12Maximum words to combine per phrase. Indivisible timed spans and attached untimed text stay intact.
max_simultaneousINT51–8Maximum visible floating items, including outgoing fades.
seedINT420–18446744073709550000Repeatable full-song positions, also preserved when trimming.
rotation_axisCOMBOTurning signRotating modes: Turning sign turns around the vertical axis; Flipping card turns around the horizontal axis. Speed follows lyric timing automatically.
vocalsoptAUDIOFull-song isolated vocals starting at zero. Duration must match audio within 50 ms; never shifted or stretched.
corrected_timingoptSTRINGFull-song timing JSON matching cleaned lyrics. Bypasses alignment and lazy vocal separation.
backgroundoptCOMFY_MATCHTYPE_V3Optional IMAGE, image batch/list, exact frame timeline, or one VIDEO. Timelines require matching caption FPS and stream exact pixels. Images play in order; VIDEO uses timestamps. Short backgrounds hold their last frame. Background audio is ignored.
unmatched_passagesoptCOMBOomitKeep matched lyrics. Transcribe uses recognized words only in gaps with unmatched lyrics; review added passages in timing JSON. Omit preserves the supplied-lyrics-only behavior.

Outputs (5)

NameTypeDescription
videoVIDEO—
srtSTRING—
reportSTRING—
timing_jsonSTRING—
cleaned_lyricsSTRING—