Enhanced Animation Timing Processor
The trick to making 2D animation hold still in video models
- images
- processed_frames
- duplicate_mask
- timing_report
- removal_indices
Traditional 2D animation is mostly holds: a drawing sits on screen for three, six, twelve frames with nothing changing. Feed that into a video model and it does what video models do - it makes the held frames drift, breathe, morph, shimmer. The Enhanced Animation Timing Processor exists for this exact problem. It detects the duplicate frames that make an animation a hold, turns them into neutral gray frames so the video model has nothing to invent, and then - this is the clever part - pads and aligns everything so keyframes land on clean multiples of 4 for glitch-free generation.
Here's the workflow it's built for: you generate a video from an animation sequence, the video model renders only the "real" keyframes cleanly, and the gray holds become placeholders that come back out. Wire this node's removal_indices output into the companion Animation Frame Remover and you strip the padding after generation, returning to the original frame count with your generated content preserved. The two nodes are a matched pair; the processor is the one that does the thinking.
How the detection works
similarity_method picks between hybrid (default), ssim, histogram, and perceptual. SSIM is the structural-similarity comparison you'd expect, histogram compares color distributions, perceptual runs edge-based Sobel filtering to compare structure, and hybrid blends them. similarity_threshold (default 0.85) decides how similar two frames must be to count as a duplicate run, with motion_tolerance absorbing tiny movements so a held pose with a millimeter of jitter still registers as a hold. Then the gray replacement frames get rendered in one of three styles - solid_gray, desaturated, or dimmed - at gray_intensity.
The defaults are tuned to be safe rather than aggressive: preserve_first and preserve_last keep the first and last frame of each duplicate sequence untouched, preserve_global_first/preserve_global_last protect the batch endpoints, and min_sequence_length (default 2) stops a single pair of frames from being treated as a hold. When holds are too short for clean padding, insert_padding inserts extra gray frames automatically.
Keyframe alignment - the knob that matters
align_keyframes (on by default) with alignment_multiple (default 4) is the part that makes or breaks a render. It inserts padding frames so keyframes land on multiples of 4 - frames 0, 4, 8, 12 - because that's the frame grid several video models (Wan included, which wants 4x+1) actually generate cleanly on. If you see glitching or interframe wobble at keyframes, this is the first thing to check: make sure align_keyframes is on and the multiple matches what your model wants.
Inputs, outputs, gotchas
You feed it images (an IMAGE batch) and read processed_frames out for generation. The three diagnostic outputs matter more than they look: duplicate_mask flags which frames are holds, timing_report gives you the readable analysis, and removal_indices is the comma-separated string you feed to Animation Frame Remover. Turn on debug_info if you want the full sequence-by-sequence breakdown in the console.
The main trap is threshold tuning. Set similarity_threshold too low and genuine animation frames get grayed out; too high and holds with any motion pass through undetected. Start at the 0.85 default, check timing_report on a short test clip, and adjust by a hundredth at a time.
Installs as part of TrentNodes - ComfyUI Manager, search "Trent Nodes", or git clone https://github.com/TrentHunter82/TrentNodes into custom_nodes/. No model downloads; this is pure analysis. Pair it with Animation Frame Remover in the same pack and the whole pad → generate → strip loop stays inside the graph.
Inputs (18)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | |
| similarity_method | COMBO | hybrid | Method for calculating frame similarity |
| similarity_threshold | FLOAT | 0.850–1 | How similar frames need to be to count as duplicates |
| motion_tolerance | FLOAT | 0.050–0.3 | Tolerance for small movements/changes |
| gray_style | COMBO | desaturated | How to render the gray replacement frames |
| gray_intensity | FLOAT | 0.50–1 | Intensity of gray effect |
| preserve_first | BOOLEAN | true | Keep the first frame of each duplicate sequence unchanged |
| preserve_last | BOOLEAN | true | Keep the last frame of each duplicate sequence unchanged |
| preserve_global_first | BOOLEAN | true | Keep the very first frame of the entire batch unchanged |
| preserve_global_last | BOOLEAN | true | Keep the very last frame of the entire batch unchanged |
| skip_second | BOOLEAN | false | Skip the 2nd preserved frame from the list of all preserved frames |
| skip_second_to_last | BOOLEAN | false | Skip the 2nd-to-last preserved frame from the list of all preserved frames |
| min_sequence_length | INT | 21–10 | Minimum frames in a sequence to consider as duplicates |
| min_gray_frames | INT | 40–20 | Minimum gray frames needed between preserved frames (will insert extra if needed) |
| insert_padding | BOOLEAN | true | Automatically insert extra gray frames when sequences are too short |
| align_keyframes | BOOLEAN | true | Align keyframes to multiples of alignment_multiple for better video generation quality |
| alignment_multiple | INT | 41–16 | Keyframes will be aligned to multiples of this value (e.g., 4 means frames 0, 4, 8, 12...) |
| debug_infoopt | BOOLEAN | false | Print detailed analysis of duplicate sequences found |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| processed_frames | IMAGE | — |
| duplicate_mask | MASK | — |
| timing_report | STRING | — |
| removal_indices | STRING | — |