LC Film Grain (Video) 🎞️
Grain that survives the encoder
- images
- images
Why you'd reach for it
Grain is the cheapest de-AI move there is. Real cameras are noisy, film is noisy, diffusion output is glassy - and adding a little back is the difference between "AI video" and "footage." The KB's post-processing notes call it folk-standard practice, and it is: people have been doing it in After Effects and Resolve because the ComfyUI options were static-image grain nodes wrapped around a loop.
This is the version built for clips. It's the same grain model as the pack's still-image LC Film Grain, but it runs the whole batch on the GPU a few frames at a time, with a progress bar, and - this is the part that matters - it sizes the grain relative to the frame instead of to the pixel grid. Pixel-sized noise is exactly what h264 and h265 throw away first: you get a slightly bigger file and a video that looks clean anyway. Bigger grain survives compression, so what you see after the encode is roughly what you saw before it.
The author's own number: about a second for 124 frames of 1080p.
How it works
Frames go through in chunks of eight, converted to float32 on the GPU, grain applied, result copied back. Each frame gets its own seed derived from seed plus its frame index, which is what makes the grain change frame to frame the way real film does. Cancel is checked between chunks, so it stops within a chunk or two rather than at the end of the clip.
The grain itself is gaussian noise, blurred with a kernel whose size scales by min(height, width) / 1024 - that's the resolution-independent bit. Then the noise is weighted by luminance according to response:
- film (the default) is strongest in shadows and midtones and fades to nothing in the highlights, like negative stock.
- classic humps around the midtones, the old behaviour.
- sensor is shot noise done properly: applied in linear light, growing with the square root of the exposure, with blue as the noisiest channel and red a little hotter than green. It reads like a phone at dusk rather than like film.
color_grain blends between one mono noise field and per-channel speckle, and grain_size is specified at a 1024-pixel reference so the same value looks the same at any resolution.
Note that this node's defaults are more conservative than the still-image version: strength 0.25 here versus 0.5 there, and response defaults to film here versus classic there. Don't copy numbers between the two and expect the same result.
The inputs that matter
- images - the clip's frames. A batch, straight out of the sampler, a video loader or a VAE Decode.
- strength - 0.15 is barely there, 0.25 is light film texture, 0.4 is clearly grainy. The tooltip's scale is honest; use it.
- grain_motion -
changingfor new grain every frame,fixedfor the same grain on all of them.fixedlooks like a dirty screen the second anything moves, which is either the effect you want or a mistake you'll notice immediately. - response - where in the tonal range the grain lives.
filmunless you're chasing a phone-camera look. - frame_offset (optional) - the first frame number of this chunk. If you render a long clip in parts, set this per part so the grain sequence continues instead of repeating.
What comes out
A single images output, same shape and dtype as what went in. Wire it into your video save node - VHS Video Combine, Save WEBM, SaveAnimatedWEBP - or into a second pass if you're chaining grades.
Install
ComfyUI Manager → ComfyUI LC123 Nodes, or:
cd ComfyUI/custom_nodes
git clone https://github.com/lonecatone23/ComfyUI_LC123_nodes
Nothing to pip install: the repo ships no requirements.txt and no install script, and the node only imports torch plus ComfyUI's own modules. Restart, confirm the [LC123] + console lines, Ctrl+F5 if you were already running the pack.
Common issues
The grain vanished after encoding. Encode with more bitrate. grain_size above about 1.2 survives h264 CRF 18 comfortably; below 1.0 you're fighting the codec and you'll need to raise both grain size and bitrate. If the file got bigger and stayed smooth, that's the codec's deblocking eating fine noise.
RAM spikes on long clips. The node allocates a full second copy of the whole clip as output before it starts. A 300-frame 1080p batch is real memory on both sides. Chunk the clip, or use frame_offset to stitch the grain across parts.
Cancel takes a few seconds. It checks between chunks of eight frames, not between frames. On a big clip that's a visible delay, not a freeze.
Strength 0 does nothing. Correct - it returns the input untouched, no error, no copy.
It reads as digital noise, not film. Drop color_grain toward 0 and response to film, then raise grain_size slightly. Colour speckle plus small grain is the phone-sensor signature.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | The clip's frames. | |
| strength | FLOAT | 0.250–1 | 0.15 = barely there, 0.25 = light film texture, 0.4 = clearly grainy. |
| grain_size | FLOAT | 1.200.7–4 | Grain size at 1024 px; scales with the resolution. |
| color_grain | FLOAT | 0.150–1 | 0 = mono grain, 1 = colour speckle. |
| response | COMBO | film | film: shadows and midtones, gone in the highlights. classic: mostly midtones. sensor: phone / digital camera noise. |
| grain_motion | COMBO | changing | changing: new grain every frame, like real film. fixed: the same grain on every frame (reads as a dirty screen on motion). |
| seed | INT | 86753090–4294967295 | — |
| frame_offsetopt | INT | 00–1000000 | For a video made in parts: the first frame number of this part, so the grain carries on instead of repeating. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | — |