arkennemasis Narration Fit (land every line on the same mark)
Land every voice-over on the same mark — no more 1.5s gaps next to 4.4s gaps
- narration
- audio
- seconds
- report
Here's the problem nobody warns you about when you build narrated video: your shots can be the same length, your script can give every line the same word budget, and your finished film still feels broken. Because TTS does not read on a metronome. This pack measured one real voice delivering the same kind of 15–17 word line anywhere from 1.88 to 2.83 words a second - a 50% spread, which on an 8-second target is 3.4 seconds of drift. One shot gets a 1.5s pause after the words, the next gets 4.4s. ArkNarrationFit is the node that fixes that: it stretches or compresses a rendered narration so it ends exactly on your target time, pitch preserved.
It lives in the arkennemasis/Video menu of the comfyui-arkennemasis pack, and it is the mirror image of the pack's other timing node, ArkNarrationLength. Pick by which side is locked:
- Shot length is flexible (you're generating each scene to order, e.g. MiniMax H3): use Narration Length to measure the voice and size the shot to cover it.
- Shot length is fixed (a locked montage, every scene sharing one slot): use Narration Fit to size the voice to the shot.
The tooltip spells out the mental math for target_seconds: with a locked shot, that's shot length minus the quiet beat you want after the words. So an 8s shot with a half-second tail means target_seconds = 7.5.
How it works
Feed it the rendered voice-over for this scene (the narration AUDIO input - per-scene, not the whole film), and it measures how long the audio actually is. If it already lands within tolerance of your target (default 0.15s), it hands the audio straight back untouched - no needless re-encode on a line that was already right.
Otherwise it computes the ratio of current length to target and runs the audio through ffmpeg's atempo time-stretch filter, which changes speed without changing pitch. That last part is why it's the right tool instead of just resampling: no chipmunking, no "slow and deep" narration, just the same voice reading a touch faster or slower.
The max_change dial (default 0.15) caps how far it will go - at most 15% faster or slower, which is inaudible on speech. Push past about 0.2 and the read starts to sound wrong, so instead of letting you mangle it, the node clamps and tells you. Its report output will say something like 2.31s -> 8.00s (target 8.00s, speed x0.289) and, when it hit the cap, appends a blunt NOTE: change the word budget, not this node. It refuses to quietly deliver something outside the band it promised.
The inputs and outputs that matter
Honestly, you set one number per scene and leave the rest:
narration- the rendered voice-over, AUDIO in, straight from your TTS node.target_seconds- where the last word should land (default 8).max_change- how much speed change you'll allow (default 0.15).tolerance- leave audio alone if it's already this close (default 0.15).
Out the other side: audio (the fitted narration, ready to feed Video Dub, Video Assemble, or a save node), seconds (the actual landed length, so downstream caption timing doesn't guess), and report (a string worth glancing at - it's the only way you'll find out a line got clamped).
Install and dependencies
Standard pack install, and since it's one repo you get all 75-ish arkennemasis nodes at once:
cd ComfyUI/custom_nodes
git clone https://github.com/Hishamahmer/comfyui-arkennemasis
Then pip install -r requirements.txt (or install via ComfyUI-Manager by the pack title) and restart ComfyUI. Two runtime caveats, both source-grounded: the node shells out to ffmpeg (found on your PATH, else imageio-ffmpeg's bundled binary - if neither exists you get a clear "no ffmpeg found" error), and it needs soundfile for the WAV round-trip. Neither is in the pack's requirements.txt - it only declares replicate and httpx - so a bare install on a lean ComfyUI may hit ModuleNotFoundError: soundfile. pip install soundfile and make sure ffmpeg is reachable, and you're set.
Gotchas worth knowing
- The direction is handled for you. To make a short line longer the node slows it down; the factor math is easy to invert when you DIY this with raw ffmpeg, and inverted it makes every short line shorter still - which looks exactly like the node doing nothing. Let the node own the math.
- A line that genuinely doesn't fit the slot is a script problem. If the clamp fires, don't crank
max_changeto 0.5 to force it - the result will sound sluggish or rushed. Cut or expand the words instead. - Empty narration isn't an error. It returns the audio untouched with a report saying so, which is handy mid-workflow when a branch hasn't rendered yet.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| narration | AUDIO | The rendered voice-over for THIS scene. | |
| target_seconds | FLOAT | 8.000.5–60 | Where the last word should land. With a locked shot, this is (shot length - the quiet beat you want after it). |
| max_change | FLOAT | 0.150–1 | How far the speed may move, as a fraction. 0.15 = at most 15% faster or slower, which is inaudible on speech. Beyond about 0.2 it starts to sound wrong; the node clamps and reports rather than mangling the read. |
| tolerance | FLOAT | 0.150–2 | Leave the audio untouched when it already lands this close. Avoids a needless re-encode on a line that was already the right length. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| audio | AUDIO | — |
| seconds | FLOAT | — |
| report | STRING | — |