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


18

我想要一个快速且准确的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

问题是:如何获得首次搜索结果的时间戳?

Answers:


18

要从字面上回答标题的问题:您可以获取带有以下内容的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 in.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之前-i,FFmpeg的将定位关键帧以前来寻找点,然后直到它到达寻求点分配负PTS值到所有后续帧起来。播放器应解码而不显示带有负PTS的帧,并且视频应正确开始。

一些播放器不正确地尊重这一点,将显示黑色视频或垃圾内容。在这种情况下,以上脚本可用于您的搜索点之后找到关键帧的PTS ,并使用该脚本从关键帧开始搜索。但是,这将是不准确的。

请注意,如果您想在寻找时保持超级准确并保持与许多播放器的兼容性,则应该将视频转换为任何无损的仅限内部格式,可以在任何位置进行剪切,然后重新编码。但这不会很快。


1
谢谢,我不是要制作视频编辑器,但我确实想进行精确的视频搜索,其间隔应小于0.5秒。
jackode

1
您可能可以使用的PTS玩弄ffprobe。如果没有,那么任何中间格式都可以,例如ProRes 422,DNxHD,它们在视觉上是无损的并且仅在帧内。或者,您使用HuffYUV之类的东西,但是,那么,您当然会再次失去“快速”方面。
slhck

您为命令使用了什么版本的ffprobe,因为我说的是Unrecognized option 'select_streams'
jackode

2
您很接近,该select_streams选项于2012年10月添加。:)您可以没有它,但是随后您还将获得音频帧的信息,介于两者之间。
slhck

2
请注意,您可以在ffmpeg行中添加此行,使其仅输出必要的2个字段,而不是被awk丢弃的很多内容:-show_entries frame = pkt_pts_time,pict_type
Jannes

7

我知道这个问题已经有好几年了,但是最新版本的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 D:\ 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]

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


1

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

这还可以-read_intervals确保ffprobe仅在秒之前25秒开始查找关键帧N。找到时间戳后,此技巧和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]有效。)


在我构图后,这将给出下一帧时间。
Ehsan Chavoshi

0

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

ffprobe -i input.mp4 -v quiet -select_streams v -show_entries frame=pkt_pts_time,pict_type|grep -B 1 'pict_type=I'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.