如何使用ffmpeg提取准确时间的视频片段?

53
这不是一个特别新的问题领域,但我尝试了那里提出的建议,没有什么运气。所以,我的故事是这样的:
我有一段15秒的直接从相机中拍摄的.mov视频,我想从中提取一个特定的片段,我可以通过开始时间和结束时间(以秒为单位)来识别它。我开始尝试所谓的“复制提取”:获取9到12秒的片段。
ffmpeg -i test.mov -vcodec copy -acodec copy -ss 9 -to 12 test-copy.mov

这是一个不错的开始,但是在剪辑的开头和结尾有一些黑色帧,我不能接受这种情况——必须从原始文件中进行干净的剪辑。因此,我尝试将原始文件重新编码为一个新的、修剪过的剪辑:

ffmpeg -i test.mov -ss 00:00:09 -t 00:00:03 test-out.mov

这样会更好,但还不够完美:片段开头不再有黑色边框,但结尾仍然存在。
在进一步浏览和阅读后,我怀疑问题在于ffmpeg在原始视频中缺少关键帧而难以找到正确的点。因此,我用几种不同的方式重新编码了原始视频,以添加关键帧(可能)。由于我想能够在秒数的边界处选择视频(“从第9秒到第12秒”),所以我试着复制网上的各种建议。
ffmpeg -i test.mov -force_key_frames "expr:gte(t, n_forced)" test-forced.mp4

and

ffmpeg -i test.mov -g 1 test-g-inserted.mp4

我将这些视频文件以mp4格式构建,因为有评论说必须使用mp4容器来支持关键帧搜索,但实际上我只是在试探。之后,我尝试对这些新视频进行像以前一样的提取,但没有成功,两者似乎差不多; 开始的部分没问题,但结尾仍然有黑帧。(顺便提一下,test-forced.mp4和test-g-inserted.mp4也有冗余的黑帧)。
所以:我还是卡住了,希望能寻求帮助。是否有人知道我做错了什么?我感觉我已经很接近成功了,但还需要去除结尾的黑帧...

你尝试过设置输出视频文件的持续时间吗?我曾经遇到过同样的问题,并使用输出视频持续时间解决了它:./ffmpeg -ss 0:1:30.00 -i Abduls.mp4 -t 0:0:30.00 -filter_complex "[0:v]trim=duration=30[b]; [b]scale=720:trunc(ow/a/2)*2[a];" -map [a] -map 0:a -c:a copy -c:v libx264 -t 30 short3.mp4 - M Abdul Sami
6个回答

55

首先假设您知道开始和停止持续时间,我们将在该持续时间添加关键帧。

ffmpeg -i a.mp4 -force_key_frames 00:00:09,00:00:12 out.mp4

大多数情况下,您可以直接完美地裁剪视频,但在您的情况下,这并没有帮助到您;因此,我们通过上述命令来解决这个问题。请注意,不要添加过多的关键帧,因为根据Ffmpeg文档的规定,这可能会在编码时造成问题。

现在,您可以再次尝试从特定时间裁剪视频。

ffmpeg -ss 00:00:09 -i out.mp4 -t 00:00:03 -vcodec copy -acodec copy -y final.mp4

这将解决问题,因为我们已经在剪切点的起始和结束位置手动添加了关键帧。它对我有用。
干杯 :)

2
感谢提供指引!虽然这个方法在时间上表现良好,但它需要重新编码,而我想避免这种情况,并且在修剪视频的末尾仍然会出现黑帧。进一步的实验表明,我的第一个无需重新编码的“复制”尝试与此方法的时间略有偏差,尽管它会在开头留下黑帧,但事实证明我对修剪后的视频所做的操作(省略了长篇故事)忽略了那些帧 - 也许它们是播放器的产物?所以,再次感谢,即使最终我选择使用复制方法。 - Jim Miller
1
跟进:我会批准这个答案,但如果其他人有任何关于如何避免那些黑框的想法,将不胜感激... - Jim Miller
@JimMiller,您能否提供您视频的链接?我想查看一下。 - BlueSword
@JimMiller 我不知道你做错了什么,或者可能是更新了你的 Ffmpeg,因为我使用你提供的视频进行了处理,结果非常完美。在我运行命令后,检查一下我从你提供的视频中以 20 fps 提取的帧,没有一个黑色帧。查看结果 - BlueSword
1
哎呀——错过了。但是很有趣:我也在你的文件中看到了黑色帧,也就是说,当我在QuickTime Player中打开文件时,在最后一个可见帧之后,视频仍然继续播放一小段时间,而且我可以在屏幕上看到全黑的播放器条。这增加了我对所有这些都是播放器产生的疑虑——也许实际视频长度与播放器认为存在的长度之间存在差异?无论如何,我想我得到了一个有效的文件,我没问题了;非常感谢你的帮助! - Jim Miller
显示剩余3条评论

13
我认为问题在于问题和其他回答将“-ss”用作输出文件的选项,而不是输入文件的选项。 大多数ffmpeg选项不是全局的,而是仅适用于它们之前的文件。 通常不清楚选项应该放在哪里,因此有时需要尝试和错误。
如果正确使用,放在输入文件之前,“-ss”和“-t”对我来说很好用。 当在输出中包含音频时,我必须将“-shortest”用作输出文件的选项,否则我会得到2分钟的音频和2秒的视频。
ffmpeg版本N-67413-g2a88c74(基本上是从2014年12月14日的git源)。
这是我最近用于制作剪辑的命令行。 (实际上进行了调整,以便更好地说明,因为我为此保留了音频,并且没有将其减速。) ffmpeg -ss 120.2 -t 0.75 -i ../mcdeint.60p.lossless264.slow.mkv -c:a libopus -shortest -aspect 16:9 -preset veryslow -x264-params nr = 250:ref = 6 -crf 22 -movflags + faststart clip2.mkv 使用“-c:a copy”(源具有AC3音频),播放开始时mplayer会出现问题。 它可能从包含开始的音频帧的开头获取音频,然后必须在容器中使用a / v偏移量。 在启动时,音频需要花费一小部分时间才能超过该偏移量的视频,并且在此之前,视频以非常低的FPS播放。 因此,我对音频进行了编码。 Opus和pcm_s16le都不能进入mp4,因此我在此示例中使用了mkv容器。
源是从NTSC DVD(可能来自DV相机)的BFF交错视频的非常慢的yadif = 3:1,mcdeint = 3:1:10的输出(“-qp 0”)的无损x264编码。 它不是全部I帧,而是具有正常关键帧间隔的P帧。
将-ss调整0.2秒正好达到了我所希望的效果,因此ffmpeg必须处理解码到所需点。 不仅仅是想要一个I帧的巧合。 也许“-accurate_seek”是默认值? 当我使用ffvhuff无损源作为输入时,我也获得了相同的结果(字节对字节相同的gif输出)。 (但它运行得更快,因为它不必解码到请求的点。)
另一个可能相关的选项是 -seek2any,但它说“在 demuxer 级别上搜索到非关键帧”,听起来好像允许你以会产生损坏输出的方式进行搜索。 (即,在实际生成当前帧所需的参考帧之前开始解码,只使用全灰色?)
我没有尝试使用 -c:v copy,因为我正在剪切一个非常短的循环片段,因此我知道不会有需要关键帧的 I 帧。
这是我实际使用的命令行,用于制作没有声音的短慢动作片段。 ffmpeg -ss 120.2 -t 0.75 -i ../vid.yadif3.1,mcdeint3.1.10.ffvhuff.mkv -an -shortest -aspect 16:9 -c:v libx264 -preset veryslow -x264-params nr=250:ref=6 -crf 22 -filter:v "setpts=3.0*PTS" -movflags +faststart -r 20 clip.mp4 请注意,-r 20 非常重要,因为与 mkv 不同,ffmpeg 的 MP4 输出仅限于恒定帧速率(编辑:因为 mp4 复用器的默认值不是 -vsync vfr)。如果不告诉它不同,它将设置输出 FPS = 输入 FPS,并在需要时复制帧以实现这一点。x264 和动态 gif(具有透明度)都可以非常有效地编码重复帧,但这仍然很愚蠢。
在将其烹制为示例之前,我已经分成了两步,首先输出到 mkv,然后使用 ffmpeg -i clip.mkv -c:v copy -movflags +faststart -r 20 clip.mp4 进行重新复用。顺便说一句,在复用时可以更改视频的帧率,而无需进行 xcoding,只是不能使用 ffmpeg。(链接)。但无论如何,当制作 mkv 时,ffmpeg 只向 libx264 发送了 45 帧,即使它认为自己正在制作一个 60fps 的 2.2 秒视频。不要使用 ffmpeg 与 mp4 一起处理可变 FPS 的内容。
编辑:原来 ffmpeg 对 mkv 输出默认为 -vsync vfr,但对 mp4 不是。使用 -vsync vfr,ffmpeg 可以很好地将 VFR 写入 mp4 输出。
如果决定不使用 HTML5 视频放置 gif 输出,则再次运行上述命令。

ffmpeg -ss 120.2 -t 0.75 -i ../vid.yadif3.1,mcdeint3.1.10.ffvhuff.mkv -an -shortest -aspect 16:9 -filter:v "setpts=3.0*PTS,scale=854x480" -r 20 clip.gif

由于gif容器不存储宽高比,因此在播放时无法自动缩放,所以我必须使用scale=。我的720x480像素的16:9视频在播放时被缩放为854x480。实际上应该是853.333,但这会被四舍五入,然后ffmpeg将853x480存储在mkv容器中,因此始终使用- aspect 16:9,这样我的mp4将存储正确的宽高比[SAR 32:27 DAR 16:9],而不是[SAR 186:157 DAR 279:157]


编辑:-vsync vfr 不是 mp4 的默认设置,但是对于 mkv 来说是。 (默认设置由复用器选择,但是 ffmpeg 的 mp4 复用器确实支持 VFR。) - Peter Cordes

9
在森林里惊醒了一只熊 ‍♂️。我没有看到这个帖子中提到 -to 标志的任何提及,而我发现它比 -t 标志更有用。一个对我来说很好用的示例命令:
ffmpeg -y -i [INPUT.file] -ss 00:42:42 -to 00:84:84 -codec copy [OUTPUT.file]

1
是的!在这里展示的所有命令中,这个(ffmpeg 4.2.2)对我起作用,并且比其他任何东西都要快得多。如果您需要剪切亚秒段,请使用以下格式 00:00:00.000 - Alex Volkov
@AlexVolkov:当然,只使用“-c copy”重新复用远比解码+编码快得多,并且不会失去任何视频质量。 但是,如果您想要的剪辑没有从关键帧开始,则可能需要在此方式下接受一些时间不准确性。 如果要制作非常短的剪辑,则这可能很重要,就像我在我的答案中提到的那样。 (或者如果源格式或比特率与您要分发的格式或比特率不同。) - Peter Cordes

5

[更新:这个答案可能不正确 - 请参见评论]

不需要添加关键帧;正如Peter所说,这只是一个将选项放在正确顺序的问题。但是,请查看https://trac.ffmpeg.org/wiki/Seeking,以获取如何正确使用它的权威官方指南。


给那位点踩了这篇文章的人:请友善地在评论中解释一下为什么,这样我才能解决你的不满。 - Adam Spiers
8
我觉得原因是答案不够完整,只是简单地说:“其他答案正确,在这里检查其他内容”。 - Javi
4
问题标题提到“时间精确”,而提问者在使用编解码器复制。如果您阅读链接指南中关于寻找的“同时进行编解码器复制”部分,您将会看到它明确说明,使用编解码器复制时,作为输入选项的“-ss”可能不准确,因为FFmpeg被强制只能使用/拆分i帧。文档基本上是说它不会准确,这也是我的经验,并且使这个答案失去了效力。 - jbielick
感谢@jbielick,这听起来是一个公正的观点。我写下这个答案已经5.5年了,所以我真的记不清了,但我怀疑我基于简单的实验和匆忙阅读链接的文章来回答这个问题 - 如果是这样的话,也许我在我的情况下准确无误,但不一定适用于所有情况。 - Adam Spiers

0

我之前尝试了上述示例,但每次结果都是音视频不同步。

解决方法是使用trimatrim过滤器,并使用以秒为单位的时间戳,因为[-][<HH>:]<MM>:<SS>[.<m>...]格式无法正常工作,所以我必须使用[-]<S>+[.<m>...]格式,如man ffmpeg-utils所述。

首先,我使用mpv --osd-fractions从我的视频中提取了精确的时间戳,然后将其转换为秒格式:

start 00:11:59.560
end 00:14:31.080

start 719.56
end 871.08

作为最后一步,运行

ffmpeg -i in.mp4 -vf "trim=start=719.56:end=871.08,setpts=PTS-STARTPTS" -af "atrim=start=719.56:end=871.08,asetpts=PTS-STARTPTS" out.mp4

请注意,对于 trimatrim 过滤器,您需要两次指定时间戳,但是生成的视频将正确同步。

0

我也想知道。到目前为止,我已经无损转换了我的视频,提取了一个片段并在编辑后重新编码它们:

ffmpeg -i input -ss 9.48 -t 3.52 -c:v libx264 -crf 0 -g 1 -c:a copy TempIframe.mp4

但这是一个耗时且损失的过程...

我尝试了以下方法,但没有成功:

ffmpeg -i source.mp4 -ss 1 -c:v copy -seek2any 1 -avoid_negative_ts 1 -safe 1 -c:a copy segment.mp4

1
唯一让它快速的方法是使用c:v copy。转码总是需要很长时间。 - Konstantin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接