NVIDIA Maxine Eye Contact (Local)
Gaze correction that stays on your own GPU
- video
- video
The effect you already recognise
If you've watched a streamer whose eyes never drift off the lens for two straight hours, you've seen NVIDIA Broadcast's Eye Contact. It's the one feature in that app nobody can un-see once they know it: the iris gets nudged onto the camera axis, frame after frame, and the person reads as intensely attentive.
This node is the same idea pointed at recorded footage inside a ComfyUI graph. It drives NVIDIA's Maxine Eye Contact NIM - the server-grade container, not the consumer Broadcast app - on your own machine over a localhost gRPC socket. Nothing gets uploaded anywhere. So you can batch a stack of talking-head clips through a normal ComfyUI queue instead of babysitting a webcam utility at your desk.
Where it earns its place is the end of a talking-head pipeline. Once you've animated a portrait with InfiniteTalk or retargeted one with LivePortrait, the eyes are whatever the driving performance did - often slightly off-axis, which is the detail that trips a viewer's "this is fake" reflex. The community alternative is nudging LivePortrait's gaze values per frame, and those people complain about its working resolution, not the idea (audio-generation.md has the map).
Fair warning: this is early-beta pack 0.2.1 from aifirst-studio, an independent MIT integration rather than an NVIDIA product, and no weights ship with it. Its community footprint is near zero - a Nov 2025 r/comfyui thread asking whether this effect is doable in ComfyUI for dynamic video got zero replies. You'll be an early adopter.
What actually happens on Run
The graph is three nodes: Load Video → NVIDIA Maxine Eye Contact (Local) → Save Video. Native VIDEO in, native VIDEO out.
Under the hood it does more than one socket suggests. The video is written to ComfyUI's temp dir at original resolution and frame rate - stream copied untouched if it's already constant-frame-rate yuv420p H.264, re-encoded at CRF 1 if not - then pushed to the container over gRPC in 64 KB chunks. The corrected MP4 streams back and gets rejected if width, height, frame count or frame rate moved. Your original audio is muxed back on.
Two consequences. There's no downscaling and no resolution cap - the node sends your real dimensions and it's the GPU's problem if that's too much. And NIM wants constant-frame-rate 8-bit SDR H.264: HDR is rejected outright (tone-map upstream), 10-bit becomes 8-bit, odd dimensions are padded then restored via an H.264 4:4:4 encode some players refuse. During inference the in-node bar goes indeterminate, because gRPC exposes no per-frame progress. That's not a hang.
The controls that matter
video is the only required socket. Of the booleans, eye_size_sensitivity (2–6, default 3) is the one beginners misread: it's the blending area of the corrected eye region - not eye size, not strength. Higher means a bigger patch of face gets blended, which is where seams show. detect_closed_eyes should stay on; it lets the model respect blinks without forcing a closed eye open. natural_lookaway is the anti-stare - occasional drifts up to 5° so the result doesn't read as hypnotised.
lossless_output (default on) keeps files big and clean; turn it off and lossy_bitrate_mbps (default 40) takes over. Leave temporal_filtering on for recorded footage - frame-independent correction jitters, and that's the giveaway. preserve_iris_color is the experimental one: a CPU MediaPipe pass matching iris colour from your source into the corrected frame, because Maxine's own config has no colour-preservation knob. Same substitution you see elsewhere in the ecosystem - MediaPipe is Apache 2.0, which is why people reach for it over InsightFace when licences matter (identity-preservation.md). It helps, it costs CPU time and an extra encode, and it skips eyes it isn't confident about.
One thing the README doesn't say and the source does: enable_multi_person is hardcoded off. Single face.
Installing it (the honest version)
cd ComfyUI/custom_nodes
git clone https://github.com/aifirst-studio/ComfyUI-Maxine-EyeContact
Manager will find it as Maxine Eye Contact (Local), registry ID maxine-eye-contact. But Manager installs the node and one small FFmpeg helper (imageio-ffmpeg) and stops. Everything that makes it actually run is manual, and this is where people get burned:
# ComfyUI's own Python - just the ffmpeg helper
python -m pip install -r requirements.txt
# Debian/Ubuntu headless hosts need these for the iris-colour step
sudo apt-get install -y --no-install-recommends libegl1 libgles2
# Isolated client, needs Python 3.11 or 3.12 specifically
python3 install_client.py
# Then the NIM container, with an NGC API key ready
python3 service/configure_ngc.py
python3 service/start_service.py
install_client.py builds a runtime/ with its own interpreter plus a pinned 3.8 MB MediaPipe landmark model, and generates config.json. Never copy config.example.json over it - its Python path is a placeholder. The service side wants Docker Compose, the NVIDIA Container Toolkit, a GPU with NVENC/NVDEC, and NGC credentials; the image is pinned by digest at NIM 1.5.0.
Where it bites
The node unloads ComfyUI's models and empties cache before a cold service start, so plan your queue - Maxine and your checkpoint fight over the same VRAM. It only talks to 127.0.0.1/localhost by design; for another Linux host, SSH-forward the gRPC port instead of editing the config. The first inference after a container start can burn minutes on warm-up. Test a ten-second clip before committing to a batch: the author's own verification is 1440×2560 at 30 fps on a 3090, with real 4K inference explicitly untested.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| video | VIDEO | Original input resolution is preserved, including 4K. No resize limit is applied by this node. Upstream resizing cannot be undone. SDR only; the service/GPU must support the source size. | |
| natural_lookaway | BOOLEAN | false | Occasionally adds small eye movements away from the camera (up to 5 degrees) to soften a fixed stare. Off keeps continuous correction within NVIDIA's head/gaze range. |
| detect_closed_eyes | BOOLEAN | true | Detects blinking, closed eyes and occlusion so correction can respect them. Recommended on for gaze redirection. Does not force closed eyes open. |
| eye_size_sensitivity | INT | 32–6 | Controls the eye-region area used to blend corrected eyes into the face. 2 = smaller area, 6 = larger area; start with 3. This is NOT eye enlargement or correction strength. Compare a short clip for blend edges/artifacts. |
| lossless_output | BOOLEAN | true | Lossless H.264 encoding of Maxine's corrected frames; larger files. Off uses the bitrate below. Iris-colour matching adds a player-compatible CRF 1 encode, so its final output is not mathematically lossless. Does not restore input compression, HDR or 10-bit detail. Resolution is unchanged in either mode. |
| temporal_filteringopt | BOOLEAN | true | Temporally filters eye-contact landmarks across consecutive frames to reduce jitter/flicker. On sends NVIDIA's default 0xffffffff; off sends 0. Usually leave on for recorded video. |
| input_qualityopt | COMBO | Maximum (CRF 1) | Used only when input conversion is necessary. Maximum minimizes additional compression at the original resolution. Compatible H.264 is copied directly. Neither conversion mode is mathematically lossless. |
| lossy_bitrate_mbpsopt | INT | 401–200 | Target output bitrate in Mbit/s, only when lossless_output is off. Higher values can retain more detail in large or moving images and produce larger files. |
| preserve_iris_coloropt | BOOLEAN | true | Experimental CPU postprocess: match detected iris colour to the source while retaining redirected gaze. Closed, tiny or uncertain eyes are skipped. Adds a very-high-quality, player-compatible H.264 High/YUV420 encode at CRF 1 and the original resolution; not mathematically lossless. Cannot guarantee identity or exact colour in every frame. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| video | VIDEO | — |