The two filters you actually need
FFmpeg can speed up or slow down video, but the math trips people up the first time. The filter you want is setpts. For audio sync, you pair it with atempo. They work independently: you control video speed through presentation timestamps, and audio speed through a time-stretching algorithm. Get both right and the result is clean. Ignore one of them and you end up with video that's out of sync or missing audio entirely.
This guide covers both, plus time-lapse, slow motion, and running speed changes at scale through the RenderIO API.
How setpts works
PTS stands for presentation timestamp. Every video frame has one, which tells the decoder when to display that frame. setpts lets you multiply all of them by a constant.
The math is inverted from what you might expect:
setpts=0.5*PTS→ frames display twice as fast → 2x speed upsetpts=2.0*PTS→ frames display twice as slow → 2x slow motionsetpts=0.25*PTS→ 4x fastersetpts=4.0*PTS→ 4x slower
Think of it this way: if you cut the timestamp in half, the player reaches the end of the video in half the time. That's a speed up. If you double the timestamp, the player takes twice as long. That's slow motion.
Speed up video (video only)
The simplest form strips audio and doubles the speed:
The -an flag disables audio. It's there because if you change video speed without touching audio, they'll be out of sync, and FFmpeg doesn't always warn you about this.
Speed presets:
If you want to slow down instead:
The audio sync problem and how atempo fixes it
Here's the issue: if you use setpts to speed up video and do nothing with audio, the audio keeps playing at normal speed. After a few seconds, the audio and video are noticeably out of sync. By the end of a minute-long clip at 2x speed, the video has finished and the audio is still playing.
The fix is atempo. This is an audio filter that changes playback speed while preserving pitch (unlike asetrate, which changes pitch along with speed).
The atempo value is the inverse of the setpts multiplier:
| Desired speed | setpts value | atempo value |
| 0.5x (half speed) | 2.0 | 0.5 |
| 1.5x | 0.667 | 1.5 |
| 2x | 0.5 | 2.0 |
| 4x | 0.25 | 4.0 (see chaining note below) |
atempo range limit: chain it for extreme speeds
atempo only accepts values between 0.5 and 2.0. If you try atempo=4.0 directly, FFmpeg throws an error. For speeds beyond that range, you chain multiple atempo filters:
The chaining approach works cleanly. Each filter stage gets valid input, and you can go as far as you need.
For a reference sheet of these commands alongside other FFmpeg operations, the FFmpeg cheat sheet has them organized by category.
Time-lapse with FFmpeg
Time-lapse is just extreme speed-up, often 30x to 120x or more. You have two approaches: PTS manipulation or frame rate reduction. They produce different results.
PTS approach (keeps all frames, speeds up playback):
Frame drop approach (keeps every Nth frame, better for large multipliers):
The frame drop approach is more efficient for very long clips. You're not processing every frame, just sampling them. The PTS approach is simpler and works better for moderate speed-ups (10x-30x) where you want smoother motion.
For a proper time-lapse from a long recording (e.g., a 2-hour sunset), the frame drop approach is the right call:
Slow motion from 60fps or 120fps footage
If your camera records at 60fps or 120fps, you can create proper slow motion by conforming it to 24fps or 30fps. This is different from setpts slow motion. You're telling FFmpeg "this footage was shot at 120fps, play it back at 30fps" rather than artificially stretching the timestamps.
This gives you clean slow motion because the actual frames are there. setpts slow motion on 30fps footage will stutter because FFmpeg has to duplicate frames to fill the gaps.
For setpts slow motion on standard footage, you can ask FFmpeg to interpolate frames with minterpolate, though this adds processing time and artifacts on complex scenes:
Batch speed changes via API
Running speed changes locally works fine for one-off tasks. When you're producing content at scale — processing dozens of clips per day across platforms — you want to batch these through an API instead of tying up your machine.
The RenderIO API runs these commands on cloud infrastructure without any local install or server to size. Send the command, wait for processing, download the result. Here's what a speed change looks like:
For batch processing, loop through your clips and fire off one request per video:
For Python batch processing, the pattern is similar but with requests and a polling loop:
If you're building a social media pipeline that needs multiple speed variations per clip — a normal version, a 1.5x for Stories, and a 2x for YouTube Shorts previews — you can generate all three in parallel. The batch processing guide for social media covers the full multi-platform pattern.
You can also change video speed in the browser without installing anything using the change video speed tool, useful for quick one-offs before you commit to automating the whole pipeline.
Common mistakes
No audio handling
If you use -vf "setpts=..." without touching audio, the output has mismatched audio unless you also add -an or the matching atempo. FFmpeg doesn't always warn you. It'll just mux them together and let you discover the problem on playback.
atempo out of range
Values outside 0.5-2.0 will fail. Chain the filter instead of trying to pass 3.0 or 4.0 directly.
PTS resets on concatenated footage
If your input is a concatenated file with PTS resets (common with some cameras), add -vf "setpts=PTS-STARTPTS" before the speed filter to normalize timestamps first: -vf "setpts=PTS-STARTPTS,setpts=0.5*PTS".
Trimming before speed change
If clips need trimming to a specific length first, do that as a separate pass before applying the speed filter. Combining trim and speed change in one -vf chain works but makes the filter string harder to debug when something goes wrong.
Codec compatibility after speed change
After changing speed significantly, re-encode with a codec that handles your target bitrate well. For web delivery, -c:v libx264 -crf 22 -c:a aac is reliable. For a full reference on encoding flags, see the FFmpeg cheat sheet.
Frame rate mismatch for timelapse
If you're creating a time-lapse and the output looks choppy, check the output frame rate. For very extreme speed-ups, add -r 30 to enforce a consistent output frame rate: -vf "setpts=0.01*PTS" -r 30.
Speed change on variable frame rate input
Phone video is often recorded in variable frame rate (VFR) mode. Running setpts on VFR footage produces uneven results because the timestamps aren't uniform to begin with. Fix it first by converting to constant frame rate: ffmpeg -i input.mp4 -vf "fps=30" -c:v libx264 cfr.mp4, then apply your speed change to cfr.mp4.
FAQ
How do I sync audio when speeding up video?
Pair setpts with atempo. The setpts filter controls video timing by multiplying presentation timestamps. The atempo filter controls audio speed independently, preserving pitch. Use the inverse ratio: setpts=0.5*PTS with atempo=2.0 for 2x speed. Apply one without the other and the audio and video drift apart immediately — FFmpeg won't warn you, it'll just produce a broken file.
Why is my slow motion choppy?
If you're using setpts to slow down standard 30fps footage, FFmpeg has to repeat frames to fill the gaps. At 2x slow motion, every frame appears twice. The result stutters on motion. For smooth slow motion, you need source footage shot at a high frame rate (60fps or 120fps), then played back at 24-30fps. Alternatively, minterpolate generates synthetic intermediate frames — slow to process and sometimes artifacts on fast motion, but better than frame doubling.
What's the max speed with atempo?
atempo accepts values between 0.5 and 2.0. For speeds outside that range, chain multiple filters: atempo=2.0,atempo=2.0 for 4x, atempo=2.0,atempo=2.0,atempo=2.0 for 8x. Going below 0.25x total speed starts to sound unnatural regardless of how you chain it.
How do I batch speed change 100 videos?
Use the RenderIO API with parallel requests. The JavaScript example above processes clips with Promise.all. For 100 clips, batch in groups of 10-20 to stay within rate limits. The Python version with a polling loop handles this well for sequential processing. See the Python FFmpeg API guide for the async batch pattern.
Does speed change affect quality?
The setpts filter itself doesn't re-encode. But you typically want to re-encode afterward to get clean output, and that's where quality is determined. Pass -c:v libx264 -crf 22 to control it directly — lower CRF is better quality and a larger file, higher CRF is smaller but more compressed. The speed change itself doesn't degrade anything; the re-encoding does, and you control how much.