Text β Frames (Speech Estimate)β‘
Stop guessing how many frames your narration needs
- frames
- seconds
- characters
- text
Here's the loop: you write a script, generate a voice track from it, then need a video long enough to hold that voice. But the video model needs the frame count before it runs - and you're the one who has to type it. Guess low and your lip-sync clip freezes on its last frame while the narrator talks over a still image. Text β Frames (Speech Estimate) β‘ turns your script text into a frame count so you stop guessing.
It's the smallest node in the pack - forty lines of arithmetic, no model, no dependencies. It's the one I'd actually reach for, because the alternative is doing this maths in your head at 2 a.m.
The mechanism, in full
There's no hidden model here. The math is:
seconds = characters Γ· chars_per_second + padding_seconds
frames = ceil(seconds Γ fps)
"Characters" means the length of your text after cleanup: trailing spaces and tabs are stripped from each line, and the whole block is trimmed, but internal spaces and line breaks survive. Empty input returns (0, 0.0, 0, "").
The rounding is deliberate. The author's comment in the source: "Always round UP so generated video is not shorter than the estimated speech duration." That matches how these pipelines actually fail - in lip-sync workflows, if the audio outlasts the video the clip hangs on the final frame. Overshooting by two frames costs nothing; undershooting wastes the whole render.
Inputs and outputs
Four inputs, all required:
text- multiline, and dynamic prompts are off, so your wildcards won't expand into something a different length than what gets spoken.fps- default 25, range 1β240. This must match the fps you'll actually render at, or the frame count is meaningless. LTX-2 defaults to 24 fps and suggests 48 for image-to-video, so it isn't always 25 - check your model.chars_per_second- default17.333333, range 1β100. This is the whole personality of the node; see below.padding_seconds- default 0, up to 60. Lead-in and lead-out; half a second at each end is a decent habit for narration.
Four outputs, and they're more useful than they look:
frames(INT) - the number you wire into your video node's length field.seconds(FLOAT) - the same estimate in seconds, for anything that wants time.characters(INT) - the cleaned text length, which is how you sanity-check why your estimate looks off.text(STRING) - your cleaned text, passed through. This is the useful one: feed this to your TTS node instead of retyping the script elsewhere. Now the string that was counted is literally the string being spoken, which is the only way the estimate stays honest when you edit the script.
Calibrating it (the actual pro move)
The default 17.33 chars/sec is the author's calibration: 156 characters β 9 seconds. That's a brisk conversational pace.
Your TTS voice doesn't know that. Kokoro, Chatterbox, F5-TTS and whatever ElevenLabs-alike you're using all read at different rates, and a dramatic read is slower still. So do this once: generate ten seconds of your chosen voice, count the characters, divide. That's your chars_per_second, and it's the difference between roughly right and exactly right for your voice.
Two caveats: the count includes spaces and newlines, so a hard-wrapped script reads slightly long; and the default is calibrated for English. For CJK or a language with different speech density, recalibrate or don't trust the number.
Install
Nothing to download. No models, no pip packages - the module is pure Python.
ComfyUI Manager: search ComfyUI-Get-Random-File, install, restart.
Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/ChrisColeTech/ComfyUI-Get-Random-File
The node appears under π€ CCTech/Files. It isn't in the pack's README (that one documents the random-file and save nodes), so don't hunt for docs - the node menu is the documentation here.
Gotchas
It's not an output node, so it won't run on its own. ComfyUI executes backwards from output nodes and caches everything else. If nothing downstream consumes frames, seconds, characters or text, the node never fires and never prints anything. That's normal, but it confuses people testing it in isolation - wire an output into something that actually runs.
frames = 0 means your text is empty or whitespace-only. Not a bug.
Your video model may want frames in a specific quantum. Wan's native window is 81 frames and its family generally wants counts of the form 4n+1, which is why you see 121 and 241 everywhere. If your loader insists, round the node's output up to the next valid value - over-shooting is free here, under-shooting isn't.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| text | STRING | β | |
| fps | INT | 251β240 | β |
| chars_per_second | FLOAT | 17.331β100 | β |
| padding_seconds | FLOAT | 0.00β60 | β |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| frames | INT | β |
| seconds | FLOAT | β |
| characters | INT | β |
| text | STRING | β |