FFmpeg Speed Up Video: setpts, atempo + Time-Lapse Guide

April 10, 2026 · RenderIO

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 up

  • setpts=2.0*PTS → frames display twice as slow → 2x slow motion

  • setpts=0.25*PTS → 4x faster

  • setpts=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:

ffmpeg -i input.mp4 -vf "setpts=0.5*PTS" -an output.mp4

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:

# 1.5x speed
ffmpeg -i input.mp4 -vf "setpts=0.667*PTS" -an output.mp4

# 2x speed
ffmpeg -i input.mp4 -vf "setpts=0.5*PTS" -an output.mp4

# 4x speed
ffmpeg -i input.mp4 -vf "setpts=0.25*PTS" -an output.mp4

# 8x speed (useful for timelapse)
ffmpeg -i input.mp4 -vf "setpts=0.125*PTS" -an output.mp4

If you want to slow down instead:

# 0.5x speed (2x slow motion)
ffmpeg -i input.mp4 -vf "setpts=2.0*PTS" -an output.mp4

# 0.25x speed (4x slow motion)
ffmpeg -i input.mp4 -vf "setpts=4.0*PTS" -an output.mp4

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).

# 2x speed — video and audio in sync
ffmpeg -i input.mp4 -vf "setpts=0.5*PTS" -af "atempo=2.0" output.mp4

The atempo value is the inverse of the setpts multiplier:

Desired speedsetpts valueatempo value
0.5x (half speed)2.00.5
1.5x0.6671.5
2x0.52.0
4x0.254.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:

# 4x speed (chain atempo 2.0 twice: 2.0 × 2.0 = 4.0)
ffmpeg -i input.mp4 \
  -vf "setpts=0.25*PTS" \
  -af "atempo=2.0,atempo=2.0" \
  output.mp4

# 8x speed (chain three times: 2.0 × 2.0 × 2.0 = 8.0)
ffmpeg -i input.mp4 \
  -vf "setpts=0.125*PTS" \
  -af "atempo=2.0,atempo=2.0,atempo=2.0" \
  output.mp4

# 0.25x speed (chain 0.5 twice: 0.5 × 0.5 = 0.25)
ffmpeg -i input.mp4 \
  -vf "setpts=4.0*PTS" \
  -af "atempo=0.5,atempo=0.5" \
  output.mp4

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):

# 60x speed (one minute of footage → one second of output)
ffmpeg -i input.mp4 \
  -vf "setpts=0.01667*PTS" \
  -an \
  output.mp4

Frame drop approach (keeps every Nth frame, better for large multipliers):

# Keep every 60th frame (equivalent to 60x speed if input is 60fps)
ffmpeg -i input.mp4 \
  -vf "select='not(mod(n\,60))',setpts=N/FRAME_RATE/TB" \
  -an \
  output.mp4

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:

# From 2 hours of footage (7200 seconds) to 2 minutes (120 seconds)
# That's 60x. At 30fps input, keep every 60th frame.
ffmpeg -i sunset.mp4 \
  -vf "select='not(mod(n\,60))',setpts=N/FRAME_RATE/TB" \
  -c:v libx264 -crf 22 -an \
  timelapse.mp4

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.

# 120fps footage, play back at 30fps (4x slow motion, no quality loss)
ffmpeg -i input_120fps.mp4 \
  -vf "fps=30" \
  -c:v libx264 -crf 18 \
  -an \
  slowmo.mp4

This gives you clean slow motion because the actual frames are there, so you're just playing them back slower. 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:

# 2x slow motion with frame interpolation
ffmpeg -i input.mp4 \
  -vf "minterpolate='mi_mode=mci:mc_mode=aobmc:vsbmc=1:fps=60',setpts=2.0*PTS" \
  -c:v libx264 -crf 20 \
  -an \
  slowmo_interp.mp4

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 different platforms, you want to batch these through an API instead of tying up your machine.

This is exactly the kind of workload RenderIO is built for. You send the FFmpeg command over HTTP, it runs in a cloud sandbox, and you get a download URL back. No FFmpeg to install, no Lambda timeout issues, no local CPU usage. The FFmpeg API complete guide covers the full setup, but here's what a speed change looks like:

curl -X POST https://api.renderio.dev/api/v1/run-ffmpeg-command \
  -H "X-API-KEY: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "ffmpeg_command": "-i {{in_video}} -vf \"setpts=0.5*PTS\" -af \"atempo=2.0\" -c:v libx264 -crf 22 -c:a aac {{out_video}}",
    "input_files": {
      "in_video": "https://your-bucket.s3.amazonaws.com/clip.mp4"
    },
    "output_files": {
      "out_video": "clip_2x.mp4"
    }
  }'

For batch processing, you'd loop through your clips and fire off a request per video:

const clips = [
  { url: "https://cdn.example.com/clip1.mp4", speed: 2.0 },
  { url: "https://cdn.example.com/clip2.mp4", speed: 1.5 },
  { url: "https://cdn.example.com/clip3.mp4", speed: 0.5 },
];

async function speedChange(clip) {
  const setpts = (1 / clip.speed).toFixed(4);
  const atempo = clip.speed <= 2.0 ? clip.speed.toFixed(1) : "2.0,atempo=2.0";

  const res = await fetch("https://api.renderio.dev/api/v1/run-ffmpeg-command", {
    method: "POST",
    headers: {
      "X-API-KEY": process.env.RENDERIO_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      ffmpeg_command: `-i {{in_video}} -vf "setpts=${setpts}*PTS" -af "atempo=${atempo}" -c:v libx264 -crf 22 -c:a aac {{out_video}}`,
      input_files: { in_video: clip.url },
      output_files: { out_video: `output_${clip.speed}x.mp4` },
    }),
  });

  return res.json();
}

// Process all clips in parallel
const results = await Promise.all(clips.map(speedChange));

If you're building a social media content pipeline that needs multiple speed variations per clip, say a normal version, a 1.5x version for Stories, and a 2x version for YouTube Shorts previews, you can use RenderIO's parallel processing to generate all three at once. The batch processing guide for social media covers the full multi-platform pattern.

You can also change video speed through 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".

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.