2

I'm looking a way to extract every second (2nd) I-Frame using ffmpeg and save them as a new timelapse video.

So far I managed to save all I-frames by the following command:

ffmpeg -i $FILE -vf "select='eq(pict_type,I)',setpts=N/FRAME_RATE/TB" -r 29.97 -vcodec libx264 -b:v 62M -an ./enc/${FILE}_cnv.mp4

but I need twice less I-frames in the resulting video. For example, if the I-frames in the original video are 1-8-16-24-32-40..., I need only 1-16-32-48... Is there a way to extract them without making a temporary video with all keyframes?

Update: since no universal solution was found, I decided to cheat with: -vf "select='eq(pict_type,I)*not(mod(n,16))',setpts=N/FRAME_RATE/TB"

tol
  • 31
  • 3

2 Answers2

1

At first glance it seems like you should be able to use a second step in your filter to select only even or odd numbered I frames, but after testing it doesn't seem like this is actually possible.

When looking at frame numbers we have two choices: n, which seems to be the absolute frame number, and selected_n, which seems to be the filtered frame number. The ffmpeg filter docs are not very clear on how these values are defined.

In my tests, it looks like if you use a filter like this:

select='eq(pict_type,I)*not(mod(n,2))'

You will get the same number of frames as you would if you omitted the *not(mod(n,2)), which I assume is because I frames will always fall on odd-numbered n due to how the encoding works.

If we try using selected_n instead, though, I find that only a single frame is ever selected. This makes sense because the filter can't ever match an even-number of selected frames, so it will only ever match the first one.

Given this, I think your only choice here is to use a 2-stage approach, where you use one run of ffmpeg to get your iframes, then a second run of ffmpeg to drop every other frame. Something like:

ffmpeg -i $FILE -vf "select='eq(pict_type,I)' $TMPFILE
ffmpeg -i $TMPFILE -vf "select='not(mod(n,2))' $CONVERTED_FILE
Mel Stanley
  • 375
  • 2
  • 8
  • 1
    The `n` will refer to the total frame count, not just I-frames. If you add another select filter with that mod expr alone, it will work. – Gyan Nov 27 '22 at 09:25
  • eq(pict_type,I)*mod(n,2) doesn't work: it makes cpu load close to 100% during a minute and then returns an empty output file... – tol Nov 27 '22 at 09:59
  • Thanks for spending time and testing. I also made many tests and read a lot of ffmpeg manual but didn't find a universal solution that will allow to make it with one conversion. Finally I decided to cheat and used -vf "select='eq(pict_type,I)*not(mod(n,16))',setpts=N/FRAME_RATE/TB" This worked for me because my files have fixed GOP=8. It will take I-frames at positions 1, 16, 32, 48... and skip 8, 24, 40 etc. I compared files source/result files in Avidemux and made sure that the result is correct. Of course, this is not universal solution and it will not work with different video files – tol Nov 30 '22 at 07:41
0

Tell the decoder to only emit keyframes and then select every other frame.

ffmpeg -skip_frame nokey -i $FILE -vf "select='not(mod(n\,2))',setpts=N/FRAME_RATE/TB" -c:v libx264 -b:v 62M -an ./enc/${FILE}_cnv.mp4
Gyan
  • 85,394
  • 9
  • 169
  • 201
  • This doesn't seem to work. It looks like it takes every second frame but not every second I-frame, i.e. it just doubles the speed of the original file. 60 seconds video become 30 seconds, but it should be 3-4 seconds if the job is done correctly. I also tried "-skip_frame nointra" but the result is the same. – tol Nov 27 '22 at 09:58