PhyFPS Detector
Your AI video isn't in slow motion — it's playing at the wrong FPS
- model
- images
- phyfps
- info
Ever generated a Wan or LTX clip where people move like they're wading through treacle - and then another where a car appears to teleport? That's not a prompt problem. That's the video generator lying about time.
Video models are trained on footage shot at wildly different real-world speeds and then normalized to a standard container frame rate. The result is what the Pulse-of-Motion paper (arXiv 2603.14375) calls chronometric hallucination: motion that isn't anchored to any real time scale. A clip labeled 24fps might actually depict motion that should play at ~36fps to look physically natural. PhyFPS Detector is the node that measures that. Feed it your frames, it tells you the Physical FPS (PhyFPS) - the speed the video should actually play at, inferred from the pixel dynamics alone, not the file's metadata.
It's an analysis node, not a fixer. It hands you a number; you then re-time the video (re-export at PhyFPS, or adjust your Wan playback speed) so the result looks like real time instead of slow motion. That's exactly how it shows up in the wild - the All-in-Wan community workflows that bundled Pulse-of-Motion ship it as "detect at what framerate this video should be played to look real-time."
How it works
The model behind it (Visual Chronometer) runs a frozen 3D-convolution encoder over 30-frame clips resized to 216×216 and normalized to [-1, 1], then uses a cross-attention "probe" token to regress a single log(FPS) value. The detector exp()s that back into FPS, so you get a real number out.
The node slides a 30-frame window across your whole frame batch. That's the part you control. If you feed it fewer than 30 frames it pads by repeating the last frame - fine for a short clip, meaningless if you hand it one still. The output is the mean of all window predictions.
The inputs that matter
- model - the
PULSE_OF_MOTION_MODELobject from Pulse-of-Motion Model Loader. Wire that first. - images - an
IMAGEbatch (B, H, W, C) of your video frames. Any node that outputs a frame sequence feeds it: a video loader, VHS's frame extraction, even the frames of a Wan/LTX generation. - stride - how many frames the window shifts between segments (default 4). Lower = more overlapping segments = a smoother, more accurate estimate but slower. For a 30–40 frame clip there's only one segment anyway, so stride only matters on longer videos.
The outputs
- phyfps (
FLOAT) - the average predicted physical frame rate. That's the number you re-time to. - info (
STRING) - a plain-text report with the per-segment breakdown, plus mean/median/std across segments. Reads like a lab report; handy for debugging whether one section of the video is dragging the average.
Two caveats before you trust the number: the released checkpoint is trained for the 10–60 FPS range, so predictions outside that are extrapolation, and upstream describes it as a demo/leaderboard model rather than their strongest. It's a decent estimate, not an oracle.
Install
Same pack as the loader - clone it into custom_nodes:
cd ComfyUI/custom_nodes
git clone https://github.com/DiXiaoO/ComfyUI-Pulse-of-Motion.git
Then grab the model so the loader has something to work with:
# vc_common_10_60fps.ckpt from https://huggingface.co/xiangbog/Visual_Chronometer
mkdir -p ComfyUI/models/visual_chronometer
# put the .ckpt in there, restart ComfyUI
Install via ComfyUI Manager (search "ComfyUI-Pulse-of-Motion") or add the few missing deps yourself - see the loader article for why you probably shouldn't run the full requirements.txt.
Troubleshooting
- Output is nonsense on short clips. Fewer than 30 frames gets padded by frame repetition. Feed a real sequence.
- Errors about a missing model aren't this node's fault - the loader dropdown will literally say
[No models found - download from GitHub]. Fix the model file, not the wiring. - Your widget set doesn't quite match this doc. The repo is honest about being vibe-coded, and it carries two divergent copies of these nodes in the tree. If your ComfyUI shows different inputs than the README, that's the pack being messy, not you. Work with what your UI shows and keep the same model connection pattern.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| model | PULSE_OF_MOTION_MODEL | Model info from Pulse-of-Motion Model Loader node | |
| images | IMAGE | Batch of images representing video frames (B, H, W, C) | |
| stride | INT | 4 | Sliding window stride - how many frames to shift between clips |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| phyfps | FLOAT | — |
| info | STRING | — |