There are two kinds of loop. A repeat plays the same footage again from the start. A boomerang (like Instagram’s Boomerang or a ping-pong GIF) plays forward and then straight away backward. In FFmpeg, a repeat needs one option, -stream_loop. A boomerang uses reverse plus concat.
Repeat: -stream_loop (no re-encode)
ffmpeg -stream_loop 2 -i input.mp4 -c copy output.mp4
-stream_loop N means “repeat N more times”, so 2 plays the video three times in total. It is an input option (a setting for the input file), so it must come before -i. If you put it after, FFmpeg treats it as an output option and stops with an error.
The main advantage is that -c copy (no re-encode) works. Turning a 1080p 10-second clip (30 MB) into six passes (60 seconds) took 0.3 seconds. The same job with a re-encode took 5.9 seconds (i9-14900KF, libx264 -preset veryfast -crf 23, measured 2026-09-02).
Loop up to a given duration
Use -stream_loop -1 to loop forever, and stop the output with -t. This is useful for matching the length of a music track.
ffmpeg -stream_loop -1 -i input.mp4 -t 60 -c copy output.mp4
Without -t, FFmpeg keeps writing until you stop it.
Looping video under a separate audio track
This repeats a short clip for the length of a music track and takes the audio from a second file. -shortest stops the output when the shorter input (here, the music) ends.
ffmpeg -stream_loop 3 -i input.mp4 -i bgm.mp3 -map 0:v -map 1:a -c:v copy -c:a aac -shortest output.mp4
To make sure the video is always longer than the music, use -stream_loop -1. -shortest then ends everything when the track ends.
The join looks wrong with -c copy
Looping with -c copy assumes that the source starts on a keyframe. If a phone MOV jumps at the join, first copy it into an MP4 with -c copy alone (ffmpeg -i input.mov -c copy fixed.mp4) and loop that file. If the video stops for a moment at every join, check whether the audio is longer than the video (see the FAQ below).
Boomerang: forward + reverse
Use split to make two copies of the video, reverse one of them, and concat them: forward first, then backward.
ffmpeg -ss 2 -t 3 -i input.mp4 -filter_complex "[0:v]split[a][b];[b]reverse[r];[a][r]concat=n=2:v=1[v]" -map "[v]" -an -c:v libx264 -crf 20 -pix_fmt yuv420p -movflags +faststart output.mp4
-ss 2 -t 3— take 3 seconds, starting at the 2-second mark. Boomerangs usually use 1–3 seconds of footage.reverseholds every frame in memory, so avoid long sources (see the memory table in Reverse a video)split[a][b]— make two copies of the stream[b]reverse[r]— reverse one copy[a][r]concat=n=2:v=1[v]— join the forward copy, then the reversed one (v=1means one video stream and no audio)-an— drop the audio. A boomerang almost never uses reversed sound-movflags +faststart— put the file’s index (the moov atom) at the front, so playback on the web can start sooner
A 3-second source gives a 6-second boomerang. At 1080p and 30 fps, the file was 6.8 MB and took 1.6 seconds to make (-crf 20).
Bounce the audio too
If you want the sound to bounce as well, do the same on the audio side with asplit and areverse.
ffmpeg -i input.mp4 -filter_complex "[0:v]split[a][b];[b]reverse[r];[a][r]concat=n=2:v=1[v];[0:a]asplit[c][d];[d]areverse[ar];[c][ar]concat=n=2:v=0:a=1[a]" -map "[v]" -map "[a]" -movflags +faststart output.mp4
Speed up the bounce
For the quick tempo of Instagram’s Boomerang, speed it up with setpts, placed after concat.
ffmpeg -ss 2 -t 3 -i input.mp4 -filter_complex "[0:v]split[a][b];[b]reverse[r];[a][r]concat=n=2:v=1,setpts=0.5*PTS[v]" -map "[v]" -an output.mp4
Repeat the bounce several times
The lightest way is to loop the finished boomerang with -stream_loop.
ffmpeg -stream_loop 2 -i boomerang.mp4 -c copy output.mp4
To do it in one command, add the loop filter after concat. loop=loop=2:size=32767:start=0 repeats all frames twice more, for three bounces in total.
ffmpeg -ss 2 -t 3 -i input.mp4 -filter_complex "[0:v]split[a][b];[b]reverse[r];[a][r]concat=n=2:v=1,loop=loop=2:size=32767:start=0[v]" -map "[v]" -an -c:v libx264 -crf 20 output.mp4
size is the most frames the filter will hold; 32767 is its limit. The loop filter also keeps the frames it receives in memory (here, the 180 frames of the 6-second bounce), so use -stream_loop whenever you can. Both methods gave an 18 s video (the 3 s source, bounced 3 times) of nearly the same size: 20.3 MB with -stream_loop -c copy and 20.8 MB with the loop filter and a re-encode.
Boomerang GIF
Some social media and chat apps need a GIF. Lower the frame rate and size with fps and scale, and make an optimised 256-colour palette with palettegen and paletteuse.
ffmpeg -ss 2 -t 3 -i input.mp4 -filter_complex "[0:v]fps=15,scale=480:-1,split[a][b];[b]reverse[r];[a][r]concat=n=2:v=1,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 output.gif
-loop 0 makes the GIF loop forever. It is an output option, so it goes after -i. It has nothing to do with -stream_loop.
Measured: GIF vs MP4
A 6-second boomerang, 480 px wide at 15 fps, made from the same 3-second 1080p 30 fps source:
| Format | Size | Time |
|---|---|---|
| GIF (palettegen / paletteuse, 256 colours) | 7.9 MB | 2.6 s |
MP4 (libx264 -crf 23) |
0.3 MB | — |
The GIF is 26× larger. A GIF can only skip pixels that did not change since the previous frame. Unlike MP4, it cannot predict motion, and it is limited to 256 colours. That is why real footage with motion gets so large as a GIF. If the place you post to accepts MP4 (X, Discord, Slack and LINE all do), post MP4, and use GIF only where nothing else is accepted. For GIF quality and size settings, see Create GIFs.
Repeat only a section (loop filter)
To repeat a range of frames in the middle of a video, use the loop filter. start is the first frame number, size is the length in frames, and loop is how many extra times to repeat.
ffmpeg -i input.mp4 -vf "loop=loop=3:size=30:start=60" -an output.mp4
For a 30 fps video, this repeats the one second that starts at the 2-second mark three more times (four times in total). The filter counts frames, so with a variable-frame-rate source the repeated part may start at a slightly different time than you expect.
FAQ
I added -stream_loop but it plays only once
-stream_loop applies to the next -i. With -i input.mp4 -stream_loop 2 -i bgm.mp3, bgm.mp3 repeats and input.mp4 plays once. Put it right before the input you want to repeat: -stream_loop 2 -i input.mp4. Some formats (raw H.264 streams, for example) can’t jump back to the start (seek), so they ignore it. An MP4 that does not start on a keyframe also plays only once. Copy it into a new MP4 with -c copy first, then loop the new file.
There’s a brief freeze at every loop
If the audio is longer than the video, -stream_loop uses the longer audio to decide the length of each pass. The video then holds its last frame at every join. Re-encoding or -fflags +genpts does not fix this. If you don’t need the audio, add -an.
The same frame shows twice at the turnaround
concat puts the last frame of [a] right before the first frame of [r], and they are the same frame. To avoid this, drop the first frame of the reversed copy and restart its timestamps at zero: [b]reverse,trim=start_frame=1,setpts=PTS-STARTPTS[r]. Without setpts, the dropped frame leaves a gap, and the frame before the turn stays on screen for two frame times.
Related tool
The Loop / boomerang tool makes a boomerang in your browser. It is meant for short clips, and your file never leaves your device.
Related articles
- Reverse a video — measured memory use of
reverseand how to work around it - Create GIFs — palette optimisation and size reduction
- Join videos with concat — joining separate files
- Change video speed —
setptsin detail