How to get time stamp of closest keyframe before a given timestamp with FFmpeg?

Solution 1:

To literally answer your title's question: You can get a list of I-frames with

ffprobe -select_streams v -show_frames <INPUT> 

You can further limit this to the necessary output by adding -show_entries frame=pkt_pts_time,pict_type.

To see which frame is closest (comes after) a certain timestamp, you'd first need to find out all timestamps of the keyframes, for example with awk.

First, define the time you want to look for, e.g., 2:30m which equals to 150s.

ffprobe -select_streams v -show_frames \
        -show_entries frame=pkt_pts_time,pict_type -v quiet input.mp4 |
awk -F= '/pict_type=/ { if (index($2, "I")) { i=1; } else { i=0; } }
         /pkt_pts_time/ { if (i && ($2 >= 150)) print $2; }
        ' |
head -n 1

For example, this would return 150.400000.


Note that when using -ss before -i, FFmpeg will locate the keyframe previous to the seek point, then assign negative PTS values to all following frames up until it reaches the seek point. A player should decode but not display frames with negative PTS, and the video should start accurately.

Some players do not properly respect this and will display black video or garbage. In this case, the above script can be used to find the PTS of the keyframe after your seek point, and use that to start seeking from the keyframe. This, however, will not be accurate.

Note that if you want to be super accurate while seeking—and retain compatibility with many players—you should probably convert the video to any lossless, intra-only format, where you could cut at any point, and then re-encode it. But this will not be fast.

Solution 2:

I understand this question is several years old, but the latest version of ffprobe has the ability to skip frames. You can pass in -skip_frame nokey to report info only on the key frames (I-frames). This can save you a lot of time! On a 2GB 1080p MP4 file it used to take 4 minutes without the skip frames. Adding the skip parameter it only takes 20 seconds.

Command:

ffprobe -select_streams v -skip_frame nokey -show_frames \
        -show_entries frame=pkt_pts_time,pict_type test.mp4

Results:

[FRAME]
pkt_pts_time=0.000000
pict_type=I
[/FRAME]
[FRAME]
pkt_pts_time=3.753750
pict_type=I
[/FRAME]
[FRAME]
pkt_pts_time=7.507500
pict_type=I
[/FRAME]
[FRAME]
pkt_pts_time=11.261250
pict_type=I
[/FRAME]
[FRAME]
pkt_pts_time=15.015000
pict_type=I
[/FRAME]

So the results will only contain info regarding the key frames.