Face Performance Eye Motion
Synthesize believable blinks and gaze from an audio track
- eye_frames_json
- summary
The thing that gives animated faces away is usually the eyes. Perfectly regular blinks, zero gaze drift, eyelids that never squint - no real human does that. MKRFacePerformanceEyeMotion is the node in MKRShift Nodes' face-performance lane that takes an audio-feature frame stream and synthesizes the eye behavior that makes a face read as alive: irregular blinks, subtle gaze wander, and optional squints.
This is a data node, not a rendering node. It works on JSON arrays of per-frame features - articulation, energy, pitch_slope, pause, prosody_valley, phrase_boundary, smile and friends - and hands back JSON describing what the eyes should do on each frame. Whatever consumes it (a rig, a mesh, a video-diffusion conditioning pass) does the actual drawing. The point of the node is that the timing is human, not mechanical.
How it works
The core is a small procedural synthesizer in the pack's lib/face_performance/ (eye_motion_synth.py). It models blink timing as a stochastic process: you give it a mean_blink_interval_s (default 4.2 seconds - humans blink roughly every 2–10s) and a blink_interval_jitter_s (default 1.6s) that spreads the intervals so you don't get metronome blinks. The audio features bias things further - pauses and phrase boundaries nudge blink timing, energy shapes intensity - so the eyes feel coupled to the performance rather than bolted on. It also generates gaze_yaw/gaze_pitch drift and brow/cheek motion.
Inputs that actually matter:
audio_frames_json- the required feature stream. Frame count of the output matches this exactly, so it has to be aligned to your base performance.seed- default 7, and-1means "surprise me." Set a fixed seed for reproducible blink patterns across runs.target_fps- blink timing and gaze drift are computed against this; keep it matching your base frames.include_squint- adds asquintchannel to every frame. On by default? No - it's off. Turn it on when your character needs expressive, not just awake, eyes.
settings_json is the advanced escape hatch: pass overrides for any EyeMotionSynthConfig field. Leave it {} until you know you need it.
Outputs are just two: eye_frames_json (array with blink_l, blink_r, gaze_yaw, gaze_pitch, eyelid openness, brow motion, cheek raise, expr_confidence) and a summary string. The author's docs suggest running this before MKRFacePerformanceLipRefine, so eye motion can inform the mouth ROI stabilization - the pack's own recommended order.
Installing
Standard pack route:
cd ComfyUI/custom_nodes
git clone https://github.com/criskb/MKRShift_Nodes
Restart ComfyUI, or ComfyUI Manager search "MKRShift_Nodes". Pure Python, no new dependencies.
The honest caveats
This node is squarely aimed at people doing structured performance data (facial rigging, blendshape pipelines, retargeting) - not at the typical img2img or video-diffusion user. If you're not already feeding JSON feature frames around your workflow, this node has nothing to plug into, and that's fine; it's a component of an experimental pipeline, not a standalone toy. There's essentially no community chatter about it yet - the pack is young and this lane is its most niche corner. Expect to read the source if you want to tune settings_json, because there are no presets and the config surface is the code.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| audio_frames_json | STRING | [] | — |
| seed | INT | 7-1–2147483647 | — |
| target_fps | INT | 601–240 | — |
| mean_blink_interval_s | FLOAT | 4.200.5–30 | — |
| blink_interval_jitter_s | FLOAT | 1.600–10 | — |
| include_squint | BOOLEAN | false | — |
| settings_json | STRING | {} | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| eye_frames_json | STRING | — |
| summary | STRING | — |