玩一下 FFmpeg
因为最近有两件事情,所以要研究一下这玩意:把一整个 capoo 动画分割出几个小片段然后做成表情包(capoo 好萌),还有就是 vps 上下的动画,直接用 filebroswer 播不了,需要稍微转一下码才行。
总参数参考:ffmpeg Documentation
视频精确分割
关键词:帧内编码(intra)
参考文章:FFMPEG 视频分割和合并
不过这篇文章没有谈一个参数,to,to 是裁剪到时间节点而 t 是裁剪自开始以来的一段时间长度,这里为了方便我很明显是要用 to 的。
1 | ffmpeg -ss 00:00.00 -to 00:00.01 -i capoo.mp4 -vcodec copy -acodec copy "C:\Users\zbttl\OneDrive - go.Stockton.edu\Desktop\capoo\ capoo1.mp4" |
不过发到 telegram 上的 gif 的 mp4 不能带音频,所以索性再改一下
1 | ffmpeg -ss 00:00.00 -to 00:00.01 -i capoo.mp4 -vcodec copy -an "C:\Users\zbttl\OneDrive - go.Stockton.edu\Desktop\capoo\ capoo1.mp4" |
顺便附带一个文件夹内批量转换为无声 mp4 的例子:
1 | for i in *.mp4; do ffmpeg -i $i -vcodec copy -an "/home/zbttl/capoo/${i%%.*}.mp4";done |
参数里可以加上 -avoid_negative_ts
移动关键帧使其与要剪辑的位置相符。
使用 gui(不成熟)
也可以使用 gui 工具 LosslessCut。gui 可以通过视频中的 Intra(I 帧,关键帧)识别转场,操作上便捷许多。不过很多视频由于参数原因(I 帧过多会增大视频体积),所以 I 帧和真正的转场不一定完全符合,可能还要通过 ffmpeg 转一下码。
参考:
加入以下参数:
1 | -keyint_min #Intra最小间隔时间,可设置为 0。 |
比较有用的就是这三个参数。我还实验了 -profile
、-preset
两个参数。profile
在文档中有 extended
这个选项可能对关键帧切换有帮助,但实际使用起来选项无法使用;preset
只要不设置成比较快的那些选项,使用 slow
和什么都不用出来的 I 帧数量和分布没有区别。
ffmpeg 使用非 copy 模式转码会显示 I/P/B 帧的数量和占比。也可以使用 elecard streameye tools
查看 I/P/B 的数量和分布(但要花钱,破解找不到,要不就只能试用)。
转码的时候也不要加入 -c:a copy
参数,可能导致时间轴误差。
例子:
1 | ffmpeg -y -i '.\capoo.mp4' -preset slow -keyint_min 1 -sc_threshold 60 './capoo_1.mp4' |
再用得到的新视频文件在 gui 内裁剪。
然而实际使用时发现在每个关键帧处还要往上倒三帧否则就会包含下个场景的画面。原因是这个 gui 命令里面用了 -c copy
参数。。。目前还无解。而且除了这个,有时候导出的某些视频还会出现只剪了后面没剪前面的情况,貌似是因为放在桌面,桌面的路径里面有中文(onedrive 的锅)。。。。
还有其他的 gui 工具,比如 ffmpegyag。但这就没有根据 Intra 帧在时间轴上快捷指向的功能了(虽然还是能识别处 I/P/B 帧)。而且不能直接使用(点 OK 就卡住),生成脚本后运行倒是没问题。
配合 opencv
门槛有点高。给两篇参考文章,先挖个坑。
vps 动画转码
为什么播不了?我想估计是位深太大了,使用 ffmpeg x264 默认参数转一下就好了。
1 | ffmpeg -i "[Sakurato.sub][Nande Koko ni Sensei ga!][01][GB][1080P].mp4" -vcodec libx264 -acodec copy test.mp4 |
不过转出来感觉略微有点不太对,windows potplayer 缩略图显示不出东西来。。。原因不明,排除了 10bit 不兼容原因(原案是 10bit,可显示)和 hevc(h265)与 avc(h264)原因(用哪个编码器都转不出来)。
另外,x264 编译器还有很多奇奇怪怪的参数,参考这里:H.264 Video Encoding Guide
显卡加速
参考文章:使用GPU硬件加速FFmpeg视频转码
ffmpeg 还支持显卡加速,不过嘛。。。参数很麻烦,没什么可靠的参考(因为 ffmpeg 的参数经常有顺序限制的,上面那篇文章的参考我试了一下,失败),下面这个,我转起来速度比较快(不过也有是用 8bit 的原因),而且 potplayer 识别了缩略图,另外显卡也工作了(不过占用只有百分之8。。。。)(未加 -hwaccel cuda
,虽然是硬解但仍然经过内存。但下面的命令仅硬解码,且仅 h265)
1 | ffmpeg -hwaccel cuda -c:v h265_cuvid -i "[Sakurato.sub][Nande Koko ni Sensei ga!][01][GB][1080P].mp4" -pix_fmt yuv420p test.mp4 |
拿来转部落战视频用的,因为 ipad 录的视频码率高,而且带了旋转属性(很诡异,是写在 ffmpeg 参数里面的,也就是说对于支持的播放器打开后会自动转正变成横屏,但其实视频硬属性是竖着的),因为有这个自动旋转所以网上写的大部分硬件转码无法使用(不支持关掉自动旋转并摆正视频),但是我试了一个新的,还凑合,而且 gpu 打满。原理是硬解硬编码,下面的方框部分是指定使用最广泛的 h264 硬解,记得如果原来就是 h265 视频需要把这个参数换掉(hevc_cuvid
)或关掉。硬编码的部分也可以换成 h264_nvenc
。
1 | ffmpeg -y -vsync 0 -hwaccel cuda [-c:v h264_cuvid] -i xxx.MP4 -vcodec hevc_nvenc -b:v 3000k xxx.mp4 |
hwaccel 也有被称为 cuvid 的参数。但我用 cuvid 代替 cuda 时会报错
1 | Pixel format 'yuvj420p' is not supported |
原因未知。cuvid 和 cuda 的区别我也没发现有参考资料能解释。
对于其他硬解方式,可以参考这篇文章:(三+1)用显卡加速视频转码压制之ffmpeg、media coder、shana encoder
查看解码方法:
1 | ffmpeg -decoders |
查看编码方法:
1 | ffmpeg -encoders |
上面提到的 cuda
和 h264_cuvid
就在解码方法里面,而 hevc_nvenc
就在编码方法里面。对于 intel 系来说,硬解应该是 qsv
后缀一类的方法;而 amd 是 amf
后缀一类的方法。
因为一般是转为 x264/265 编码的视频,可以借助 h264/hevc 快速筛选出当前 ffmpeg 转换时支持的编解码方法,类似于:
1 | ffmpeg -decoders|findstr h264 |
我仔细看了看最新版 ffmpeg,发现起码对于 amd,只找到了编码方法(比如 h264_amf
)而没有找到解码方法。。。不过实测,仅使用编码方法也能有效的加速视频的转换:
1 | ffmpeg -c:v h264_amf -i "[Sakurato.sub][Nande Koko ni Sensei ga!][01][GB][1080P].mp4" test.mp4 |
好在我平常操作的视频都是解码不怎么费劲的视频,解码费劲的 4k 编码起来必然更慢,暂时不属于我手上硬件能触及的范围了。。。
另外,文章提到
1 | ffmpeg -hwaccels |
能查看当前硬件和 ffmpeg 支持的硬解,但我看结果感觉扯淡。。。我用 amd 的机子能查出来 intel 和 cuda,却没有 amf,就 tm 离谱(当然文章里面也提到了这个方法不准就是了)。另外实际转换时使用qsv -hwaccels qsv
时需保证没有独立显卡(特别是N卡),否则会报错,是bug,来自#6996(尝试在 Windows 10 上使用 NVidia 主 GPU 支持的 Intel 系统上使用 QSV 会导致崩溃)– FFmpeg。
多线程
参考文章:ffmpeg 多线程转码
通过写在 -i
参数前的 -threads [线程数]
可以指定 ffmpeg 使用的线程。不过经过测试比较新的 ffmpeg 都会用完 cpu 的所有线程,所以除非要限制 cpu 功率否则这个参数没必要动。
mkv 内挂字幕嵌入 mp4
内挂字幕的 mkv 在 filebroswer 里面看不到字幕啊。。。于是要想办法提取字幕出来,再把字幕直接内嵌进去。
有两种方法:
不靠谱方法,很快,但是不一定能识别
1
ffmpeg -i input.mkv -c copy -c:s mov_text output.mp4
重新编码的方法。一定能识别,但是贼慢
1
ffmpeg -i input.mkv -vf subtitles=input.mkv output.mp4
mp4 批量转换 gif
参考文章:High quality GIF with FFmpeg
以 centos 为例,先编辑个小脚本:
change.sh
1 | #!/bin/sh |
运行
1 | ./change.sh './[要转换的mp4]' '[转换成的gif名字]' |
或者把需要转换的 mp4 文件放到和上面这个脚本相同目录中,执行
1 | for i in *.mp4; do ./change.sh $i "${i%%.*}.gif";done |
这条命令会把所有的 mp4 的后缀名去掉,换成 gif。
另外,windows 端也可以通过修改成两个 bat 文件做到类似的效果(不过第二部我不知道怎么把原 mp4 文件夹的所有后缀替换成 gif,只能直接加 gif),要批量转换的时候运行第二个 bat 就 ok 了
change.bat
1 | @echo off |
change_all.bat
1 | @echo off |
嫌转换成的 gif 太大?改点参数就成,比如:
change_slim.bat
1 | @echo off |
合并音视频
1 | ffmpeg -i xxx -i xxx -c:v copy -c:a copy output.mp4 |
mp4 转音频
对于大部分 mp4,可以直接提取其中的音频,速度很快:
1 | ffmpeg -i .\test.mp4 -acodec copy -vn test.mp3 |
不过,这条命令是直接把 mp4 封装中的音频部分提取出来,如果音频部分不是 mp3 格式就会出错:
从日志里面,我们可以观察到原来封装里面是什么格式的,比如这里就是 aac 的:
这时候就需要把命令中的格式从 mp3 改成 aac:
1 | ffmpeg -i .\test.mp4 -acodec copy -vn test.aac |
如果需要转换其他格式,可以使用 c:a
参数替代 acodec
,比如转换为当前较先进的 opus 格式(当然速度就慢多了):
1 | ffmpeg -i .\test.mp4 -c:a libopus -vn test.opus |
下载 m3u8
参考文章:Why does ffmpeg ignore protocol_whitelist flag when converting https m3u8 stream?
某些视频网站和 ios 软件用的视频地址抓出来是 m3u8 的。比如机核的视频。可以用 chrome 插件猫抓获取 m3u8 文件。然后,加入相关参数,注意需要紧跟在 ffmpeg 命令后面:
1 | ffmpeg -protocol_whitelist file,http,https,tcp,tls,crypto -i xxxx.m3u8 xxxx.xxx |
xxxx.xxx 指的是你需要输出的视频名字和格式,因为 m3u8 流切下来一般是 h264 的,封装格式需要你自己来确定,ffmpeg 会帮你把所有切片合并。
自动裁切黑边
参考文章:CROPDETECT AND FFPLAY - 2020
先检测黑边:
1 | ffmpeg -i .\test.mp4 -vcodec copy -acodec copy cropdetect=24:16:0 test1.mp4 |
24:16:0 是默认参数,一般如果要调也只调第一个参数(黑边阈值)。
输出的日志中会有类似于
1 | [Parsed_cropdetect_0 @ 0x3704360] x1:0 x2:639 y1:43 y2:317 w:640 h:272 x:0 y:46 pts:181320 t:181.320000 crop=640:272:0:46 |
其中有用的就是 w、h、x、y 四个参数,分别放入新命令的相应位置
1 | ffmpeg -i .\test.mp4 -vcodec copy -acodec copy -vf crop=640:272:0:46 test1.mp4 |
即可。(虽然经过我测试默认参数检测还是有点偏差,但稍微手动调一下 w 和 h 的值效果就令人满意了)
其他
比较有用的几个地方:
转换格式时不压缩视频
使用-qscale 0
1 | $ ffmpeg -i input.webm -qscale 0 output.mp4 |
(21.6.26 更新)批量将视频大小限制到一定范围内(试做)
参考文章:
众所周知媒体文件(图片,视频,音频等)要降低体积不难,但要降低到一个大小范围内,就很折腾了。
ffmpeg 缩小视频体积一般有那么几种途径:
- 降低码率,这种方法用的比较多。降低码率比较简单的一共有三种方法
- 动态码率(vbr)下,直接使用参数
-b:v [目标码率]
。 - 固定码率(cbr)下,使用参数
-cbr [cbr参数] -pass 1
,cbr 参数默认为 23,增大至 28 左右,可有效减小体积。 - 参数
-qp
。
- 动态码率(vbr)下,直接使用参数
- 降低分辨率。
- 降低帧数。
对于视频文件,通常我们不采用降低帧率的方法,而是配合使用降低分辨率和降低码率的方法,因为在固定分辨率的情况下,码率的下降是有极限的,比方说在 1080p 分辨率下,即使我们使用参数 -b:v 1000
,最后转换出来的视频平均码率可能也有 1500 左右;并且后续再使用 -b:v 500
也会发现转出来的视频和上一个视频大小几乎没有区别。另一方面,高分辨率低码率的视频有可能比同体积的稍低分辨率但高码率的视频还要胡上许多。
我们的思路是,第一次先对码率进行下调,当体积大于目标值时,再对视频的码率和分辨率同时进行缩小,直到体积符合要求。
change.bat
1 | ::调试时改为echo on |
然后将需要处理的视频放到和本 bat 同一个文件夹下即可。处理过的视频文件将被命名为 change+源文件名.mp4
。
当然这个脚本还是有不少的改进空间:
- 不同分辨率的目标码率应该不太一样,我现在把目标码率统一设置为一个数字了。如果设置的太小就会牺牲高分辨率的视频文件;设置得过大低分辨率的视频文件就可能需要多次转换。
- 直接使用参数
-b:v [目标码率]
进行码率降低实际上是一个简单而糟糕的方法。
第一个问题,如果分辨率和码率有一个合适的对应函数曲线可能就能解决,我没找到;有个大概的表格
但用分辨率中的宽度/码率得到的结果在 0.2-0.3 直接浮动,差距太大,可能凑合用都有点勉强。
第二个问题,推荐的方法是使用 Two-Pass ABR
转换法:
用于限制输出文件的大小,比如预期视频文件有 10min(600s),200 MB:
1
2
3 >200 * 8192 / 600 = ~2730 Kb
>2730 - 128(音频常用的比特率) = 2602 kb
>12那么:
1
2 >ffmpeg -y -i input -c:v libx264 -b:v 2600k -pass 1 -c:a aac -b:a 128k -f mp4 /dev/null && \
>ffmpeg -i input -c:v libx264 -b:v 2600k -pass 2 -c:a aac -b:a 128k output.mp4
但要在 bat 下这样处理是有点麻烦。。。如果下一次我再改这个脚本,我可能会选择用 python 写吧,现在这个,先凑合着用吧2333
(22.1.19 更新)批量将视频大小限制到一定范围内-python 版(试做)
用 bash 写脚本真是吃力。。。还不能在 linux 上用。想要改进一下脚本,让子目录下的文件也能被一键转换,发现用 bash 写实在是太复杂了,所以干脆把上面的脚本用 python 重构了:
1 | import os |
新脚本在实现了上一个一键限制视频到对应体积功能的基础上,增加了以下功能:
- 通过修改参数使编解码均使用硬解,默认使用支持最为广泛 qsv 硬解(intel 家的硬解标准)。
- 对子目录中的视频文件同样进行转换。
- 默认的封装格式为 mp4,可更改。
- 同时对帧数进行了更改。
- 转换时对修改/无需进行修改的文件均进行备份,多次运行时不会对已转换的项目进行多次转换。
版本
ffmpeg windows 端出了新的编译版:BtbN/FFmpeg-Builds。release 中有许多不同版本。lgbl 和 gbl 应该是许可证之间的不同,但大小也有区别,我个人认为是为了 lgbl 剔除了一些东西;shared 和不带 shared 的版本,区别是后者将运行库文件合并进了 ffmpeg 二进制文件中(但不带 shared 的版本体积大了很多)。最关键的是 vulkan 版本,带有 vulkan 版本需要比较新的独显和新的驱动才能使用,我的 965m 运行都会报错 Lossless encoding not supported
。