147

Hi I need to extract frames from videos using ffmpeg.. Is there a faster way to do it than this:

ffmpeg -i file.mpg -r 1/1 $filename%03d.jpg

?

Stpn
  • 5,346
  • 7
  • 43
  • 86

8 Answers8

184

If the JPEG encoding step is too performance intensive, you could always store the frames uncompressed as BMP images:

ffmpeg -i file.mpg -r 1/1 $filename%03d.bmp

This also has the advantage of not incurring more quality loss through quantization by transcoding to JPEG. (PNG is also lossless but tends to take much longer than JPEG to encode.)

Multimedia Mike
  • 10,946
  • 4
  • 44
  • 57
  • 1
    this seems to output one more frame that there is in the video (number I can get with the showinfo filter or ffprobe) – Guig Apr 05 '16 at 22:30
  • 11
    This results in *a lot* of frame dropping on my machine. Can I tell ffmpeg to render everything? – Evi1M4chine Jul 07 '16 at 17:19
  • 51
    @Evi1M4chine just remove the -r parameter this will extract all frames – studioj Nov 15 '16 at 06:14
  • 16
    I'd like to add that while JPEG isn't really hard on the CPU, uncompressed Bitmaps is really really hard on the storage, so I doubt you'll get higher throughput with BMP compared to JPEG. – Marcus Müller Apr 03 '17 at 13:12
  • 4
    To extract all frames: `ffmpeg -r 1 file.mp4 -r 1 "$filename%03d.png"` – fiatjaf Dec 25 '17 at 19:52
  • 15
    You mean `ffmpeg -r 1 -i file.mp4 -r 1 "$filename%03d.png`, right? (you were missing the `-i`) – Joschua Jan 23 '20 at 19:00
  • Is it possible to output a frame, or every frame into a local variable (In Node or Python for example)? – FireBrand Oct 08 '20 at 08:48
78

Came across this question, so here's a quick comparison. Compare these two different ways to extract one frame per minute from a video 38m07s long:

time ffmpeg -i input.mp4 -filter:v fps=fps=1/60 ffmpeg_%0d.bmp

1m36.029s

This takes long because ffmpeg parses the entire video file to get the desired frames.

time for i in {0..39} ; do ffmpeg -accurate_seek -ss `echo $i*60.0 | bc` -i input.mp4   -frames:v 1 period_down_$i.bmp ; done

0m4.689s

This is about 20 times faster. We use fast seeking to go to the desired time index and extract a frame, then call ffmpeg several times for every time index. Note that -accurate_seek is the default , and make sure you add -ss before the input video -i option.

Note that it's better to use -filter:v -fps=fps=... instead of -r as the latter may be inaccurate. Although the ticket is marked as fixed, I still did experience some issues, so better play it safe.

blutorange
  • 163
  • 2
  • 12
  • 7
    Feb 2016: as of ffmpeg 2.1, the accurate seek option is now default - https://trac.ffmpeg.org/wiki/Seeking – mafrosis Feb 07 '16 at 07:19
  • 1
    `bc` is not a native Ubuntu package, instead one can use bash: `let "i = $i * 60"`. BTW - excellent idea – gilad mayani Dec 12 '17 at 16:01
  • 3
    Good tip adding `-ss` before `-i`. Otherwise, the whole video will be decoded and the unrequired frames will be discarded – MoustafaAAtta Jan 16 '18 at 01:42
  • 2
    Since this is top of google, I'd like to note that in 2018 this is still an approach that yields dividends. The best result seems to be running one `ffmpeg` per core of your host - which (for bmp) yields near-linear improvements in speed (until you hit some other bottleneck, like disk). – Knetic Apr 11 '18 at 23:06
  • Could you explain why the second method be faster than the first method much more? They may also do the same work: locate the frame. The first method locate a frame at 10-frame intervals, and the second method locate a specified frame in many times. – Domi.Zhang Mar 08 '19 at 01:58
12

If you know exactly which frames to extract, eg 1, 200, 400, 600, 800, 1000, try using:

select='eq(n\,1)+eq(n\,200)+eq(n\,400)+eq(n\,600)+eq(n\,800)+eq(n\,1000)' \
       -vsync vfr -q:v 2

I'm using this with a pipe to Imagemagick's montage to get 10 frames preview from any videos. Obviously the frame numbers you'll need to figure out using ffprobe

ffmpeg -i myVideo.mov -vf \
    select='eq(n\,1)+eq(n\,200)+eq(n\,400)+eq(n\,600)+eq(n\,800)+eq(n\,1000)',scale=320:-1 \
    -vsync vfr -q:v 2 -f image2pipe -vcodec ppm - \
  | montage -tile x1 -geometry "1x1+0+0<" -quality 100 -frame 1 - output.png

.

Little explanation:

  1. In ffmpeg expressions + stands for OR and * for AND
  2. \, is simply escaping the , character
  3. Without -vsync vfr -q:v 2 it doesn't seem to work but I don't know why - anyone?
Voy
  • 2,938
  • 1
  • 30
  • 43
  • `eq(n\,1)` would extract 2nd frame (order starts at 0) – Zimba Feb 15 '21 at 04:59
  • 1
    `-q:v` is an alias for `-qscale:v` (https://trac.ffmpeg.org/wiki/Encode/MPEG-4) and controls image quality. `-vsync vfr` is the video sync method (you first need to understand `-vf` which is a filtergraph) . According to the docs `vfr` means `Frames are passed through with their timestamp or dropped so as to prevent 2 frames from having the same timestamp.` *I think* this is to avoid the default option `cfr` as stated here https://ffmpeg.org/ffmpeg.html – Antonio Gomez Alvarado Mar 02 '21 at 08:59
5

I tried it. 3600 frame in 32 seconds. your method is really slow. You should try this.

ffmpeg -i file.mpg -s 240x135 -vf fps=1 %d.jpg
Kübra
  • 121
  • 1
  • 9
3

This worked for me

ffmpeg -i file.mp4 -vf fps=1 %d.jpg

Kishan Vaghela
  • 7,008
  • 3
  • 36
  • 63
3

Output one image every minute, named img001.jpg, img002.jpg, img003.jpg, etc. The %03d dictates that the ordinal number of each output image will be formatted using 3 digits.

ffmpeg -i myvideo.avi -vf fps=1/60 img%03d.jpg

Change the fps=1/60 to fps=1/30 to capture a image every 30 seconds. Similarly if you want to capture a image every 5 seconds then change fps=1/60 to fps=1/5

SOURCE: https://trac.ffmpeg.org/wiki/Create a thumbnail image every X seconds of the video

mouse_freak
  • 31
  • 2
  • 4
1

In my case I need frames at least every second. I used the 'seek to' approach above but wondered if I could parallelize the task. I used the N processes with FIFO approach here: https://unix.stackexchange.com/questions/103920/parallelize-a-bash-for-loop/216475#216475

open_sem(){
  mkfifo /tmp/pipe-$$
  exec 3<>/tmp/pipe-$$
  rm /tmp/pipe-$$
  local i=$1
  for((;i>0;i--)); do
    printf %s 000 >&3
  done
}
run_with_lock(){
    local x
    read -u 3 -n 3 x && ((0==x)) || exit $x
    (
    "$@" 
    printf '%.3d' $? >&3
    )&
}
N=16
open_sem $N
time for i in {0..39} ; do run_with_lock ffmpeg -ss `echo $i` -i /tmp/input/GOPR1456.MP4  -frames:v 1 /tmp/output/period_down_$i.jpg  & done

Essentially I forked the process with & but limited the number of concurrent threads to N.

This improved the 'seek to' approach from 26 seconds to 16 seconds in my case. The only problem is the main thread does not exit cleanly back to the terminal since stdout gets flooded.

Ondra Žižka
  • 36,997
  • 35
  • 184
  • 250
Tycon
  • 103
  • 2
  • 12
1

This is simpler than all the other commands so far:

ffmpeg -i input.mp4 '%d.png'
makeworld
  • 187
  • 9
  • No need for `-pix_fmt rgba`. The PNG encoder will automatically choose the appropriate pixel format. – llogan Mar 08 '21 at 17:49
  • @llogan thanks, removed it with an edit. Got a source for that though? – makeworld Mar 08 '21 at 19:27
  • You can view a list of supported pixel formats for the PNG encoder with `ffmpeg -h encoder=png`. See [`avcodec_find_best_pix_fmt_of_list` documentation](https://ffmpeg.org/doxygen/trunk/group__lavc__misc__pixfmt.html#ga9e74b43a3433ccfe836814f0a6371aa0). – llogan Mar 08 '21 at 19:52