如何使用 ffmpeg 将 MPEG 视频分成 10 分钟的块?

Gab*_*iel 123 ffmpeg video-editing

开源或活跃的开发者社区通常需要在线发布大型视频片段。(聚会视频、露营、技术讲座……)因为我是一名开发人员而不是一名摄像师,所以我不想在高级 Vimeo 帐户上支付额外的费用。那么我如何获取 12.5 GB (1:20:00) MPEG 技术谈话视频并将其切成 00:10:00 片段以便轻松上传到视频共享站点?

War*_*ung 122

$ ffmpeg -i source-file.foo -ss 0 -t 600 first-10-min.m4v
$ ffmpeg -i source-file.foo -ss 600 -t 600 second-10-min.m4v
$ ffmpeg -i source-file.foo -ss 1200 -t 600 third-10-min.m4v
...
Run Code Online (Sandbox Code Playgroud)

将其包装成一个脚本以循环执行并不难。

请注意,如果您尝试根据从剪辑开始时的平均比特率和剪辑的文件大小估计ffprobe调用的持续时间输出来计​​算迭代次数,除非您提供参数,这会大大减慢其操作.-count_frames

另一件需要注意的事情是-ss选项在命令行上的位置很重要。我现在拥有它的地方很慢但准确。这个答案的第一个版本给出了快速但不准确的替代方案。链接的文章还描述了一种大多数情况下快速但仍然准确的替代方案,您需要为此付出一些复杂性。

抛开这些不谈,我不认为您真的希望每个剪辑都在 10 分钟内进行剪辑。这将在句子,甚至单词的中间放置切割。我认为您应该使用视频编辑器或播放器来找到相距不到 10 分钟的自然切入点。

假设您的文件采用 YouTube 可以直接接受的格式,您无需重新编码即可获取片段。只需将自然切点偏移量传递给ffmpeg,告诉它使用“复制”编解码器将编码的 A/V 原封不动地传递:

$ ffmpeg -i source.m4v -ss 0 -t 593.3 -c copy part1.m4v
$ ffmpeg -i source.m4v -ss 593.3 -t 551.64 -c copy part2.m4v
$ ffmpeg -i source.m4v -ss 1144.94 -t 581.25 -c copy part3.m4v
...
Run Code Online (Sandbox Code Playgroud)

-c copy参数告诉它按原样将所有输入流(音频、视频以及可能的其他流,例如字幕)复制到输出中。对于简单的 A/V 程序,它相当于更详细的 flags-c:v copy -c:a copy或旧式的 flags -vcodec copy -acodec copy。当您只想复制一个流但重新编码另一个流时,您将使用更详细的样式。例如,很多年前,QuickTime 文件有一种普遍做法,即使用 H.264 视频压缩视频,但将音频保留为未压缩的 PCM;如果您今天遇到了这样的文件,您可以对其进行现代化改造,-c:v copy -c:a aac只重新处理音频流,而视频原封不动。

上面第一个命令之后的每个命令的起点是前一个命令的起点加上前一个命令的持续时间。

  • “每个剪辑精确地剪辑 10 分钟”是一个很好的观点。 (2认同)

Jon*_*Jon 117

这是单行解决方案

ffmpeg -i input.mp4 -c copy -map 0 -segment_time 00:20:00 -f segment output%03d.mp4
Run Code Online (Sandbox Code Playgroud)

请注意,这不会为您提供准确的拆分,但应该适合您的需求。它会在指定的时间之后的第一帧处剪切segment_time,在上面的代码中,它将在 20 分钟标记之后。

如果您发现只有第一个块可以播放,请尝试-reset_timestamps 1按照评论中的说明添加。

ffmpeg -i input.mp4 -c copy -map 0 -segment_time 00:20:00 -f segment -reset_timestamps 1 output%03d.mp4
Run Code Online (Sandbox Code Playgroud)

  • 发现添加 `-reset_timestamps 1` 为我解决了这个问题 (14认同)
  • 如果您重视视频质量,它实际上会为您提供*非常* 准确的分割。它不是基于特定时间分割,而是在请求时间之后最近的关键帧上分割,因此每个新段总是以关键帧开始。 (8认同)
  • 单位是什么?8s?8分钟?8小时? (4认同)
  • 在 Mac 上,我发现这导致了 N 个输出视频块,但只有第一个是有效的、可见的 MP4。其他 N-1 块是没有音频的空白视频(全黑)。为了使它工作,我需要像这样添加 reset_timestamps 标志:ffmpeg -i input.mp4 -c copy -map 0 -segment_time 8 -f segment -reset_timestamps 1 output%03d.mp4。 (2认同)

小智 11

一种替代可读的办法是

ffmpeg -i input.mp4 -ss 00:00:00 -to 00:10:00 -c copy output1.mp4
ffmpeg -i input.mp4 -ss 00:10:00 -to 00:20:00 -c copy output2.mp4

/**
* -i  input file
* -ss start time in seconds or in hh:mm:ss
* -to end time in seconds or in hh:mm:ss
* -c codec to use
*/
Run Code Online (Sandbox Code Playgroud)

是常用 FFmpeg 命令的来源和列表。


小智 8

如果您想创建真正相同的块,必须强制 ffmpeg 在每个块的第一帧上创建 i-frame,以便您可以使用此命令创建 0.5 秒的块。

ffmpeg -hide_banner  -err_detect ignore_err -i input.mp4 -r 24 -codec:v libx264  -vsync 1  -codec:a aac  -ac 2  -ar 48k  -f segment   -preset fast  -segment_format mpegts  -segment_time 0.5 -force_key_frames  "expr: gte(t, n_forced * 0.5)" out%d.mkv
Run Code Online (Sandbox Code Playgroud)


小智 7

之前遇到过同样的问题,并编写了一个简单的 Python 脚本来做到这一点(使用 FFMpeg)。可在此处获得:https : //github.com/c0decracker/video-splitter,并粘贴在下方:

#!/usr/bin/env python
import subprocess
import re
import math
from optparse import OptionParser
length_regexp = 'Duration: (\d{2}):(\d{2}):(\d{2})\.\d+,'
re_length = re.compile(length_regexp)
def main():
    (filename, split_length) = parse_options()
    if split_length <= 0:
        print "Split length can't be 0"
        raise SystemExit
    output = subprocess.Popen("ffmpeg -i '"+filename+"' 2>&1 | grep 'Duration'",
                              shell = True,
                              stdout = subprocess.PIPE
    ).stdout.read()
    print output
    matches = re_length.search(output)
    if matches:
        video_length = int(matches.group(1)) * 3600 + \
                       int(matches.group(2)) * 60 + \
                       int(matches.group(3))
        print "Video length in seconds: "+str(video_length)
    else:
        print "Can't determine video length."
        raise SystemExit
    split_count = int(math.ceil(video_length/float(split_length)))
    if(split_count == 1):
        print "Video length is less then the target split length."
        raise SystemExit
    split_cmd = "ffmpeg -i '"+filename+"' -vcodec copy "
    for n in range(0, split_count):
        split_str = ""
        if n == 0:
            split_start = 0
        else:
            split_start = split_length * n
            split_str += " -ss "+str(split_start)+" -t "+str(split_length) + \
                         " '"+filename[:-4] + "-" + str(n) + "." + filename[-3:] + \
                         "'"
    print "About to run: "+split_cmd+split_str
    output = subprocess.Popen(split_cmd+split_str, shell = True, stdout =
                              subprocess.PIPE).stdout.read()
def parse_options():
    parser = OptionParser()
    parser.add_option("-f", "--file",
                      dest = "filename",
                      help = "file to split, for example sample.avi",
                      type = "string",
                      action = "store"
    )
    parser.add_option("-s", "--split-size",
                      dest = "split_size",
                      help = "split or chunk size in seconds, for example 10",
                      type = "int",
                      action = "store"
    )
    (options, args) = parser.parse_args()
    if options.filename and options.split_size:
        return (options.filename, options.split_size)
    else:
        parser.print_help()
        raise SystemExit
if __name__ == '__main__':
    try:
        main()
    except Exception, e:
        print "Exception occured running main():"
        print str(e)
Run Code Online (Sandbox Code Playgroud)

  • 您好,欢迎来到该网站。请不要只发布链接的答案。虽然您的脚本本身可能会是一个很好的答案,但指向它的链接不是答案。它是_指向_答案的路标。更多关于[这里](http://meta.stackexchange.com/q/225370/203101)。由于您提供了链接,因此我继续将脚本包含在您的答案正文中。如果您对此有异议,请完全删除答案。 (3认同)

Joh*_*ard 6

只需使用 ffmpeg 中内置的内容即可完成此操作。

ffmpeg -i invid.mp4 -threads 3 \
       -vcodec copy -f segment -segment_time 10:00 \
       cam_out_h264_%02d.mp4
Run Code Online (Sandbox Code Playgroud)

这将是在相关的关键帧将输出分割成约10分钟块,拆分,以及文件 cam_out_h264_01.mp4cam_out_h264_02.mp4等等。

  • @Jasper 您可以使用 `--reset_timestamps` 标志来撤消该操作 (2认同)