Nodes/DJZ-Nodes/Film Grain Effect V2 (video)
ComfyUI Node

Film Grain Effect V2 (video)

Film grain that breathes — and lets you write the math

By MushroomFleet·Created 2 years ago·Updated 5 months ago· 78
Film Grain Effect V2 (video)
  • images
  • IMAGE
presetsubtle
expression_input0.08 * normal(0.5, 0.15) * (1 + 0.2 * sin(t/25))
base_intensity0.10
time_scale1.0
noise_scale0.20
seed0

Static grain is easy - plenty of nodes add a fixed noise layer and call it a day. Film grain that moves like real celluloid, pulsing in intensity over time, is harder. FilmGrainEffect_v2 is the version of DJZ-Nodes' grain node that gives you an actual expression editor, so you can control exactly how the grain swells and recedes across the frames. If you're finishing a video and it feels a touch too clean, this is the polish node.

How it works

For each frame the node computes a time-based intensity from a mathematical expression where t is the frame number times time_scale, then multiplies it by base_intensity, and adds gaussian noise scaled by noise_scale. The clever part is that the expression can call a small safe function set - sin, cos, exp, normal(mu, sigma), uniform(a, b) - so the grain isn't just "on" but breathing according to your formula. It's evaluated with a restricted sandbox (__builtins__ emptied), so you can't blow up your machine with it, and a bad expression just returns 0.0 with a console message rather than crashing.

Six presets ship baked in: subtle, vintage, unstable_signal, dip, ebb, and flow. unstable_signal is the one that looks like a transmission problem - it combines a fast oscillation with a decaying burst. dip does a gaussian "dip" every ~60 frames, great for that brief darkening between reel changes.

The inputs

  • preset - pick one, or custom to use your own expression.
  • expression_input - the fun part. Default is 0.08 * normal(0.5, 0.15) * (1 + 0.2 * sin(t/25)). Swap in exp(-t/40) for grain that fades in over a clip, or uniform(0.2, 0.8) for erratic popping. t starts at frame 0.
  • base_intensity - overall strength, 0-1. Start at 0.05-0.1; above 0.2 you're past "grain" and into "snowstorm."
  • time_scale - speeds up or slows down the temporal modulation. Lower = lazier breathing.
  • noise_scale - the size/graininess of the noise itself.
  • seed - the one thing v1 got wrong that v2 fixes: reproducible grain. Same seed, same pattern, every time.

Output is the same-resolution IMAGE batch. Feed it straight into a save node or an encoder.

Installing

It lives in the DJZ-Nodes pack (MushroomFleet / Drift Johnson). Grab the pack once:

cd ComfyUI/custom_nodes
git clone https://github.com/MushroomFleet/DJZ-Nodes
cd DJZ-Nodes
pip install -r requirements.txt

Or "DJZ-Nodes" via ComfyUI Manager. The grain nodes are pure numpy/PIL, so no special deps beyond the pack's usuals.

Where people get burned

If expression_input errors, you get no intensity (a flat 0.0) - silent-ish, only visible in the console. And remember the expression is evaluated per frame with fresh randomness from normal/uniform, so the pattern is reproducible only with a fixed seed. Want a quick taste? Leave preset on subtle, set base_intensity to 0.08, and it's a ten-second upgrade to any video.

Categoryimage/effects

Inputs (7)

NameTypeDefaultDescription
imagesIMAGE
presetCOMBOsubtle7 options: custom, subtle, vintage, unstable_signal, dip, ebb, +1
expression_inputSTRING0.08 * normal(0.5, 0.15) * (1 + 0.2 * sin(t/25))
base_intensityFLOAT0.100–1
time_scaleFLOAT1.00.1–10
noise_scaleFLOAT0.200–1
seedINT00–2147483647

Outputs (1)

NameTypeDescription
IMAGEIMAGE