“Loop” means two different things. A repeat plays the same footage from the start again; a boomerang (Instagram’s Boomerang, a ping-pong GIF) plays forward, then immediately backward. In FFmpeg a repeat is a single -stream_loop option, and a boomerang is reverse plus concat. Here are both, with the file sizes and processing times we got when we actually made them.
Verified: FFmpeg 8.1 on Windows — all 10 commands in this article were executed by the verification script and passed (2026-09-06).
Repeat: -stream_loop (no re-encode)
ffmpeg -stream_loop 2 -i input.mp4 -c copy output.mp4
-stream_loop N means “repeat an additional N times”, so 2 gives three passes in total. It is an input option and must come before -i; after it, FFmpeg treats it as an output option and fails.
Being able to use -c copy is the big advantage. Turning a 1080p 10-second clip (30 MB) into six passes / 60 seconds took 0.3 seconds. The same job with a re-encode takes 5.9 seconds (i9-14900KF, libx264 -preset veryfast -crf 23, measured 2026-09-02).
Loop up to a given duration
Use -stream_loop -1 for an infinite loop and stop the output with -t. Handy for matching a music track.
ffmpeg -stream_loop -1 -i input.mp4 -t 60 -c copy output.mp4
Forget -t and it writes forever.
Looping video under a separate audio track
Repeat a short clip for the length of a music track and take the audio from a second file. -shortest stops 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 always outlasts the music, use -stream_loop -1 and let -shortest end everything at the end of the track.
The join looks wrong with -c copy
Looping with -c copy assumes the source starts on a keyframe and its timestamps start at 0. If a phone MOV skips or freezes at the join, either drop -c copy and re-encode, or clean up the start first with -ss 0 -c copy.
Boomerang: forward + reverse
split the video into two branches, reverse one, and concat them forward-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 are usually built from 1–3 seconds, andreverseholds every frame in memory, so avoid long sources (see the measured table in Reverse a video)split[a][b]— duplicate the stream[b]reverse[r]— reverse one copy[a][r]concat=n=2:v=1[v]— join forward then reverse (v=1= one video stream, no audio)-an— drop the audio; reversed audio is almost never wanted in a boomerang-movflags +faststart— move the moov atom to the front for web playback
A 3-second source gives a 6-second ping-pong. At 1080p / 30 fps the file was 6.8 MB and took 1.6 seconds (-crf 20).
Bounce the audio too
If you do want it, mirror the structure on the audio side with asplit / 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 snappy Boomerang tempo, raise the speed with setpts 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 run the finished boomerang through -stream_loop.
ffmpeg -stream_loop 2 -i boomerang.mp4 -c copy output.mp4
To do it in one command, append 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 output.mp4
size is the maximum number of frames to buffer; 32767 is the filter’s limit. The loop filter also keeps size frames in memory, so prefer -stream_loop whenever it applies. Both give 3 s × 3 bounces = 18 s and nearly identical sizes: 20.3 MB (-stream_loop -c copy) vs 20.8 MB (loop filter with re-encode).
Boomerang GIF
Social media and chat apps sometimes want a GIF. Reduce the information with fps and scale, and optimise the 256-colour palette with palettegen / paletteuse.
ffmpeg -ss 2 -t 2 -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 is the GIF’s infinite-loop flag (an output option, so it goes after -i). It is unrelated to -stream_loop.
Measured: GIF vs MP4
A 6-second boomerang at 480 px wide / 15 fps 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 | — |
A 26× difference. A GIF stores every pixel of every frame and is capped at 256 colours, so this is what live footage with motion costs. If the destination accepts MP4 (X, Discord, Slack and LINE all do), post MP4 and reserve GIF for places that accept nothing else. Quality and size tuning for GIFs is covered in Create GIFs.
Repeat only a section (loop filter)
To repeat a specific range of frames in the middle of a video, use the loop filter: start is the first frame, size the length in frames, loop the number of extra repeats.
ffmpeg -i input.mp4 -vf "loop=loop=3:size=30:start=60" -an output.mp4
At 30 fps this reads “repeat the one second starting at the 2-second mark three more times (four in total)”. Because it works in frames, variable-frame-rate sources may land at a slightly different time than you expect.
FAQ
I added -stream_loop but it plays only once
Check it’s not after -i. It is an input option: -stream_loop 2 -i input.mp4. Some formats (raw H.264 streams, for example) can’t be seeked and ignore it; MP4 / MKV / MOV are fine.
There’s a brief freeze at every loop
Timestamps are discontinuous at the -c copy join. Re-encode (-c:v libx264) or try adding -fflags +genpts.
The same frame shows twice at the turnaround
concat puts the last frame of [a] directly before the first frame of [r], which is the same frame. If it bothers you, drop the first frame of the reversed branch: [b]reverse,trim=start_frame=1[r].
Related tool
The Loop / boomerang tool runs this article’s boomerang processing in your browser. It’s meant for short clips, and the 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