ffmpeg -i in.mp4 -vf "mpdecimate,setpts=N/FRAME_RATE/TB" -an -fps_mode cfr -r 60 out.mp4
Drops repeated frames, re-times what is left to an even 60 fps, and leaves the audio out (it would no longer line up). Details below.
Why the frames are there
Recorders such as OBS write a constant frame rate. When the game has not finished drawing a new frame by the next tick, the recorder repeats the last one. The dropped-frames counter stays at zero because every slot got a frame. The file looks like A B C C C D E E F instead of A B C D E F G H I. The longer explanation covers how to confirm that this is what your file has.
What mpdecimate does
mpdecimate compares each frame with the previous kept frame, block by block. If no 8×8 block differs by more than hi, and the number of blocks differing by more than lo is below the fraction frac, the frame is considered a duplicate and dropped. Defaults are hi=768, lo=320, frac=0.33, tuned for telecined film. Games are different: a late-game BTD6 frame has thousands of moving sprites, so when the game really did move, almost every block changes, and when it stalled, almost none do. The defaults usually work; the knobs below are for when they don't.
The full workflow
- Count first.
ffmpeg -i in.mp4 -vf mpdecimate -f null -prints how many frames survive. Compare that to the total frame count (ffprobe -count_frames -select_streams v -show_entries stream=nb_read_frames in.mp4). If 70% of frames are dropped, you have a lag recording; if 2% are, the stutter is something else. - Drop and re-time. Dropping frames alone leaves gaps in the timestamps, and most players show that as a stutter.
setpts=N/FRAME_RATE/TBre-numbers the kept frames evenly.-fps_mode cfr -r 60(older ffmpeg:-vsync cfr) writes a constant frame rate container. Result: the game appears to run smoothly at the pace of the frames that were actually drawn. - Choose a pace. The command above plays kept frames at 60 per second, which is a fast-forward if the game was really drawing 20. For real-time pacing instead, keep the original timestamps:
-vf mpdecimate -fps_mode vfrand skip the setpts. For a specific speed-up, usesetpts=PTS/3after the decimate instead of the N-based form. - Deal with audio. Once frames are removed the video is shorter than the audio, so the two cannot stay in sync. Use
-anand lay music or commentary back in your editor. If you need the original audio under a real-time (vfr) cut, it will drift at every removed stall; there is no clean fix for that. - Check the output. Step through frame by frame in a player and watch the game clock. If it never freezes, the pass worked.
Tuning for games
| Symptom | Change | Why |
|---|---|---|
| Real frames dropped (motion skips) | Lower hi and lo, e.g. mpdecimate=hi=200:lo=100 | Small movements (one bloon) fell under the threshold |
| Stalls still present | Raise frac, e.g. frac=0.5 | Noise, webcam or a HUD timer changed a few blocks every frame |
| Webcam or chat overlay keeps every frame | crop to the game area before decimating, then keep the full frames with a second pass | mpdecimate sees the overlay moving even when the game is frozen |
| Pause menu kept | Cut by time range after inspecting, or use a tool that classifies menus | A menu is a different image, not a repeat; mpdecimate keeps it |
That last row is the limit of the approach. Duplicates are a repetition problem and mpdecimate solves it; pause menus and loading screens are a content problem, and need either a manual cut list or a classifier trained on the game. Cutting dead time from gameplay video covers both.
If you'd rather not run ffmpeg
LagCut does the duplicate pass in the browser with a per-game frame model (it compares only the map viewport in They Are Billions, so tooltips and webcams don't count), detects BTD6 pause menus, shows you the cut list on a timeline to accept or reject, and exports a constant-frame-rate MP4 at 1× or 3×. Nothing is uploaded. It processes the first 30 seconds of a clip, so trim first; for a two-hour VOD, ffmpeg is still the right tool.
Questions people ask
Does mpdecimate remove pause menus?
No. It removes frames that repeat the previous frame. A pause menu is a new image that then repeats, so the first menu frame is kept and the rest dropped, leaving a single menu frame in the output. Cut menus by time range or with a classifier.
Why does my output stutter after mpdecimate?
Because the kept frames still carry their original timestamps, with gaps where frames were removed. Add setpts=N/FRAME_RATE/TB and write constant frame rate (-fps_mode cfr), or accept variable frame rate for real-time pacing.
Can I keep the audio?
Not in sync. Removing video frames shortens the video; the audio is unchanged. Drop it with -an and re-add music or commentary in an editor.
What is a good threshold for games?
Start with the defaults. If real movement is being dropped, lower hi and lo (hi=200:lo=100). If stalls survive because of a HUD timer or webcam, raise frac to 0.5 or crop to the game area.