开源或活跃的开发人员社区中经常需要在线发布大型视频片段。 (聚会视频,露营,技术讲座...)既然我是一名开发人员,而不是一名摄像师,所以我不想花很多钱在高级Vimeo帐户上。然后,如何拍摄12.5 GB(1:20:00)的MPEG技术演讲视频并将其切成00:10:00的片段,以便轻松上传到视频共享站点?

评论

特别感谢@StevenD容纳新标签。

#1 楼

$ 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
...


将其包装到脚本中以进行循环操作并不难。

请注意,如果您尝试根据持续时间来计算迭代次数来自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
...


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

以上每个命令的起点是前一个命令的开始点加上上一条命令的持续时间。

评论


“每个剪辑精确地剪切10分钟”是一个好主意。

–克里斯
2010-09-07 11:42

也许通过使用-show_packets参数可以使其更准确。

–rogerdpack
2011年6月13日21:36

如何把上面循环?

–kRazzy R
17年12月6日在19:32

我使用此cmd ya将其拆分为正确的,但是现在视频和音频没有同步,没有任何帮助

– Sunil Chaudhary
18年3月16日在6:08

@SunilChaudhary:A / V文件格式很复杂,并且并非所有软件都以相同的方式实现它们,因此ffm​​peg保持音频和视频流之间同步所需的信息可能不会出现在源文件中。如果您遇到这种情况,可以使用更复杂的拆分方法来避免此问题。

–沃伦·杨(Warren Young)
18年3月16日在21:32

#2 楼

这是单行解决方案:

ffmpeg -i input.mp4 -c copy -map 0 -segment_time 00:20:00 -f segment output%03d.mp4


请注意,这不能给您准确的分割,但应满足您的需求。相反,它将在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


评论


如果您重视视频质量,它实际上会为您提供非常准确的分割效果。它不是根据特定时间进行拆分,而是在请求的时间之后在最近的关键帧上进行拆分,因此每个新段始终以关键帧开始。

–马耳他
17-2-25在6:51

单位是多少? 8s? 8分钟? 8小时?

–user1133275
17 Mar 20 '17在20:58

@ user1133275第二

–乔恩
17 Mar 20 '17在21:11

在Mac上,我发现这导致N个输出视频块,但只有第1个是有效的,可视的MP4。其他N-1个块是无音频的空白视频(全黑)。为了使其正常工作,我需要像这样添加reset_timestamps标志:ffmpeg -i input.mp4 -c复制-map 0 -segment_time 8 -f segment -reset_timestamps 1 output%03d.mp4。

– jarmod
17年5月5日在15:32

发现添加-reset_timestamps 1为我解决了该问题

– jlarsch
17-10-30在8:27

#3 楼

较早面对相同的问题,并使用一个简单的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)
 


评论


下次请在评论中执行此操作。仅链接的答案在这里不是很喜欢,如果您在网站上做广告,也是如此。如果这是一个带有源代码的开源项目,也许是个例外,但是我现在冒着审查特权的风险,没有投票赞成删除您的答案。是的,您不能发表评论,但是在您收集了5个支持票(对于您而言,这似乎非常快)之后,您会发表评论。

–peterh-恢复莫妮卡
2014年11月21日,0:40



嗨,欢迎来到该网站。请不要只发布链接答案。尽管脚本本身可能会给出一个很好的答案,但指向该脚本的链接不是一个答案。这是指向答案的路标。在这里更多。既然您亲切地给出了链接,我便继续将脚本包含在您的答案正文中。如果您反对,请彻底删除答案。

– terdon♦
2014年11月21日,下午1:54

#4 楼

如果要创建真正相同的块,则必须强制ffmpeg在每个块的第一帧上创建i帧,以便可以使用此命令创建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


评论


这应该是公认的答案。多谢分享好友!

–斯蒂芬·艾尔夫
19年7月21日在7:02

#5 楼

另一种更具可读性的方法是

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
*/


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

#6 楼

请注意,替代格式的确切标点是-ss mm:ss.xxx。我努力工作了几个小时,试图使用直观但错误的mm:ss:xx无效。

$ man ffmpeg | grep -C1 position



-ss位置
寻求给定的时间位置(以秒为单位)。还支持“ hh:mm:ss [.xxx]”语法。


此处和此处的引用。

#7 楼

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

ffmpeg -i invid.mp4 -threads 3 -vcodec copy -f segment -segment_time 2 cam_out_h264%04d.mp4


这会将其拆分为大约2秒的卡盘,在相关的关键帧处拆分,并将输出到文件
cam_out_h2640001.mp4,cam_out_h2640002.mp4等

评论


不知道我是否理解为什么这个答案被否决。似乎工作正常。

–科斯汀
19年8月23日在10:21

这是最好的解决方案!唯一的问题是时间戳与旧时间戳相同,因此在文件0004处,它以4 * segment_time秒开始。

–碧玉
20 May 24 '15:58

#8 楼

#!/bin/bash

if [ "X" == "X" ]; then
    echo "No file name for split, exiting ..."
    exit 1
fi

if [ ! -f "" ]; then
    echo "The file '' doesn't exist. exiting ..."
    exit 1
fi

duration=$(ffmpeg -i "" 2>&1 | grep Duration | sed 's/^.*Duration: \(.*\)\..., start.*$//' | awk -F: '{ print ( * 3600) + ( * 60) +  }')      #'
split_time=${split_time:-55}
time=0
part=1

filename=${file%%.*}
postfix=${file##*.}

while [ ${time} -le ${duration} ]; do

echo    ffmpeg -i "" -vcodec copy -ss ${time} -t ${split_time} "${filename}-${part}.${postfix}"
    (( part++ ))
    (( time = time + split_time ))

done


#9 楼

一线解决方案
 
for i in {3..100}; do ffmpeg -i input.mp4 -ss $(($((i))*600)) -t $(($((i+1))*600)) $i.mp4 ; done;

 


评论


欢迎来到该网站,并感谢您的贡献。请注意,不必嵌套$((...))构造,$((i * 600))或$(((i + 1)* 600))完全可以。同样,最好在参数扩展中使用双引号,例如“ $ i.mp4”(尽管在您的示例中几乎没有不必要的单词拆分的危险)。

– AdminBee
20 Sep 24'7:13