我想要一个快速而准确的 FFmpeg 查找命令。我发现这。
解决方法是,我们-ss
同时应用输入(快速寻道)和输出(精确寻道)。但是:如果输入寻道不精确,我们如何确保寻道位置是精确的?
例如:如果我们想要搜索到 00:03:00,命令如下:
ffmpeg -ss 00:02:30 -i <INPUT> ... -ss 00:00:30 <OUTPUT>
第一个搜索-ss
将搜索到其他地方,而不是00:02:30
。00: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
。
注意,使用-ss
before时-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'