ffmpeg相关技术

视频合并 

FFmpeg concat 分离器

file 'input1.mkv'
file 'input2.mkv'
file 'input3.mkv'

然后:

ffmpeg -f concat -i filelist.txt -c copy output.mkv
1. 原理

比如我有三个视频片段,想要合并成一个完整视频。视频片段的文件名如下:

media_w384769807_0.ts
media_w384769807_1.ts
media_w384769807_2.ts

那么只要新建一个文本文件,命名为list.txt,写入如下内容:

file 'media_w384769807_0.ts'
file 'media_w384769807_1.ts'
file 'media_w384769807_2.ts'

(注意:文件名前面的file是关键字,不能省略)然后用下列命令行调用ffmpeg:

ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4

命令结束后,就生成你要的结果output.mp4。
顺带一说:在我认识ffmpeg这一强大工具之前,我一直用copy /b的方法合并视频。copy /b的结果,看是能看,跳着看就会花屏——原因不用想也明白,原来片段里有些文件头不是视频流数据,强行合并进来,当视频流解析当然要出错了。


2. 用批处理将上述过程自动化:第一步尝试

话虽如此,总不能让我每次合并视频的时候都新建一个文本文件,打上几行字,然后再去命令行终端调用ffmpeg吧!没错,我们可以用批处理来做这些事,比如上面建立文本文件这一步,可以用下面的批处理命令完成:

(for %%a in (*.ts) do @echo file '%%a') > list.txt

这个命令执行时,它会穷举本目录下所有.ts文件的文件名,然后逐行以 "file '文件名'"的格式输出到文件list.txt中。


3. 解决文件名穷举次序与片段次序不符的问题

上面所说的方案,当片段数比较少的时候是正确运行的。但片段数比较多的时候,比如有下列超过10个的片段:

media_w384769807_0.ts
media_w384769807_1.ts
media_w384769807_2.ts
...
media_w384769807_9.ts
media_w384769807_10.ts

那么当你执行:

(for %%a in (*.ts) do @echo file '%%a') > list.txt

再打开list.txt文件一瞧,就会发现内容是这样:

file 'media_w384769807_0.ts'
file 'media_w384769807_1.ts'
file 'media_w384769807_10.ts'
file 'media_w384769807_2.ts'
file 'media_w384769807_3.ts'
file 'media_w384769807_4.ts'
file 'media_w384769807_5.ts'
file 'media_w384769807_6.ts'
file 'media_w384769807_7.ts'
file 'media_w384769807_8.ts'
file 'media_w384769807_9.ts'

发现了什么?第10个片段跑到第2个片段的前面去了!而ffmpeg并没有智能,它只会忠实按照你提供的列表顺序来拼接视频,结果当然不对了。
顺便要说的是,许多人津津乐道的copy /b *.ts merge.mp4之类的命令也存在同样的问题,不要以为“能用一句命令解决任务”。问题关键在于,当DOS使用通配符 * 来穷举文件名时,用的是字典顺序而不是数字顺序,按这种顺序,当比较到从左往右第一个不相等的字符时,一看2比1大,就不管1跟2的后面还跟着多少个字符了,于是10就总在2的前面了。

那么,怎样解决这个问题呢?我们可以用带参数/l的for命令来生成数字列表。比如如果我要把上面那11个片段生成次序正确的列表,可以采用:

(for /l %%i in (0,1,10) do @echo file 'media_w384769807_%%i') > list.txt


4. 较为完善的方案

用for /l解决了列举次序的问题,可是又出来两个新问题:

(1) for /l 只能生成单纯的数字,而不能列举文件名,于是我们不得不在%%i的前面加上“media_w384769807_”这个前缀才能成为完整的文件名,而我下次运行时,面对的也许是另外一批片段,具有完全不同的前缀,于是我只得修改批处理。
(2) 片段的个数也是会变的,比如下一次的片段也许是从_0开始,到_15结束,那我的批处理命令又得对应改为for /l %%i in (0,1,15) ....。

怎样能让批处理适应这些变化情况,而不用每次修改批处理本身?要达到这一目的,涉及的是如何提取文件名的不变部分,以及自动判断有多少个片段要合并。这个任务的解决办法就因具体情况而异了,在这里,我只拿上面说过的例子来讲:比如在像“media_w384769807_0.ts”的文件名中,前缀“media_w384769807_”是不变部分,而“0”是序号。那么我只要把最后的序号部分切掉,就得到不变部分,把这个不变部分拼接上由for /l自动生成的序号就行了。注意到不变部分的最后一个字符是“_”,我只要随便拿一个文件名来,依次切掉尾字符,切到“_”为止就行了。另外,用于列举文件名的for命令虽然不能按片段的正确次序,却可以用于统计有多少个片段,设置一个计数器每次加一就行了。这样,我们就得到如下的代码:

@echo off
:: 因为下面循环每一轮都改变file_prefix的值,所以用了enabledelayedexpansion
setlocal enabledelayedexpansion

set aaa=%random%
set bbb=%random%
set /a last_index=-1

for %%a in (*.ts) do (
set file_prefix=%%~na
set /a last_index+=1
)

:cutright
if !file_prefix:~-1! neq ^_ (
set file_prefix=!file_prefix:~0,-1!
goto cutright
)

(for /l %%i in (0,1,%last_index%) do @echo file '%file_prefix%%%i.ts') > %aaa%.txt
:: 这里可以加一句pause,用于确认列表文件是否正确,如不正确按Ctrl+C
ffmpeg -f concat -safe 0 -i %aaa%.txt -c copy %bbb%.mp4
erase %aaa%.txt
endlocal
echo on

FFmpeg视频转码技巧之-crf参数

image.png

ForDream:当前文章用户名 http://lowfk.com/index.php/2021/08/24/114.html:当前文章地址
THE END
分享
二维码
< <上一篇
下一篇>>