⚡ Latent Motion Sharpener
Sharpen the moving pixels, leave the static background alone
- latent
- latent
Video upscaling in latent space has a funny failure mode: the moving parts end up soft and the static parts stay crisp. That's because temporal interpolation and resampling smear whatever is actually moving, while stationary regions just get re-sampled cleanly. A global sharpener fixes the smeared motion but also hammers the already-fine background into that overcrisp, crunchy look nobody wants. LatentMotionSharpener's whole premise is that you don't want a global sharpener at all.
It's a motion-adaptive spatial sharpener: an unsharp-mask pass whose strength is scaled per pixel region by how much inter-frame motion that region has. Areas with no motion get almost no sharpening; areas that are actively moving get a strong boost. The result reads as "crisper video" rather than "an unsharp mask was applied to everything."
How it decides
The node computes per-frame motion maps from the frame-to-frame difference, normalizes them, and maps that onto sharpening strength between two bounds:
base_sharpen(0.08) - the sharpening applied everywhere, including static regions. Keep this small; it's the floor.motion_sharpen(0.55) - the additional sharpening applied where motion is detected. This is your main dial.motion_thresh(0.04) - below this normalized motion level, regions are treated as static and only getbase_sharpen.temporal_smooth_mask(True) - blurs the motion mask over time (a 3-tap temporal filter) so the sharpening strength doesn't flicker frame-to-frame as the motion map jitters. Leave it on unless you see it smearing a fast-moving edge.
Under the hood it's an unsharp mask in latent space: result = flat + strength * high_freq, where high_freq is the difference from a 3×3 Gaussian-ish blur. Working in latent space matters here - you're sharpening the model's own representation before decode, which avoids the ring-artifacts that aggressive pixel-space sharpening produces.
Where it goes
The pack's recommended fast-motion pipeline puts it before the temporal upsampler:
KSampler @ 24fps → LatentMotionSharpener → LatentTemporalUpsampler → ...
Sharpening before upsampling makes sense: the upsampler interpolates the already-crisp motion, so you get clean edges for the Hermite velocity field to follow. If you run it after upsampling instead, you're sharpening interpolated mush - it helps, but you're chasing your tail.
It's a transparent, cheap, no-training latent op. Input is a LATENT, output is a LATENT - chain it anywhere in the temporal processing chain. Install is the pack-wide clone (10S_Nodes into custom_nodes, restart, or ComfyUI Manager → "10S-Comfy-nodes"); no extra deps, LTX 5D latent layout assumed. If your 24→30fps upscale looks soft exactly where things move, this is the node that's actually aimed at that symptom.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| latent | LATENT | — | |
| base_sharpenopt | FLOAT | 0.080–1 | — |
| motion_sharpenopt | FLOAT | 0.550–2 | — |
| motion_threshopt | FLOAT | 0.040–0.5 | — |
| temporal_smooth_maskopt | BOOLEAN | true | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| latent | LATENT | — |