如何使用 FFmpeg 获取给定时间戳之前最近关键帧的时间戳?

如何使用 FFmpeg 获取给定时间戳之前最近关键帧的时间戳?

我想要一个快速而准确的 FFmpeg 查找命令。我发现

解决方法是,我们-ss同时应用输入(快速寻道)和输出(精确寻​​道)。但是:如果输入寻道不精确,我们如何确保寻道位置是精确的?


例如:如果我们想要搜索到 00:03:00,命令如下:

ffmpeg -ss 00:02:30 -i <INPUT> ... -ss 00:00:30 <OUTPUT>

第一个搜索-ss将搜索到其他地方,而不是00:02:3000:02:31应用第二个搜索后,最终结果将是00:03:01-不是这正是我们想要的。对吗?

第一个要-ss搜索哪里?它会搜索最接近的关键帧吗00:02:30

如果是这样,那么我的想法是这样的 - 如果我错了请纠正我:第一次搜索之后,我们得到了结果的时间戳(在这个例子中是00:02:31:),然后我们在适当的时间应用第二次搜索,在本例中是00:00:29

问题是:我们如何获取第一个搜索结果的时间戳?

答案1

从字面上回答你的标题问题:你可以获得 I 帧列表

ffprobe -select_streams v -show_frames <INPUT> 

您可以通过添加来进一步将其限制为必要的输出-show_entries frame=pkt_pts_time,pict_type

要查看哪个帧最接近(晚于)某个时间戳,您首先需要找出所有关键帧的时间戳,例如使用awk

首先,定义您要查找的时间,例如 2:30m,相当于 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

例如,这将返回150.400000


注意,使用-ssbefore时-i,FFmpeg 将定位关键帧以前的到达搜索点,然后将负 PTS 值分配给所有后续帧,直到到达搜索点。播放器应解码但不显示具有负 PTS 的帧,并且视频应准确开始。

有些播放器没有正确遵守这一点,会显示黑屏或乱码。在这种情况下,可以使用上述脚本来查找关键帧的 PTS您的搜索点,并使用该点从关键帧开始搜索。但是,这并不准确。

请注意,如果你想在搜索时保持超高精度——并且保持与许多播放器的兼容性——你应该将视频转换为任何无损、仅内部编码的格式,这样你可以在任何一点进行剪切,然后重新编码。但这并不快。

答案2

我知道这个问题已经有好几年了,但是最新版本的 ffprobe 有能力跳帧。你可以传入-skip_frame nokey仅报告关键帧(I 帧)的信息。这可以为您节省大量时间!对于 2GB 1080p MP4 文件,如果没有跳过帧,则需要 4 分钟。添加跳过参数后,只需 20 秒。

命令:

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

结果:

[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]

因此结果将只包含有关关键帧的信息。

答案3

构建于slhck 的回答,这是一个 bash 函数,它将返回在N秒之前发生的最近的关键帧。

这也有助于-read_intervals确保 ffprobe 仅在N秒前 25 秒开始寻找关键帧。此技巧以及在找到时间戳时让 awk 退出可大大加快速度。

function ffnearest() {
  STIME=$2; export STIME;
  ffprobe -read_intervals $[$STIME-25]% -select_streams v -show_frames -show_entries frame=pkt_pts_time,pict_type -v quiet "$1" |
  awk -F= '
    /pict_type=/ { if (index($2, "I")) { i=1; } else { i=0; } }
    /pkt_pts_time/ { if (i && ($2 <= ENVIRON["STIME"])) print $2; }
    /pkt_pts_time/ { if (i && ($2 > ENVIRON["STIME"])) exit 0; }
  ' | tail -n 1
}

使用示例:

➜ ffnearest input.mkv 30
23.941000

我使用它来修剪视频文件而无需重新编码。由于您无法在不重新编码的情况下添加新的关键帧,因此我使用它来ffnearest在想要剪切之前寻找关键帧。以下是一个例子:

ffmpeg  -i input.mkv -ss 00:00:$(echo "$(ffnearest input.mkv 30) - 0.5" | bc)  -c copy -y output.mkv;

-ss请注意,对于该示例,如果您要搜索的时间超过前 60 秒,则可能需要更改传递的参数的格式。

(令人恼火的是,告诉 ffmpeg 精确地寻找关键帧的时间戳似乎会让 ffmpeg 在输出中排除该关键帧,但从关键帧的实际时间戳中减去 0.5 秒就可以了。对于 bash,您需要使用来bc评估带有小数的表达式,但在 zsh 中-ss 00:00:$[$(ffnearest input.mkv 28)-0.5]可以工作。)

答案4

如果你想获取 I 帧的信息,你可以使用

ffprobe -i input.mp4 -v quiet -select_streams v -show_entries frame=pkt_pts_time,pict_type|grep -B 1 'pict_type=I'

相关内容