Fully offline: the same work in ffmpeg

The browser version is already local, and ffmpeg in a terminal removes even the dependency on an open tab. Here are ready commands for macOS, Windows and Linux that reproduce every mode of the tool.

When you have many files, very large ones, or you want a script, ffmpeg on your own machine is the better tool. Below are the same modes as on the site: duplicate, blend and motion-compensated interpolation. Swap in your own file names.

In every command F is the slow-down factor (8 or 16), FPS is the output frame rate (60 for x8, 30 for x16), and INPUT and OUTPUT are the source and result file names. In the Windows commands those same two files are called IN and OUT: in PowerShell $input is a reserved automatic variable, so it cannot be reused as your own.

Press "Copy" to take the whole command.

Install ffmpeg

Install ffmpeg once and everything after that runs offline. Check the install with ffmpeg -version.

macOS

brew install ffmpeg
ffmpeg -version
brew tap homebrew-ffmpeg/ffmpeg && brew install homebrew-ffmpeg/ffmpeg/ffmpeg --with-zimg

The Homebrew build includes libx264, libx265 and videotoolbox, so every slow-down mode works straight away. But zimg is NOT in it: there is no zscale filter, and the HDR tone mapping chain will not run. Check with: ffmpeg -hide_banner -filters | grep zscale. If that prints nothing and you do need HDR, install the tap build with the third command. If you do not have Homebrew yet, install it from brew.sh.

Windows

winget install Gyan.FFmpeg
ffmpeg -version
chcp 65001

Close the terminal and open a new one after installing, otherwise PATH is stale. The Gyan.FFmpeg build includes libx264, libx265, zimg and nvenc, qsv and amf support. The third command switches cmd.exe to UTF-8: without it, Cyrillic and emoji in file names turn into question marks.

Linux

sudo apt update && sudo apt install ffmpeg
ffmpeg -version
curl -L -O https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && tar xf ffmpeg-release-amd64-static.tar.xz && sudo install -m 755 ffmpeg-*-amd64-static/ffmpeg ffmpeg-*-amd64-static/ffprobe /usr/local/bin/

On Debian and Ubuntu the repository version is often a year or more behind. If ffmpeg -version reports 4.x, use the johnvansickle.com static build in the third command: it unpacks into a single directory, conflicts with nothing, and includes libx264, libx265 and zimg. On Fedora use dnf install ffmpeg, on Arch pacman -S ffmpeg.

Inspect the file

Start with the codec, resolution, frame rate and rotation: every command below depends on them.

macOS

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,r_frame_rate,avg_frame_rate,nb_frames,color_transfer:stream_tags=rotate -of json "$INPUT"

A color_transfer of arib-std-b67 means HLG and smpte2084 means PQ. Both are HDR: see the tone mapping step.

Windows

$IN="input.mp4"; $OUT="output.mp4"; $F=8; $FPS=60
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,r_frame_rate,avg_frame_rate,nb_frames,color_transfer:stream_tags=rotate -of json "$IN"

A color_transfer of arib-std-b67 means HLG and smpte2084 means PQ. Both are HDR: see the tone mapping step.

Linux

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,r_frame_rate,avg_frame_rate,nb_frames,color_transfer:stream_tags=rotate -of json "$INPUT"

A color_transfer of arib-std-b67 means HLG and smpte2084 means PQ. Both are HDR: see the tone mapping step.

Slow down by duplicating frames

The fastest and most reliable route: frames are repeated, there are no artifacts, and motion looks stepped.

macOS

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"

setpts MUST come before fps. Swap them and fps runs on the original timestamps, setpts then stretches an already-decimated stream, and you get no slow motion at all.

Windows

$IN="input.mp4"; $OUT="output.mp4"; $F=8; $FPS=60
ffmpeg -i "$IN" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUT"

setpts MUST come before fps. Swap them and fps runs on the original timestamps, setpts then stretches an already-decimated stream, and you get no slow motion at all.

Linux

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"

setpts MUST come before fps. Swap them and fps runs on the original timestamps, setpts then stretches an already-decimated stream, and you get no slow motion at all.

Slow down with blending

Neighbouring frames are mixed, so stutter turns into a soft smear. Almost as fast as duplication.

macOS

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "setpts=${F}*PTS,minterpolate=fps=${FPS}:mi_mode=blend" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"

Same ordering rule: setpts, then minterpolate. The fps option inside minterpolate sets the output rate, so no separate fps filter is needed.

Windows

$IN="input.mp4"; $OUT="output.mp4"; $F=8; $FPS=60
ffmpeg -i "$IN" -vf "setpts=${F}*PTS,minterpolate=fps=${FPS}:mi_mode=blend" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUT"

Same ordering rule: setpts, then minterpolate. The fps option inside minterpolate sets the output rate, so no separate fps filter is needed.

Linux

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "setpts=${F}*PTS,minterpolate=fps=${FPS}:mi_mode=blend" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"

Same ordering rule: setpts, then minterpolate. The fps option inside minterpolate sets the output rate, so no separate fps filter is needed.

Motion-compensated interpolation

The minterpolate filter builds in-between frames from motion vectors. Best result, long processing, and water, foliage and cuts can come out distorted.

macOS

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "setpts=${F}*PTS,minterpolate=fps=${FPS}:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"

Run ten seconds first with -ss 0 -t 10, look at the result, and only then start the whole file. Redoing a two-hour render hurts.

Windows

$IN="input.mp4"; $OUT="output.mp4"; $F=8; $FPS=60
ffmpeg -i "$IN" -vf "setpts=${F}*PTS,minterpolate=fps=${FPS}:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUT"

Run ten seconds first with -ss 0 -t 10, look at the result, and only then start the whole file. Redoing a two-hour render hurts.

Linux

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "setpts=${F}*PTS,minterpolate=fps=${FPS}:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"

Run ten seconds first with -ss 0 -t 10, look at the result, and only then start the whole file. Redoing a two-hour render hurts.

Variable frame rate

Screen recordings and phone footage often drift in frame rate. Force a constant rate first, or the slow motion will drift away from the audio.

macOS

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "fps=30,setpts=${F}*PTS,minterpolate=fps=${FPS}:mi_mode=mci" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"

The 30 in fps=30 is the SOURCE rate from ffprobe, not the target. The target lives inside minterpolate. It is easy to swap the two, and then the file simply does not slow down.

Windows

$IN="input.mp4"; $OUT="output.mp4"; $F=8; $FPS=60
ffmpeg -i "$IN" -vf "fps=30,setpts=${F}*PTS,minterpolate=fps=${FPS}:mi_mode=mci" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUT"

The 30 in fps=30 is the SOURCE rate from ffprobe, not the target. The target lives inside minterpolate. It is easy to swap the two, and then the file simply does not slow down.

Linux

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "fps=30,setpts=${F}*PTS,minterpolate=fps=${FPS}:mi_mode=mci" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"

The 30 in fps=30 is the SOURCE rate from ffprobe, not the target. The target lives inside minterpolate. It is easy to swap the two, and then the file simply does not slow down.

HDR to SDR

HLG or PQ footage from an iPhone looks flat in ordinary players. Convert the color to BT.709. Check that your build has the zscale filter: the stock Homebrew build does not.

macOS

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=bt709:t=bt709:m=bt709:r=tv,format=yuv420p,setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"
ffmpeg -hide_banner -filters | grep zscale

zscale exists only in builds configured with --enable-libzimg. Check with: ffmpeg -hide_banner -filters | grep zscale. macOS is where it is usually missing: the stock Homebrew build ships without zimg, while the Gyan builds for Windows, the Debian and Ubuntu packages and the johnvansickle static builds all have it. If it is missing you have two options. On a machine with OpenCL you can use tonemap_opencl (fiddlier, needs -init_hw_device opencl). Or drop the whole chain down to format=yuv420p: the file will open everywhere, but the colours stay wrong, highlights clip to white and saturation drops. Do not stay quiet about that in the result.

Windows

$IN="input.mp4"; $OUT="output.mp4"; $F=8; $FPS=60
ffmpeg -i "$IN" -vf "zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=bt709:t=bt709:m=bt709:r=tv,format=yuv420p,setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUT"
ffmpeg -hide_banner -filters | Select-String zscale

zscale exists only in builds configured with --enable-libzimg. Check with: ffmpeg -hide_banner -filters | grep zscale. macOS is where it is usually missing: the stock Homebrew build ships without zimg, while the Gyan builds for Windows, the Debian and Ubuntu packages and the johnvansickle static builds all have it. If it is missing you have two options. On a machine with OpenCL you can use tonemap_opencl (fiddlier, needs -init_hw_device opencl). Or drop the whole chain down to format=yuv420p: the file will open everywhere, but the colours stay wrong, highlights clip to white and saturation drops. Do not stay quiet about that in the result.

Linux

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=bt709:t=bt709:m=bt709:r=tv,format=yuv420p,setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"
ffmpeg -hide_banner -filters | grep zscale

zscale exists only in builds configured with --enable-libzimg. Check with: ffmpeg -hide_banner -filters | grep zscale. macOS is where it is usually missing: the stock Homebrew build ships without zimg, while the Gyan builds for Windows, the Debian and Ubuntu packages and the johnvansickle static builds all have it. If it is missing you have two options. On a machine with OpenCL you can use tonemap_opencl (fiddlier, needs -init_hw_device opencl). Or drop the whole chain down to format=yuv420p: the file will open everywhere, but the colours stay wrong, highlights clip to white and saturation drops. Do not stay quiet about that in the result.

Rotation

If the picture is sideways, bake the rotation into the pixels and clear the flag from the metadata.

macOS

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "transpose=1,setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -metadata:s:v rotate=0 -an "$OUTPUT"

transpose=1 is 90 degrees clockwise, transpose=2 is counter-clockwise, and for 180 use transpose=1,transpose=1 or hflip,vflip. The rotate tag from ffprobe tells you which: 90 means transpose=1, 270 means transpose=2. Width and height swap in the process.

Windows

$IN="input.mp4"; $OUT="output.mp4"; $F=8; $FPS=60
ffmpeg -i "$IN" -vf "transpose=1,setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -metadata:s:v rotate=0 -an "$OUT"

transpose=1 is 90 degrees clockwise, transpose=2 is counter-clockwise, and for 180 use transpose=1,transpose=1 or hflip,vflip. The rotate tag from ffprobe tells you which: 90 means transpose=1, 270 means transpose=2. Width and height swap in the process.

Linux

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "transpose=1,setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -metadata:s:v rotate=0 -an "$OUTPUT"

transpose=1 is 90 degrees clockwise, transpose=2 is counter-clockwise, and for 180 use transpose=1,transpose=1 or hflip,vflip. The rotate tag from ffprobe tells you which: 90 means transpose=1, 270 means transpose=2. Width and height swap in the process.

Audio

Dropping the track is usually simplest. If you need sound, stretch it with a chain of atempo filters, though at 8x it becomes a drone.

macOS

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -filter_complex "[0:v]setpts=${F}*PTS,minterpolate=fps=${FPS}:mi_mode=blend[v];[0:a]atempo=0.5,atempo=0.5,atempo=0.5[a]" -map "[v]" -map "[a]" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -c:a aac -b:a 192k "$OUTPUT"
ffmpeg -i "$INPUT" -filter_complex "[0:v]setpts=16*PTS,minterpolate=fps=30:mi_mode=blend[v];[0:a]atempo=0.5,atempo=0.5,atempo=0.5,atempo=0.5[a]" -map "[v]" -map "[a]" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -c:a aac -b:a 192k "$OUTPUT"
ffmpeg -i "$INPUT" -filter_complex "[0:v]setpts=0.125*PTS[v];[0:a]atempo=2.0,atempo=2.0,atempo=2.0[a]" -map "[v]" -map "[a]" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -c:a aac -b:a 192k "$OUTPUT"

The product of the atempo chain must equal 1/F or the audio will drift away from the picture. Check by multiplying: 0.5 cubed is 0.125, which is 1/8. The three commands above are x8, x16 and 8x faster; for other factors just add or remove links.

Windows

$IN="input.mp4"; $OUT="output.mp4"; $F=8; $FPS=60
ffmpeg -i "$IN" -filter_complex "[0:v]setpts=${F}*PTS,minterpolate=fps=${FPS}:mi_mode=blend[v];[0:a]atempo=0.5,atempo=0.5,atempo=0.5[a]" -map "[v]" -map "[a]" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -c:a aac -b:a 192k "$OUT"

The product of the atempo chain must equal 1/F or the audio will drift away from the picture. Check by multiplying: 0.5 cubed is 0.125, which is 1/8. The three commands above are x8, x16 and 8x faster; for other factors just add or remove links.

Linux

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -filter_complex "[0:v]setpts=${F}*PTS,minterpolate=fps=${FPS}:mi_mode=blend[v];[0:a]atempo=0.5,atempo=0.5,atempo=0.5[a]" -map "[v]" -map "[a]" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -c:a aac -b:a 192k "$OUTPUT"
ffmpeg -i "$INPUT" -filter_complex "[0:v]setpts=16*PTS,minterpolate=fps=30:mi_mode=blend[v];[0:a]atempo=0.5,atempo=0.5,atempo=0.5,atempo=0.5[a]" -map "[v]" -map "[a]" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -c:a aac -b:a 192k "$OUTPUT"
ffmpeg -i "$INPUT" -filter_complex "[0:v]setpts=0.125*PTS[v];[0:a]atempo=2.0,atempo=2.0,atempo=2.0[a]" -map "[v]" -map "[a]" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -c:a aac -b:a 192k "$OUTPUT"

The product of the atempo chain must equal 1/F or the audio will drift away from the picture. Check by multiplying: 0.5 cubed is 0.125, which is 1/8. The three commands above are x8, x16 and 8x faster; for other factors just add or remove links.

Progress and timing

For long jobs, write progress to a file so you can see how much is left.

macOS

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -progress pipe:1 -nostats -i "$INPUT" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"
ffmpeg -progress progress.txt -nostats -i "$INPUT" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"

Example: a 12 second source with F=8 and FPS=60 gives ceil(12 * 8 * 60) = 5760 output frames. If frame= climbs by 20 per second, that is about 5 minutes of work. The second command writes the same data to progress.txt, which is handy when ffmpeg runs in the background.

Windows

$IN="input.mp4"; $OUT="output.mp4"; $F=8; $FPS=60
ffmpeg -progress pipe:1 -nostats -i "$IN" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUT"

Example: a 12 second source with F=8 and FPS=60 gives ceil(12 * 8 * 60) = 5760 output frames. If frame= climbs by 20 per second, that is about 5 minutes of work. The second command writes the same data to progress.txt, which is handy when ffmpeg runs in the background.

Linux

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -progress pipe:1 -nostats -i "$INPUT" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"
ffmpeg -progress progress.txt -nostats -i "$INPUT" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"

Example: a 12 second source with F=8 and FPS=60 gives ceil(12 * 8 * 60) = 5760 output frames. If frame= climbs by 20 per second, that is about 5 minutes of work. The second command writes the same data to progress.txt, which is handy when ffmpeg runs in the background.

Which encoders you actually have

Start by checking what your ffmpeg was built with. Being listed guarantees nothing: an encoder can be compiled in while the hardware for it is simply absent. Only a one-frame test run tells you the truth.

macOS

ffmpeg -hide_banner -encoders | grep -E "videotoolbox|nvenc|qsv|amf|vaapi"
ffmpeg -hide_banner -f lavfi -i testsrc=size=1280x720:rate=30 -frames:v 1 -c:v h264_videotoolbox -f null -

The honest trade-off: libx264 -crf 18 stays the default because at equal bitrate it gives the better picture. A hardware encoder is the "fast" option and quality at the same bitrate will be lower, especially on motion. And more to the point: in motion mode the bottleneck is not the encoder, it is minterpolate, which runs on the CPU. There a hardware encoder buys almost nothing, because the encoder sits idle waiting for frames anyway.

Windows

ffmpeg -hide_banner -encoders | Select-String "videotoolbox|nvenc|qsv|amf|vaapi"
ffmpeg -hide_banner -f lavfi -i testsrc=size=1280x720:rate=30 -frames:v 1 -c:v h264_nvenc -f null -

The honest trade-off: libx264 -crf 18 stays the default because at equal bitrate it gives the better picture. A hardware encoder is the "fast" option and quality at the same bitrate will be lower, especially on motion. And more to the point: in motion mode the bottleneck is not the encoder, it is minterpolate, which runs on the CPU. There a hardware encoder buys almost nothing, because the encoder sits idle waiting for frames anyway.

Linux

ffmpeg -hide_banner -encoders | grep -E "videotoolbox|nvenc|qsv|amf|vaapi"
ffmpeg -hide_banner -vaapi_device /dev/dri/renderD128 -f lavfi -i testsrc=size=1280x720:rate=30 -frames:v 1 -vf "format=nv12,hwupload" -c:v h264_vaapi -f null -

The honest trade-off: libx264 -crf 18 stays the default because at equal bitrate it gives the better picture. A hardware encoder is the "fast" option and quality at the same bitrate will be lower, especially on motion. And more to the point: in motion mode the bottleneck is not the encoder, it is minterpolate, which runs on the CPU. There a hardware encoder buys almost nothing, because the encoder sits idle waiting for frames anyway.

Hardware encoding

A hardware encoder is several times faster, but at equal bitrate the picture is worse, especially on motion. In "Motion" mode the bottleneck is minterpolate on the CPU anyway, so it buys you almost nothing there.

macOS

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v h264_videotoolbox -b:v 12M -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"
ffmpeg -i "$INPUT" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v hevc_videotoolbox -tag:v hvc1 -b:v 8M -movflags +faststart -an "$OUTPUT"
ffmpeg -i "$INPUT" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v libx265 -crf 20 -preset slow -tag:v hvc1 -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"

VideoToolbox does not understand -crf: quality is set by bitrate with -b:v, or -q:v on Apple Silicon. It works on both Intel and M-series.

Windows

$IN="input.mp4"; $OUT="output.mp4"; $F=8; $FPS=60
ffmpeg -i "$IN" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v h264_nvenc -rc vbr -cq 23 -b:v 0 -pix_fmt yuv420p -movflags +faststart -an "$OUT"
ffmpeg -i "$IN" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v h264_qsv -global_quality 23 -pix_fmt nv12 -movflags +faststart -an "$OUT"
ffmpeg -i "$IN" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v h264_amf -rc cqp -qp_i 22 -qp_p 24 -pix_fmt yuv420p -movflags +faststart -an "$OUT"

nvenc uses -cq with -rc vbr -b:v 0 instead of crf, qsv uses -global_quality, and amf uses -rc cqp with -qp_i and -qp_p. For AMD: ffmpeg -i "$IN" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v h264_amf -rc cqp -qp_i 22 -qp_p 24 -pix_fmt yuv420p -movflags +faststart -an "$OUT"

Linux

INPUT="input.mp4"; OUTPUT="output.mp4"; F=8; FPS=60
ffmpeg -i "$INPUT" -vf "setpts=${F}*PTS,fps=${FPS}" -c:v h264_nvenc -rc vbr -cq 23 -b:v 0 -pix_fmt yuv420p -movflags +faststart -an "$OUTPUT"
ffmpeg -vaapi_device /dev/dri/renderD128 -i "$INPUT" -vf "setpts=${F}*PTS,fps=${FPS},format=nv12,hwupload" -c:v h264_vaapi -b:v 12M -movflags +faststart -an "$OUTPUT"

VA-API needs an explicit device and an upload of the frames into GPU memory: hence -vaapi_device /dev/dri/renderD128 and the format=nv12,hwupload tail on the filter. Check that the device exists with ls /dev/dri/. nvenc needs the proprietary NVIDIA drivers; the open nouveau driver cannot encode.

The same thing without a browser

Once ffmpeg is installed no internet is involved at all, neither to start nor to process. That suits air-gapped setups, machines with no network and batch jobs where the data must stay on one specific computer.

These commands target ffmpeg 6 and newer. Some filter names differ in older builds.

How it works

Not a single byte of your video goes over the network. Everything below happens inside your browser tab.

  1. The file opens locally

    You pick a video and the browser reads it through the File API, like any file on disk. No request carries your file to a server: the page itself is static, and from there only your device does the work.

  2. The container is unpacked

    The MP4 or MOV is split into video packets, and the headers give up the codec, frame rate, rotation and color space. From those we build a plan: how many frames you will get and how long it should take.

  3. Frames are decoded with WebCodecs

    The browser's VideoDecoder turns packets into frames with hardware acceleration. Frames live in the tab's memory and are closed the moment they are used, so memory does not run away.

  4. New frames are built in between

    Duplicate repeats a frame, blend cross-fades neighbours, and motion mode computes motion vectors on the GPU through WebGPU. These new frames are what make the movement smooth.

  5. The result is encoded back to MP4

    VideoEncoder writes H.264 at a constant frame rate with a keyframe every two seconds, it is wrapped into an MP4, and it lands in your downloads. No copy is left anywhere.

Frequently asked questions

Is this free?

Completely. There are no paid tiers, no cap on how many files you run, and no watermarks. Your own computer does the work, so there is nothing for us to charge for.

Is it safe? What happens to my video?

The video is processed inside the tab and is never sent anywhere. We physically cannot receive it: the server only holds HTML, CSS and JavaScript. Close the tab and nothing is left behind.

Is my video really not uploaded?

Really. It is easy to check: open developer tools, go to the Network tab and run a job. No request carries the file. You can even go offline after the page loads and processing still works.

Does it work on iPhone and on a MacBook?

Yes. On a MacBook it works in Safari, Chrome and Edge, HEVC from the camera included. On iPhone it needs Safari 17 or newer, and the tab has to stay in the foreground, because iOS stops the work in the background. Trim long clips first on a phone.

Which browsers are supported?

Chrome, Edge and other Chromium browsers from version 94, Safari 17 and newer, and Firefox with limits. WebCodecs is what matters; without it the ffmpeg.wasm fallback kicks in, which is slower.

Why does HEVC not work in Firefox?

Firefox does not decode HEVC through WebCodecs. That is a browser limitation, not a site one, and iPhone footage is often HEVC. Open the file in Chrome, Edge or Safari, or convert it to H.264.

Why is my portrait video sideways?

MP4 stores rotation as a separate flag and plenty of software ignores it. We read that flag and rotate the actual picture while encoding, so the result opens the right way up everywhere, old players included.

What is the maximum file size?

There is no hard limit, only your device's memory. On a desktop, files up to a couple of gigabytes are comfortable; on a phone, stay within a few hundred megabytes. If memory looks tight, we warn you before the job starts.

Why is the output so long?

Slowing down 8 times turns 10 seconds into 80, and 16 times gives you over two minutes. There are that many more frames, so processing time and file size grow with it. If that is more than you need, trim the source first.

Can I do the same thing without a browser?

You can. The "Without a browser" page has ready ffmpeg commands for macOS, Windows and Linux: the same three modes, plus variable frame rate, HDR and rotation. For batches that is the better route.