Nodes/IAMCCS-nodes/IAMCCS-CinePostEfx-v2
ComfyUI Node

IAMCCS-CinePostEfx-v2

Film grain, tape damage and broadcast rot in one node

By IAMCCS·Created 11 months ago·Updated about 17 hours ago· 113
IAMCCS-CinePostEfx-v2
  • images
  • grain_plate
  • images
  • grain_map
  • report
enginefilm_emulsion
preset35mm_fine_negative
input_transfersrgb
blend_methodlog_density
strength0.100
grain_size_4k_px1.00
softness0.18
roughness0.24
complexity3
temporal_correlation0.00
chroma_amount0.055
shadow_response0.62
midtone_response1.00
highlight_response0.38
red_response1.00
green_response0.96
blue_response1.06
seed1
frame_start0
horizontal_instability0.00
chroma_spill0.00
luma_noise0.00
chroma_noise0.00
color_drift0.00
edge_echo0.00
line_dropout0.00
scanline_strength0.00
field_interlace0.00
vertical_roll0.00
highlight_glow0.00
head_switch_distortion0.00
luma_trail0.00
chroma_delay0.00
analog_mix1.00
tracking_error0.00
tape_warp0.00
ghost_echo0.00
signal_saturation1.00
black_lift0.00
chroma_phase_noise0.00
chroma_loss0.00
grain_profilenegative_stock
texture_microcontrast0.18
analog_color_lookneutral
disturbance_amount1.00
lens_presetlens_none
lens_master0.00
lens_distortion0.00
lens_edge_stretch0.00
lens_anamorphic_width0.00
lens_zoom1.00
lens_keystone_x0.00
lens_keystone_y0.00
lens_tilt_angle0
lens_focus_position0.00
lens_tilt_blur0.00
lens_chromatic_aberration0.00
lens_vignette0.00
settings_json
horizontal_instability_randomness0.00
tracking_randomness0.00
tape_warp_randomness0.00
color_drift_randomness0.00
scanline_randomness0.00
interlace_randomness0.00
vertical_roll_randomness0.00
head_switch_randomness0.00

Grain is the cheapest de-AI pass there is: a gamma node, a colour match and a grain pass will do in milliseconds what people keep burning diffusion passes on. It is also the pass everyone gets wrong by overlaying the same noise texture on every frame, which reads as a filter rather than as film.

IAMCCS-CinePostEfx-v2 is the ambitious version of that idea. It renders stochastic grain per frame in linear light, scales it to your actual resolution, makes it respond to tone, and then - if you want - drags the whole frame through a tape deck or a broadcast chain on the way out.

Five engines, one node

The engine dropdown is the fork in the road:

  • film_emulsion (default) - density-aware grain from a stock profile
  • digital_sensor - sensor noise rather than emulsion
  • scanned_grain_plate - uses a real grain plate you supply
  • analog_tape - the video/tape branch
  • broadcast_signal - the signal-path branch

preset (34 options) picks the stock: 65mm_clean_scan, 35mm_fine_negative, 35mm_high_speed, 16mm_documentary, 8mm_expression, soft_cassette_memory, worn_video_copy, family_camcorder_1988, pristine_tape_master, and so on. Presets set the whole look; the individual fields override on top.

The grain core is a short list of knobs you actually touch. strength defaults to a very restrained 0.1. grain_size_4k_px is resolution-aware - it is expressed at 4K and scaled, so the same number looks right on a 720p preview and a 4K master. grain_profile picks the emulsion character (negative_stock, high_speed_negative, reversal_stock, print_stock, modern_fine). complexity (1–4) buys finer structure at more compute. softness and roughness shape it. temporal_correlation is the one that matters for video: at 0 grain is completely re-rolled every frame, which is physically right for fine grain and a flicker disaster on a slow shot. Nudge it up to stabilise.

Tone weights do the thing that separates real film from a noise overlay: shadow_response (default 0.62), midtone_response (1.0) and highlight_response (0.38) decide where grain lives. Highlights at 0.38 is why bright skies don't turn into sandpaper. Per-channel red_response / green_response / blue_response are the chroma asymmetry, with chroma_amount (0.055) on top.

input_transfer (srgb, rec709, linear) tells it what your frames actually are, and blend_method (log_density default, density_exposure, linear_additive, soft_light_luma) decides how the field is combined. If your grain looks chalky or washes the mids, that pairing is what you change, not strength.

The analogue half

Everything from horizontal_instability downward is the tape/broadcast toolkit, and all of it sits at zero by default: tracking_error, tape_warp, ghost_echo, line_dropout, scanline_strength, field_interlace, vertical_roll, head_switch_distortion, luma_trail, chroma_delay, chroma_spill, color_drift, edge_echo, highlight_glow, plus signal_saturation, black_lift, chroma_loss, chroma_phase_noise and an analog_mix master. analog_color_look gives you 14 graded looks (consumer_color, warm_camcorder, late_night_blue, archival_amber, crt_broadcast, night_vision_green…).

Only eight fields have their own *_randomness partner - the cyclic signal-motion families: horizontal_instability_randomness, tracking_randomness, tape_warp_randomness, color_drift_randomness, scanline_randomness, interlace_randomness, vertical_roll_randomness, head_switch_randomness. That is the difference between a loop that looks like a loop and one that looks like a dying VHS tape. disturbance_amount (default 1.0) scales the intrusion of all of it.

The lens_* group is optical, not analogue: lens_preset (11 options, from fisheye_8mm to anamorphic_2x_cinema) plus manual lens_distortion, lens_tilt_blur, lens_chromatic_aberration, lens_vignette and friends. lens_master at 0 means the preset's own values are used unchanged.

Two fields look scary and aren't. seed plus frame_start make the noise field reproducible: same seed, same frame_start, same grain on the same frames - which is how you get consistent grain across shots cut from separate renders. settings_json is the UI's own snapshot of all of the above; leave it alone and let the frontend write it.

Outputs

images is your batch. grain_map is a MASK of the grain field itself - wire it to a PreviewImage when you want to see what is being added instead of guessing. report is a text summary naming the engine, preset, blend, active randomness count and lens preset; when a render looks wrong, that string tells you which settings actually took effect.

Install

Manager → search IAMCCS, or:

cd ComfyUI/custom_nodes
git clone https://github.com/IAMCCS/IAMCCS-nodes.git

Restart. No model downloads, no extra pip packages - it is pure tensor math on your frames. The pack's README quotes ComfyUI ≥ 0.3.0, Python ≥ 3.12, Torch ≥ 2.8 (CUDA 12.6/12.8).

Where people get burned

  • Cranking strength. 0.1 is the default for a reason. This node is doing physical grain, not a "film look" filter; 0.3+ on a 4K master is a different aesthetic.
  • Flicker at temporal_correlation = 0. Fine for stills and high-speed stock, visible on slow camera moves. Raise it before you blame the encoder.
  • Wrong input_transfer. If your frames are linear (a few video pipelines hand you linear EXR-ish data) and you leave it on srgb, the grain sits wrong in the tones.
  • A plate that doesn't match your resolution. scanned_grain_plate wants the optional grain_plate IMAGE input; feed a plate at a very different resolution and the grain will look like a texture overlay again.

The obvious comparison point is the pack's own older grain nodes, and the two legacy IDs IAMCCS_CineFilmGrainPro and IAMCCS_CinePostEfxV2 are aliases of this one - same code, older names, kept so old workflows still load.

CategoryIAMCCS/Cine Nodes/Film Delivery

Inputs (69)

NameTypeDefaultDescription
imagesIMAGE
engineCOMBOfilm_emulsion5 options: film_emulsion, digital_sensor, scanned_grain_plate, analog_tape, broadcast_signal
presetCOMBO35mm_fine_negative34 options: 65mm_clean_scan, 35mm_fine_negative, 35mm_high_speed, 16mm_documentary, 8mm_expression, digital_cinema_sensor, +28
input_transferCOMBOsrgb3 options: srgb, rec709, linear
blend_methodCOMBOlog_density4 options: log_density, density_exposure, linear_additive, soft_light_luma
strengthFLOAT0.1000–1
grain_size_4k_pxFLOAT1.000.35–6
softnessFLOAT0.180–1
roughnessFLOAT0.240–1
complexityINT31–4
temporal_correlationFLOAT0.000–0.95
chroma_amountFLOAT0.0550–0.5
shadow_responseFLOAT0.620–2
midtone_responseFLOAT1.000–2
highlight_responseFLOAT0.380–2
red_responseFLOAT1.000.5–1.5
green_responseFLOAT0.960.5–1.5
blue_responseFLOAT1.060.5–1.5
seedINT10–9223372036854776000
frame_startINT00–2147483647
horizontal_instabilityFLOAT0.000–1
chroma_spillFLOAT0.000–1
luma_noiseFLOAT0.000–1
chroma_noiseFLOAT0.000–1
color_driftFLOAT0.000–1
edge_echoFLOAT0.000–1
line_dropoutFLOAT0.000–1
scanline_strengthFLOAT0.000–1
field_interlaceFLOAT0.000–1
vertical_rollFLOAT0.000–1
highlight_glowFLOAT0.000–1
head_switch_distortionFLOAT0.000–1
luma_trailFLOAT0.000–1
chroma_delayFLOAT0.000–1
analog_mixFLOAT1.000–1
tracking_errorFLOAT0.000–1
tape_warpFLOAT0.000–1
ghost_echoFLOAT0.000–1
signal_saturationFLOAT1.000–2
black_liftFLOAT0.000–1
chroma_phase_noiseFLOAT0.000–1
chroma_lossFLOAT0.000–1
grain_profileCOMBOnegative_stock5 options: modern_fine, negative_stock, high_speed_negative, reversal_stock, print_stock
texture_microcontrastFLOAT0.180–1
analog_color_lookCOMBOneutral14 options: neutral, consumer_color, warm_camcorder, cool_camcorder, tungsten_home_video, fluorescent_cctv, +8
disturbance_amountFLOAT1.000–2
lens_presetCOMBOlens_none11 options: lens_none, wide_angle_14mm, ultra_wide_10mm, fisheye_8mm, anamorphic_1_33x, anamorphic_2x_cinema, +5
lens_masterFLOAT0.000–2
lens_distortionFLOAT0.00-1–1
lens_edge_stretchFLOAT0.00-1–1
lens_anamorphic_widthFLOAT0.00-0.5–0.5
lens_zoomFLOAT1.000.7–1.6
lens_keystone_xFLOAT0.00-0.75–0.75
lens_keystone_yFLOAT0.00-0.75–0.75
lens_tilt_angleFLOAT0-90–90
lens_focus_positionFLOAT0.00-1–1
lens_tilt_blurFLOAT0.000–1
lens_chromatic_aberrationFLOAT0.000–1
lens_vignetteFLOAT0.000–1
settings_jsonSTRING
horizontal_instability_randomnessFLOAT0.000–1
tracking_randomnessFLOAT0.000–1
tape_warp_randomnessFLOAT0.000–1
color_drift_randomnessFLOAT0.000–1
scanline_randomnessFLOAT0.000–1
interlace_randomnessFLOAT0.000–1
vertical_roll_randomnessFLOAT0.000–1
head_switch_randomnessFLOAT0.000–1
grain_plateoptIMAGE

Outputs (3)

NameTypeDescription
imagesIMAGE
grain_mapMASK
reportSTRING