ffmpeg常用命令

ffmpeg常用命令

ffmpeg简介及视频的本质

ffmpeg简介

ffmpeg是 Fast Forward MPEG (Motion Picture Experts Group) 的缩写,见这里,英语读法,会把“ff”和“mpeg”分开读,因为mpeg可以直接按一个单词发音[ˈempeɡ],而两个ff就直接按字母发音读。

其中Fast Forward字面意思是“快速向前”,也就是“快进”的意思,而“Motion Picture”字面意思是“会动的图片”,一般翻译为“电影”(比较正式的说法),事实上电影确实就是会动的图片。

视频的本质

视频的本质,是以一定速度按顺序显示的图片(比如在1秒钟时间内快速显示24张图,由于视觉暂留效果,我们看起来就是画面会“动”)。

小时候看的连环画,只要快速翻动,就会看到画面动起来
连环画.gif

快速翻动多张小黄鸭图片,就能看到小黄鸭在动

当多张图片之间的差别很小,图片够多,翻动的速度够快(即每秒钟翻动的页数够多)时,我们就能看到动起来的画面,这就是视频的原理。

而最初的电影,也是使用胶片记录,播放的时候用强光照射胶片,经放大后投影到电影幕布上,同时胶片不断的卷动,当卷动的速度够快时(一般每秒24张),这样我们在幕布上看到的画面就是连续流畅的会动的画面。
电影胶卷

现在我们说“剪片”或“剪辑”,通常都是用剪辑软件来对视频进行剪辑,而以前的胶片电影,那可是真的是要把胶卷剪开的,所以为什么现在叫“剪辑”,也是这个意思,拼接的话是用胶水粘起来的

数字时代的电影,已经不需要胶片,只需要以二进制的方式存成一个文件即可,比如我们常的mp4格式。但视频的本质没有变,它还是由一张一张的静止图片合成的,我们每一张图片,叫“一帧(frame)”,但是,如果真的把这些图片一张一张合成一个视频,那么视频的体积将会非常巨大,所以需要压缩,事实上我们看的视频都是压缩过的,因为很多时候,相邻图片大部分都是相同的,存储这么多非常类似的图片,是对硬盘空间的巨大浪费。

我们一般把前面说的“压缩”称为“编码”(比如最常见的h.264编码),而播放过程就是把编码后的视频解码还原出来。

视频的分辨率其实就是组成这个视频的图片的分辨率,比如视频分辨率是1920⨉1080,意思是组成这个视频的每张图片,横向有1920个像素,纵向有1080个像素,对于图片来说,每张图片的分辨率越大,它就越清晰,由此可见,组成一个视频的图片分辨率越高,视频就越清晰,也就是说,一个视频的分辨率越高就越清晰(但这不是绝对的,下面会说到分辨率越高,清晰度反而越低的情况)。

前面说过,胶片电影的播放是靠每秒钟卷动一定数量的胶片来让画面达到“会动”的效果,一般来说,要一秒16张就可以达到让我们看起来画面是运动的,当然再多一点,画面就会更细腻,对于胶片电影,一般一秒钟24张。我们称一个视频每秒钟播放的图片数量为该视频的“帧率(frame-rate)”。常见的帧率有24000/1001=23.976, 30000/1001=29.970, 60000/1001=59.940, 25.000, 50.000等等。这个数字是一秒钟内闪过的图像的数量。比如23.976,就是1001秒内,有24000张图像。视频的帧率是可以是恒定的(cfr:Const Frame-Rate),也可以是变化的(vfr:Variable Frame-Rate)。

帧率决定一个视频的流畅度,试想一下,我们把一本100页的连环画,每隔一页撕掉一页,那么每两页之间的画面变动就比较大,视觉上就是画面跳动大,不连贯,不流畅。

码率,即比特率(bit-rate),每秒钟播放多少比特(bit),对于生成一个视频(摄影机或者剪辑软件),表示每秒种会产生多少比特的数据。

现在我们来解释,为什么分辨率越高,视频反而越不清晰的情况。这种情况会在码率固定、帧率固定,而单独提高分辨率时发生。帧率固定,代表每秒钟要播放的图片数量是固定的(比如是24),码率固定表示生成一个视频时,每秒钟生成的文件体积是固定的,但是,如果我们提高每张图片的分辨率,很显然每秒24张分辨率更高的图片生成的文件体积更大,但现在我们却不能增加每秒钟生成的文件体积(不能增加码率),怎么办?

办法就是采样!由于每张图片分辨率变大,它本身的体积已经变大,完整保存每张图片肯定会导致码率变大,我们只能抽取每张图片里的一部分像素(这部分像素也能让你看清这个图片),这就是采样,毫无疑问,视频清晰度取决于从每张图片中抽取的像素数量(我们叫采样率),当一张图片的分辨率越高,代表它的像素越多,而我们能抽取的像素量却有限,所以,图片分辨率越高会导致采样率越低,这样的后果就是这张图片越不清晰,而我们知道,播放视频就是连续播放图片,既然你每张图片都不清晰,那表现出来就是这个视频不清晰,这就是为什么“分辨率越高,视频反而越不清晰”,因为这是被码率限制了,如果我们不限制码率,那么肯定是分辨率越高,视频就越清晰了。


ffmpeg的使用

ffmpeg命令生成器

ffmpeg命令很多,对初学者来说比较复杂,想要用ffmpeg对视频进行某种操作,如果不会就只能网上查,需要怎么写命令。

就算有些人使用过一段时间后熟悉了命令,但如果放下一段时间,很容易又不知道命令怎么用了,为了方便使用,很多人都写了一些工具,这些工具分为两类:

  • 1、一类是GUI工具,就是写一个壳,让你有一个界面可以选择文件,可以调整参数,最后点击转换实际上还是用ffmpeg转换的,只不过它是直接调用ffmpeg的api了,这类软件用起来更像是格式转换器;
  • 2、另一类是命令生成器工具,就是他也写了一个界面(通常是web界面,因为简单,无需下载安装),让你可以选择参数,但它并不调用ffmpeg api直接帮你转换文件,而是根据你提供的参数自动生成一条命令,你把这条命令粘贴到终端里面执行,就可以对你的视频进行处理。

我找到一个比较不错的ffmpeg命令生成器(界面如下)
image.jpg
这是生成器github,这是生成器界面

另外还有两个ffmpeg命令生成器,不过我感觉没有前面那个功能多:生成器2生成器3

一些参数解释

  • -codeccoder-decoder的缩写,coder:编码器,decoder:解码器,合起来就是:编码解码器,读作['kodɛk],可简写为-c,经常后面用冒号跟着v或a,比如-c:v表示指定视频编码(v:video),-c:a表示指定音频编码(a:audio);
  • v一般表示video(即视频),a一般表示audio(即音频);
  • -codec:vv代表“video”,用于指定视频编码格式,可简写为-c:v,另一种写法是-vcodec
  • -codec:aa代表“audio”,用于指定音频编码格式,可简写为-c:a,另一种写法是-acodec
  • -b:vb代表“bitrate”(即比特率),v代表“video”,用于指定视频比特率(即码率),也可写成-vb(video bitrate的意思);
  • -b:ab代表“bitrate”(即比特率),a代表“audio”,用于指定音频比特率(即码率),也可以写成-ab(表示audio bitrate的意思),但这种写法不是标准写法,我看有些类似的写法在ffmpeg新版本中都弃用了(虽然还能用);
  • -vbsf:已经弃用,现在一般写成-bsf:v,用于指定视频比特流过滤器(bit stream filter),比特流过滤器用于“在不做码流解码的前提下,对已经编码后的比特流做特定的修改、调整”,用ffmpeg -bsfs可查看所有支持的比特流过滤器;
  • -absf:已经弃用,现在一般写成-bsf:a,用于指定音频比特流过滤器,用ffmpeg -bsfs可查看所有支持的比特流过滤器;
  • -aq audio quality,音频质量,它是-q:a的别名;
  • -crf Constant Rate Factor,恒定速率因素,是x264和x265编码器的质量参数,取值范围为0-51,数字越大,视频质量越差,数字越小,视频质量越好,当然视频文件体积也越大;0为无损,51为最差,默认值为23,一般认为能用的数值是17-28,大于28就会有肉眼可见的模糊了,详见H.264 Video Encoding Guide
  • -vf video filter,用于指定视频过滤器,是-filter:v的简写,比如-vf crop=1080:743:0:454表示从输入的视频中的(0,454)坐标为左上角顶点,截取一个宽x高=1080×743的视频;
  • -af audio filter,用户指定音频过滤器,是-filter:a的缩写,比如-af "volume=1.5"表示把音量调整为原来的1.5倍。
  • -preset 预设是专门针对x264编码的一组选项,可以提供一定的编码速度和压缩比,如preset medium,除了medium,还有很多值:ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow(文档),在可忍受的情况下,越慢越好。

比如,以下参数就是指定视频编码(-c:v)为libx264,音频编码(-c:a)为aac,音频比特率(-b:a)为64k

-c:v libx264 -c:a aac -b:a 64k

另外还可以不指定编码,直接用原视频的编码,copy表示直接复制原视频的内容(不改变编码)

-c:v copy -c:a copy

查看视频参数

使用以下命令可查看一个视频或gif图的参数(如分辨率、帧率、码率),注意这里包括gif图,因为gif图的本质也跟视频类似(即由一系列图片组成,并且是按顺序播放出来),当然普通图片也可以查看,只不过没有帧率和码率

# 事实上ffmpeg -i默认是需要指定输出文件的,但是假如只有输入文件,它会输出它的信息
# 而不对视频进行处理,只不过最后一行会有个提示:At least one output file must be specified
ffmpeg -i /path/to/xxx.mp4

# 如果我们把ffmpeg换成ffprobe就不会有最后那句提示,但整个输出结果都是一样的
ffprobe -i /path/to/xxx.mp4

输出截图如下,我们主要是要找到其中的Stream #0
image.jpg

像这张图我主要看这几个信息Video: h264, 360x640, 646 kb/s, 30 fps,分别是:视频编码为h264,分辨率为360×640,码率为646ksps,帧率为30fps。


但是ffmpeg(包括ffprobe)不管做什么操作,都会在头部输出它的编译信息,但这些信息在绝大多数时候都是没用的(如下图)
image.jpg

可以看到,使用了-hide_banner后,头部的一大段信息就没有了,这样就整洁多了
image.jpg

但是每次都加上-hide_banner太麻烦了,我们可以创建一个别名,在~/.bashrc中添加一句

alias ffmpegnb="ffmpeg -hide_banner"

这样,我们平时就使用ffmpegnb,nb代表no banner,当然你也可以认为是“牛逼”哈哈,反正这样用的话,就方便多了,不加-hide_banner也没有一大片密密麻麻的头部了
image.jpg

查看视频时长

查看视频时长(容器时长),只能使用ffprobe不能使用ffmpeg

# 输出结果会用[FORMAT][/FORMAT]标签包着
ffprobe -v error -show_entries format=duration -i /path/to/input.mp4

# 输出结果直接是一个时间,没有[FORMAT][/FORMAT]标签包着
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 -i /path/to/input.mp4

只查看音频的时长(有些视频的画面和声音时长不同)

 ffprobe -v error -select_streams a -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 -i

主要是添加了-select_streams a,a表示audio,音频轨,同理还可用v表示视频轨,当然,也可以用v:0表示音频,v:1表示视频,这个0和1我不知道是不是轨道编号,参见这里

查看播放时长

ffmpeg -i /path/to/input.mp4 -f -

容器时长跟播放时长一般相等,但可能有时候有点区别,不过这样查看需要解码,如果视频大,会比较消耗时间和cpu资源,一般查看前面的容器时长就行。

提取纯视频或纯音频

提取纯音频

# 单文件提取
ffmpeg -i input.mp4 -vn -c:a copy output.m4a

# 提取当前文件夹下所有mp4文件中的音频为m4a
for i in *.mp4; do ffmpeg -i "$i" -vn -c:a copy "${i:0:$[${#i}-4]}.m4a"; done
  • -vn 表示video none,意思就是不要视频;
  • -c:a copy就是指原样复制音频(c是codec的意思,a是audio);
  • 原样copy的音频编码是aac,格式都是用m4a(当然你用aac也行,但更常用m4a,因为还是略有区别的);
  • 如果需要转mp3格式,可以看下边的提取mp3或转mp3

提取纯视频(视频会变的无声音)

ffmpeg -i input.mp4 -an -c:v copy output.mp4
  • -an audio none,意思是不要音频;
  • -c:v copy,意思是原样拷贝视频编码,不要重编码(否则重编码很费时间,清晰度也会降低);

提取mp3或转mp3

把当前目录下的所有m4a文件转成mp3

for i in *.m4a; do ffmpeg -i "$i" -acodec libmp3lame -aq 2 "${i:0:$[${#i}-4]}.mp3"; done

# 跟前面一句完全一样,就是参数名的写法不一样,但我更喜欢这种写法
for i in *.m4a; do ffmpeg -i "$i" -c:a libmp3lame -q:a 2 "${i:0:$[${#i}-4]}.mp3"; done
  • -acodec 也可写成-c:a
  • -aq 也可写成-q:a,它是-qscale:a的简写,qscale就是quality scale(质量等级),a就是audio,表示前面的qscale作用于音频,该值为0-9共10个等级,0质量最好,9质量最差,0-3基本上人耳朵感觉不出差别(但从音质参数上有差别),4是默认值,从4开始人耳可感知到差别,6是可接受的质量,再大就能明显感觉到音质变差了,另外,当比特率设置为320kb/s(即-b:a 320k)时,貌似不需要指定-q:a参数,它默认就是最高音质。具体见:FFmpeg MP3 Encoding Guide

从当前目录下的所有mp4视频中提取mp3(mkv也可以换成其它格式,比如mkv/m4v/mov)

for i in *.mp4; do ffmpeg -i "$i" -c:a libmp3lame -b:a 320k -vn -map_metadata 0 -id3v2_version 3 -f mp3 "${i:0:$[${#i}-4]}.mp3"; done

-i inupt,用于指定输入文件路径;
-c:a libmp3lame 用于指定编码为,mp3的编码格式为libmp3lame;
-b:a b是bitrate,a是audio,即指定音频比特率(决定音质的参数,对于mp3来说,最高音质为320kbps,再高就需要flac格式了),该选项也可简写成-ab(即audio bitrate的意思)。
-vn video none,也就是不要把视频信息保存到输出文件中,其实就是去掉视频;
-f format,用于指定输出格式(可省略,因为ffmpeg会根据输出文的后缀自动获取应该输出什么格式)。


flac转320kbps音质的mp3(最高音质的mp3)

ffmpeg -i input.flac -ab 320k -map_metadata 0 -id3v2_version 3 output.mp3

转wav

主要是要把音频编码设置为pcm_s16le

ffmpeg -i input.mp3 -c:a pcm_s16le output.wav

ffmpeg -i input.flac -c:a pcm_s16le output.wav

注意:wav是无损音乐文件,mp3是有损的,从已经有损的mp3转为wav并不能让它音质变的更好,只不过是格式变了,以及文件体积变大了而已,当然你可以从flac转wav。

转flac

主要是要把音频编码设置为flac

ffmpeg -i input.wav -c:a flac output.flac

注意:只有选择无损的音乐格式(wav/aiff)来转flac,才有意义,否则你拿mp3虽然可以转成flac,但它的音质并不会因此而变好。因为mp3属于有损压缩,有损就是把一些信息丢掉了,丢掉的信息,并不是转换成flac就能变回来的,因为这属于凭空变出来,软件没有那么强大,所以是变不出来的,只不过是会增加一些无效的字节来凑数罢了。

音频升调降调

12是因为12平均律

# 升半音
ffmpeg -i /path/to/input.mp4 -filter_complex "asetrate=48000*2^(1/12),atempo=1/2^(1/12)" /path/to/output.mp4

# 升全音
ffmpeg -i /path/to/input.mp4 -filter_complex "asetrate=48000*2^(2/12),atempo=1/2^(2/12)" /path/to/output.mp4

# 降半音
ffmpeg -i /path/to/input.mp4 -filter_complex "asetrate=48000*2^(-1/12),atempo=1/2^(-1/12)" /path/to/output.mp4

# 降全音
ffmpeg -i /path/to/input.mp4 -filter_complex "asetrate=48000*2^(-2/12),atempo=1/2^(-2/12)" /path/to/output.mp4
  • a开头表示设置audio(音频),asetrate表示设置sample rate,取样率。
  • atempo,a开头表示设置audio(音频),tempo是速度的意思(这下知道为什么物理都用t表示速度了吧,就从这单词来的),atempo=n表示把音频速度设置为原速的n倍,当然速度倍数是负数我也没懂,按道理速度不可能是负的。
  • 1/12, 2/12中的12是因为十二平均律(即每组音分为12个音,钢琴中每组音有7个白键+5个黑键共12个键,7个白键两两之间有6个间隔,如果每个间隔放一个黑键,是可以放6个黑键的,但是实际上是5个黑键,所以一定有一个间隔没放,那就是mi fa之间没放,因为它们本身就是半音关系)。
  • 但一般很多人要求只升降调,但不要改变速度,这时用atempo=1/2^(1/12)就不行,只能自己看着来调,atempo的最小值是0.5,表示把速度设置为原值的0.5倍,我们只能随便设置一个值,看看音频总时长,只要把音频总时长调成哪原来的一样或者差不多,那么速度就跟原来差不多。

音频加速减速

把音频速度设置为原速的2.0倍

ffmpeg -i input.mp3 -af "atempo=2.0" output.mp3

atempo中的a是audio,tempo是速度的意思,atempo=n表示把音频的速度设置为原速的n倍,这个值最小0.5,最大是100,但最好不要设置大于2.0的数,否则会丢掉样本(跟视频丢帧一个意思,只不过音频没有帧,只有样本,即sample)。

如果你想把音频速度变为原速的4倍速,可以用叠加一次atempo的方式

ffmpeg -i input.mp3 -af "atempo=2.0, atempo=2.0" output.mp3

如果你想把音频速度变为原速的3倍速,可以用叠加两个sqrt(3)(sqrt是squareroot,开平方根),两个根号3相乘就是3,但分两次比直接一次用3好,就像你漂洗衣服,同样一桶水,你分成两半,洗两次,跟整桶水洗一次,肯定是分两半洗的干净

ffmpeg -i input.mp3 -af "atempo=sqrt(3), atempo=sqrt(3)" output.mp3

具体可以看官方文档:atempo
官方文档:Speeding up/slowing down video/audio

视频加速减速

方式一:设置pts,用setpts视频过滤器,这种方式会导致丢帧,从流畅度下降,可以通过增加输出帧率的方式来避免。

把pts设置为原PTS的0.5倍,意思是原来在第1秒和第2秒显示的帧,现在在0.5秒和第1秒就显示了,所以结果就是加速了

ffmpeg -i input.mp4 -vf "setpts=0.5*PTS" output.mp4

官方文档上说,可以通过增加输出帧率的方式来避免丢帧,比如你0.25是原速度的4倍,那么帧率就设置为原来的4倍

ffmpeg -i input.mp4 -r 16 -filter:v "setpts=0.25*PTS" output.mp4

官方举例原帧率是4,输出帧率可设置为16,这样就是原帧率的4倍,但这是不实用的,因为实际帧率没有这么小的,一般最小帧率是24(23.98跟24没啥区别了),那么如果要加速4倍,就要把24也设置为原来的4倍,也就是96,但事实上现在60帧是最大了,96实在太大,没有什么实际意义。

减速:一样的原理,只需要把倍数大于1自然就是减速(比如1.1*PTS,表示本来在第1秒显示的,现在要在第1.1秒显示,本来在第10秒显示的,现在要在10×1.1=11秒显示,整体效果就是变“慢”了)。

pts:presentation timestamp,帧展示时间戳。
setpts官方文档


方法二(不推荐):调整帧率,比如,我一共就6000帧(6000张图片,视频本质是连续的图片),我从60帧改为30帧,即由原来的每秒播放60张图片改为生秒播放30张图片,那速度肯定就慢了一倍,整个视频的时长就会增加一倍。

对于h.246编码

# 先转为raw
ffmpeg -i input.mp4 -map 0:v -c:v copy -bsf:v h264_mp4toannexb raw.h264

# 再用-r指定帧率
ffmpeg -fflags +genpts -r 30 -i raw.h264 -c:v copy output.mp4

对于h.245编码

# 先转为raw
ffmpeg -i input.mp4 -map 0:v -c:v copy -bsf:v hevc_mp4toannexb raw.h265

# 再用-r指定帧率
ffmpeg -fflags +genpts -r 30 -i raw.h265 -c:v copy output.mp4

但是这种方式只能调整视频,无法调整音频,而且目前来说我不知道要怎么调音频,但视频基本上都会包括音频,所以这种方法其实并不太实用。

我实测从60降到30,怪怪的,好像动作在重复,就是有30帧是重复的,看来不丢帧也不好。

减速:一样原理,只需要把帧率变小,自然就是减速。

官方文档:Speeding up/slowing down video/audio

同时加速视频和音频

加速为原速的一倍(减速同理,把setpts的0.5改为2,把atempo的2改为0.5就行)

ffmpeg -i input.mp4 -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]" output.mp4
  • [0:v]0表示第1个-i(从0开始数),v是video首字母,表示获取第1个-i指定的文件中的视频轨;
  • setpts是set presentation timestamp的缩写,显示时间戳,就是一帧图片应该在第几秒显示;
  • 0.5*PTS 大写的PTS表示原视频中的PTS,0.5就是把它变成原来的一半,这样显示时间变早了,速度就变快了;
  • [v] 是一个标签名,表示[0:v]setpts=0.5*PTS处理后的视频用[v]来标记,以便后面可以重复调用,你也可以写成[abc],后面调用的时候也用[abc]来调用就行;
  • [0:a]atempo=2.0[a][0:v]setpts=0.5*PTS[v]是一样的意思,只不过它作用于音频而已(a是audio的首字母);
  • [0:a][a][0:v][v]是一样的意思,只不过a表示音频(audio首字母),v表示视频(video首字母);
  • atempo=2.0 a是audio(音频),tempo这个单词是速度的意思(物理课上都用t表示速度,就是因为它的单词是tempo,首字母为t),tempo=n,表示把音频的速度设置为原值的n倍,当然音频其实也是有asetpts这个属性的,所以也可以写成[0:a]asetpts=0.5*PTS[a],只不过这种方式容易丢掉样本(与视频的“帧”类似),表现出来的效果就是音质很差,强烈不建议用asetpts,用atempo就好;
  • -map 用于指定输出视频的轨道,-map的顺序决定输出文件中的轨道顺序,本例第一个-map后面跟的是[v],是视频轨,第二个[a]是音频轨,所以输出的视频output.mp4中第1轨(编号为#0)是视频轨,第2轨(编号#1)是音频轨。

官方文档:Speeding up/slowing down video/audio

转mp4

首先需要用-i单独查看视频编码,如果视频编码已经是h.264,那就不需要重新编码,直接用-c:v copy复制原编码就行,这样能极大的节省时间,因为不需要重新编码会快很多(一般1-5秒就搞定),如果不是h.264,则需要指定编码格式,重新编码

ffmpeg -i input.mov -c:v libx264 -b:v 5000k output.mp4

-b:v 5000k 比特率,一般需要先用-i查看原比特率,再看需要设置为多少,一般不是非常大的要求,都可以减半,比如之前10000kbps,现在5000kbps就行。


如果是mkv转mp4,用以上方法很可能丢失字幕,需要用-vf指定字幕

ffmpegnb -i input.mkv -vf "subtitles=input.mkv" -b:v 5000k output.mp4

解释:因为mkv容器封装字幕不是直接到字幕渲染到视频中,而是单独一个轨道(这样封装会快很多,因为渲染到视频中需要重新编码,比较慢),但是mp4必须把字幕渲染到视频中,需要重编码,所以需要用"subtitles=input.mkv"从mkv视频中选择字幕。

h265/hevc不兼容问题

我们知道,h265其实就是hevc编码格式(Hight Efficiency Video Coding),正如它的名字说的那样,它是非常高效的视频编码,同画质情况下,视频文件体积可比h264体积小约50%,所以我们当然希望都用它来存储,因为这样可以大大节省硬盘存储空间。

ffmpeg把视频转换为h265编码非常简单,把前面转换mp4的libx264换成libx265就行了,格式还是mp4

ffmpeg -i input.mov -c:v libx265 -b:v 5000k output.mp4

可是这样转换出来的h265编码视频,是无法存储到iPhone的“照片”app中的(原因是因为它不支持播放),也无法用macOS的“快速预览”(空格键)播放。

但是iPhone和macOS早就支持h256了,特别是iPhone自己拍出来的视频都是h265的,怎么可能就播放不了呢?

原因出在它们的封装code point(码点)不一样,hvc1和hev1是两种不同的码点,用于表示mp4容器中打包hevc流的两种方式,苹果设备只支持hvc1而不支持hev1,所以导致无法播放

# iOS/macOS
Stream #0:0[0x1](und): Video: hevc (Main) (hvc1 / 0x31637668)

# ffmpeg
Stream #0:0[0x1](und): Video: hevc (Main) (hev1 / 0x31766568)

解决方法:添加一个-tag:v hvc1即可

ffmpeg -i input.mov -c:v libx265 -tag:v hvc1 -b:v 5000k output.mp4

如果你一开始得到的视频就是hevc编码的,无需转码,只需要把码点改成hvc1即可

ffmpeg -i input.mp4 -c:v copy -tag:v hvc1 -c:a copy output.mp4

HEV1标记指的是HEVC视频样本描述符的版本1,其中视频数据存储在一个单独的NAL单元中。而HVC1标记指的是HEVC视频样本描述符的版本2,其中视频数据存储在多个NAL单元中。因此,HVC1标记通常用于需要流式传输的HEVC视频中,因为它可以更容易地进行分段和传输。

在实际应用中,大多数情况下都使用HVC1标记来标识HEVC视频中的样本描述符,因为它更适合网络流媒体传输。不过,有些设备可能不支持HVC1标记,这时候就需要使用HEV1标记。

参考:FFmpeg hevc codec_tag兼容问题Encode H265 to hvc1 codec

转纯音频mp4

mp4是一个容器格式而不是一种编码格式,典型的mp4包含一个视频轨一个音频轨,视频格式一般为h264,音频一般为aac,但现在我们要纯音频mp4,就代表没有视频,可是mp4又必须要视频轨,怎么办?其实视频本质是一系列连续变化的图片,但事实上一张图片也可以是一个视频,只不过这个“视频”不会播放,图案不会变化罢了。

所以我们只需要用一个音频+一张图片即可制作纯音频mp4,而这张图片就是这个音频的“封面”

ffmpeg -i input.mp3 -i cover.jpg -c:a aac -map 0:a:0 -map 1:v:0 output.mp4
  • -map用于从输入源中选择轨道,第一个-map选择的轨道将会作为输出文件的第一轨道(编号#0),第2个-map选择的轨道将会作为输出文件的第2轨道(编号#1)
  • 0:a:0表示选择第0个-i指定的文件路径中的所有音频(a:audio)中的第0个音频;
  • 1:v:0同理,表示选择第1个-i指定的文件路径中的所有视频(v:video)中的第0个视频;

:一个文件中可能有多个音频轨也可能有多个视频轨,用以下命令可查看音频视频的信息(图片属于视频,只不过是特殊的静止的视频)

ffprobe -hide_banner -i /path/to/音频或视频.xxx

用以上命令输出的信息,一般会有Stream #0:0Stream #1:0这种记号,冒号前面有#号那个代表-i(从0开始数),冒号后面就代表当前是第几轨道。

比如-i cover.jpg这个-i在第二位,从0开始数,它的编号就是1,所以-map 1:v:0中第一个数字为1,v表示匹配它的所有视频轨(事实上它也只有视频轨),最后那个0,就表示选择所有视频轨中的第0条视频轨,最后这个0正好也对应Stream #0:0Stream #1:0中冒号后面那个0,所以如果写-map的时候你不知道应该写0还是写1,比如本例,你就可以用ffprobe -hide_banner -i cover.jpg来查看这个图片在图片轨的第几条轨道,答案当然是在第0条轨道,因为它只有一个轨道。

转m4a

m4a是mp4视频中的音频,m4a是容器格式,m4a的编码格式还是aac,所以用-c:a aac指定编码为aac,用-vn表示没有视频(vn:video none)

ffmpegnb -i input.mp3 -c:a aac -vn  output.m4a

# 批量从当前目录下所有mp4文件中提取出m4a文件
for i in *.mp4; do ffmpeg -i "$i" -vn -c:a copy "${i:0:$[${#i}-4]}.m4a"; done

转gif

# 原速
ffmpeg -i input.mp4 output.gif
# 4倍速,即1/4=0.25,所以就写0.25
ffmpeg -i input.mp4 -vf "setpts=0.25*PTS" output.gif

检测音量

检测音量官方文档:Audio Volume Manipulation

要调整一个视频的音量,我们通常需要检测一下它原来的音量是多少,这样才方便调整,否则就要不停的试验。

以下命令用于检测一个视频中音频的音量

ffmpeg -i 爱的代价_clip_inc_20dB.mp4 -filter:a volumedetect -f null /dev/null

正常情况下,ffmpeg有输入必须有输出,但是我们检测音量不需要输出一个文件,所以我们把它输出到/dev/null里(熟悉Linux的童鞋应该知道,这是一个空设备,输出到这儿的数据就代表丢弃)。

可是-f null又是什么意思呢?因为正常的输出是xxx.mp4xxx.mov等等,是有后缀的,ffmpeg可以自动检测后缀并指定输出格式,但是这次我们输出到/dev/null,没有后缀,ffmpeg检测不出来要输出什么格式,它就会报错,所以我们用-f null手动指定它的输出格式为null-f表示format,格式的意思(搜遍了ffmpeg的帮助文档也没有-f这个参数,就算在网上搜,也很少相关资料,不知道为什么)。

上图命令执行结果如下图红框所示,mean_volume是平均音量,max_volume是最大音量
image.jpg
:mean有平均值的意思。

调整视频音量

调整音量官方文档:Audio Volume Manipulation

将音量增加20dB

 ffmpeg -i 爱的代价.mp4 -af "volume=20dB" -c:v copy 爱的代价20dB.mp4

其中-af表示audio filter,音频过滤器,是-filter:a的简写,volume=20dB意思是将原来的音量增加20dB(分贝)。


同理,如果增加负数,那就是减少相应的分贝,比如以下命令表示在原音量基础上减少5分贝

 ffmpeg -i 爱的代价.mp4 -af "volume=-5dB" -c:v copy 爱的代价-5dB.mp4

当然,还可以用倍数表示,比如以下命令表示将音量调整为原音量的1.5倍

 ffmpeg -i 爱的代价.mp4 -af "volume=1.5" -c:v copy 爱的代价1.5.mp4

也可以静音,把音量设置为0,就是静音

 ffmpeg -i 爱的代价.mp4 -af "volume=0" -c:v copy 爱的代价_mute.mp4

批量把当前目录下所有.m4a文件的音量设置为原来的1.5倍

for i in *.m4a; do ffmpeg -i "$i" -af "volume=1.5" "${i:0:$[${#i}-4]}x1.5.m4a"; done

替换视频中的音频

替换视频中的音频,其实就是合并视频和音频,不需要先去掉原视频中的音频,因为所谓替换,其实并不是替换,而是从原视频中选择视频轨(不选音频轨,代表只有视频没有声音),从音频中选择音频轨,然后再把选择的视频轨和音频轨合并在一起生成一个新的视频文件。

首先把你想要替换(或叫合并)到视频中的音频文件转换为aac格式

# 转成aac
ffmpeg -i /path/to/input.wmv -c:a aac -b:a 128k /path/to/output.aac

然后再合并到mp4中

ffmpeg -i /path/to/input.mp4 -i /path/to/output.aac -c copy -map 0:v:0 -map 1:a:0 /path/to/output.mp4

如果是嵌入了字幕的mkv格式视频,字幕也属于一个轨道,需要用-map 0:s:0把它映射进去(s代表subtitles),否则字幕就会丢失

ffmpeg -i /path/to/input.mkv -i /path/to/output.aac -c copy -map 0:v:0 -map 1:a:0 -map 0:s:0 /path/to/output.mkv

解释:

  • 1、用两个-i分别输入一个视频和一个音频,-c copy表示原样复制视频和音频,不对它们进行处理。注意:还可以写成-c:v copy表示只复制视频(v:video),-c:a copy表示只复制音频(a:audio);
  • 2、-map用于从输入文件中选择轨道给输出文件,比如-map 0:v:0,意思是选择第一个(0)输入文件中的视频轨(v)中的第一轨(0),即:v左边的0表示第一个-i(程序中都是从0开始,所以编号0都代表第一,同理编号1代表第二),v右边的0,代表视频轨中的第0轨(视频轨可以有多条,但一般都只有一条);
  • 3、由第2点,我们很容易知道-map 1:a:0就是选择第二个-i文件中的音频轨(a:audio)中的第0轨;
  • 4、由前面2、3点可知,第一个-map选择的是输入文件中的视频轨,第二个-map选择的是输入文件中的音频轨,那么输出文件中的,第一条轨道就是视频轨(第一个-map),第二条轨道就是音频轨(第二个-map);
  • 5、第三个-map是第三条轨道,是字幕,其中的s代表subtitles(字幕)。

把当前文件夹下的所有.m4a音频替换到同名的.mp4视频中,并把音量调整为原来的1.5倍

for i in *.m4a; do ffmpegnb -i "${i:0:$[${#i}-4]}.mp4" -i "$i" -c:v copy -b:a 128k -af "volume=1.5" -map 0:v:0 -map 1:a:0 "${i:0:$[${#i}-4]}-rm-voice.mp4"; done

制作多音轨视频

比如你想做一个点歌机的视频,它必须要有两个音轨,一个是有原唱的,一个是只有伴奏的,这样就是双音轨,当然也可以三条音轨以上,那样就叫多音轨了。

ffmpegnb -i 带人声.mp4 -i 伴奏.aac -c copy -map 0:v:0 -map 0:a:0 -map 1:a:0 output_双音轨.mp4

假设mp4是带原唱的,我想再加一路伴奏进去,事实上并不是加进去,而是从mp4视频中获取到视频轨和音频轨,再从第二个-i中获取到音频轨,然后再把这一路视频轨和两路音频轨合并成一个视频(-c copy表示视频和音频编码都直接从原轨中复制,这样不会重新编码,会比较快,质量也能完全保持原质量)。-map出现的顺序即为输出的视频中的轨道顺序。


把当前目录下的所有mp4都做成双音轨,第一音轨为当前目录下的“视频名-accompanimentx1.5.m4a”,第二音轨为视频原音轨

for i in *.mp4; do ffmpeg -i "$i" -i "${i:0:$[${#i}-4]}-accompanimentx1.5.m4a" -c copy -map 0:v:0 -map 1:a:0 -map 0:a:0 "${i:0:$[${#i}-4]}_双音轨.mp4"; done

压缩视频

目前我知道的压缩视频的参数有四个:

  1. 等比例缩小宽高(其实就是缩小组成视频的图片的宽高),比如1920×1080(1080p)的,可以等比例缩小到1280×720(720p);
  2. 降低比特率(也叫码率),比特率是视频每秒钟播放的比特数(即二进制位数,1Byte=8bit,1KB=1024Byte=1024x8bit=8192bit),码率有动态码率和固定码率;
  3. 降低帧率,帧率即每秒播放的图片数,一般最低24fps(frame per second, 帧每秒),现在大多数都是30fps;
  4. 增大恒定速度因素(-crf),取值范围0-51,一般认为是17-28范围比较正常,大于28,视频会比较模糊,小于17,视频文件会很大,并且没啥必要(比如肉眼可能已经分辨不出17跟16、15的区别)

等比例修改分辨率

ffmpeg -i input.mp4  -vcodec libx264 -preset fast -crf 28 -vf "scale=640:-1" output.mp4

scale=640:-1 表示等比例缩放,宽缩放为640,高为-1表示自动根据比例缩放。


修改视频分辨率,以下两种方法效果一样

# 使用-s,s是size(即尺寸)的意思,后面跟着的是宽x高,注意乘号就是小写字母“x”
ffmpeg -i input.mp4 -s 320x240 output.mp4

# 使用-vf,video filter(视频过滤器)的意思,后面的scale是“比例”的意思
ffmpeg -i input.mp4 -vf scale=320:240 output.mp4

# 当然还可以直接修改为原值乘以或除以一个数,其中iw和ih分别表示原来的宽和高
ffmpeg -i input.mp4 -vf scale=iw/2:ih/2 output.mp4
ffmpeg -i input.mp4 -vf scale=iw*0.9:ih*0.9 output.mp4

修改视频比特率(即码率,每秒钟播放的比特数)

# -b表示bitrate,v表示video,如果是音频的比特率那就是:-b:a,因为a表示audio,后面跟着的是你要设置的比特率数值,可以用k、M等单位,分别表示kbps/Mbps
ffmpeg -i input.mp4 -b:v 700k output.mp4

压缩当前目录下所有mp4文件

# 自动比特率(比特率是决定视频清晰度的其中一个参数)
for i in *.mp4; do ffmpeg -i "$i" "${i:0:$[${#i}-4]}_opt.mp4"; done

# 指定视频比特率为5000kbps(比特率是决定视频清晰度的其中一个参数,越大越清晰,同时文件体积也越大)
for i in *.mp4; do ffmpeg -i "$i" -b:v 5000k "${i:0:$[${#i}-4]}_opt.mp4"; done

修改帧率(每秒播放的图片数)

# 1、用 -r 参数设置帧率
ffmpeg -i /path/to/input.mp4 -r 25 /path/to/output.mp4

# 2、用fps的filter设置帧率"fps=fps=25"并没有写重复,就是这么写的,我也不知道为什么...
ffmpeg -i /path/to/input.mp4 -vf fps=fps=25 /path/to/input.mp4

# 设置帧率为29.97fps,下面三种方式具有相同的结果:
ffmpeg -i input.avi -r 29.97 output.mp4
ffmpeg -i input.avi -r 30000/1001 output.mp4
ffmpeg -i input.avi -r netsc output.mp4

按指定输出文件大小来压缩

# 控制输出文件大小 -fs (file size首字母缩写)
# 计算输出文件大小:(视频码率+音频码率) * 时长 /8 = 文件大小
ffmpeg -i input.avi -fs 1024K output.mp4

动态码率与缓冲区

bitrate,中文叫比特率,也叫码率,意思是视频每秒钟播放多少比特(bit),我们知道,1bit就是一个二进制位,所以每秒钟播放多少比特,这能影响整个视频的文件大小,比如一个视频文件总时长为10秒,如果每秒播放的比特数为5000kbps,那就表示5000/8=625KB,即一秒钟会播放625字节的数据,10秒就是6250KB,6250/1024=6.1M,说明这个视频文件的大小为6.1M。

然而比特率不可能一直保持在一直恒定的数值,即使你用-b:v 5000k这样把码率设置为5000k,它也不可能一直5000k,它会围绕这个值做一个上下浮动,因为播放视频其实就是播放一张张的图片,每秒钟图片包括的信息可能不一样,比较复杂的画面,它包含的信息可能比较多,码率就大点,简单的画面信息少,可能码率就少点。

如果我们设置固定码率,其实那些用比较少比特就能描述的画面,也会被硬塞一些无效数据用来“保持”固定码率,这个其实是没有必要的,所以就有了动态码率的说法。

动态码率
动态码率,就是可以用minratemaxrate来控制最大和最小码率,只要保证码率在这个范围就可以,这样,那些本来就不需要太多比特来描述的画面就不需要太高的码率,它会自动降低码率,这样就达到了压缩视频而又不会太影响清晰度的目的。

然而,单纯靠minratemaxrate是不行的,因为动态码率涉及到码率变化,那怎么变?多久变一次?minratemaxrate是无法指定的,所以我们要用另一个参数-bufsize来协助,bufsize,英文好点的一看就知道它应该是buffer size的缩写,也就是缓冲区。

缓冲区-bufsize
缓冲区,就是ffmpeg处理视频的时候,暂时存储视频数据的地方,每当缓冲区满了,ffmpeg就开始根据缓冲区的数据计算该缓冲区所有数据的码率应该是多少,当然它会在-minrate-maxrate指定的范围内,一般是平均值上下。

如果缓冲区没有缓冲区,ffmpeg可能很长时间才计算一次码率,但是如果这段时间内的视频内容变化比较大,码率一平均的话,就比较不适应该时间段内的所有时间,比如10秒才计算一次,那可能第一二秒需要的码率很低,而七八秒需要的码率很高,但由于时间拉太长了,导致低码率那边也被设置成偏高的码率,这就是没有必须的,而且这会造成视频不稳定。

如果缓冲区太小,那说明它很快满,每次满了,ffmpeg就得计算一次当前缓冲区数据的码率,这会导致视频质量降低,因为ffmpeg要一直遵守这个缓冲区的限制,让它没有空间去处理一些优化,因为缓冲区太小,它可能没有包含足够多的帧来让优化起效果。

所以无论是使用-b:v指定码率,还是用-minrate-maxrate指定,都建议设置一个-bufsize,事实上-minrate-maxrate-bufsize一起使用,否则报错,而-b:v倒是可以不用-bufsize

一般来说,-bufsize可以设置为跟-b:v一样大,然后慢慢测试增大它,直到码率跳动太大(也就是比你用-b:v设置的码率高太多或者低太多),这时的-bufsize值基本上就是太大了,你可以适当缩小一点试试,直到码率跳动范围不那么大。

实例

ffmpeg -i /path/to/input.mp4 -c:v libx264 -b:v 2M -maxrate 2M -bufsize 1M /path/to/output.mp4

参见:
Limiting the output bitrate
How to consider bitrate, -maxrate and -bufsize of a video for web

视频翻转(镜像)

我们平时说的镜像其实都是水平翻转,水平翻转一个视频,我们可以用-vf "hflip"来翻转,h表示horizonal(水平的),flip表示翻转,同理,垂直翻转用-vf "vflip",v是vertical,垂直的

# 水平翻转
ffmpeg -i input.mp4 -vf "hflip" -b:v 4000k -c:a copy output.mp4

# 垂直翻转
ffmpeg -i input.mp4 -vf "vflip" -b:v 4000k -c:a copy output.mp4

由于翻转视频是会修改视频内容的,所以必须指定一下视频比特率,否则它会用默认比特率,而默认值会比较低,造成视频不够清晰。另外,由于我们只是修改视频,所以音频直接copy原来的编码就行(否则它貌似也会被重编码,但这是不必要的)。

裁剪视频

ffmpeg -i input.mp4 -vf crop=1080:743:0:454 -b:v 5000k output.mp4
  • -vf video filter,表示使用某个视频过滤器
  • crop-vf指定的视频过滤器,即裁剪视频过滤器,它的参数为crop=w:h:x:y,w为width,指定裁剪宽度,h为height,指定裁剪高度,x和y裁剪起始点(左上角坐标点)
  • -b:v b即bitrate,比特率(也叫码率),v表示视频比特率,另外有-b:a表示音频比特率
  • 本例的-vf crop=1080:743:0:454表示从input.mp4的(0,454)坐标为左上角起始点,裁剪一个宽x高=1080×743的视频,-b:v 5000k表示指定视频比特率为5000kbps.

获取裁剪参数(简单情况)

根据前面的裁剪视频可知,要裁剪一个视频,我们要两个参数:

  • 1、从哪里开始裁剪(即裁剪起始点坐标,裁剪起始点都是视频左上角,所以这个坐标应该是要裁剪出来的视频的左上角在原视频里的坐标点);
  • 2、要裁多大尺寸(即要裁剪的宽x高是多少);

先用播放器(比如IINA)打开这个视频,暂停播放,然后对这个播放器窗口截屏。

注意:如果使用自带的截图,cmd+shift+4启动截屏后,按一下空格可以切换到对窗口截屏,此时光标变成一个相机,这个相机放到某个窗口上,它会用淡蓝色显示截取的区域,此时点击鼠标即可截取这个窗口,但它默认是有阴影的,有阴影就会影响尺寸,如果不想要阴影,可以按住option键再来点击鼠标,这样截出来的图就是没有阴影的。

如果用其它截图软件截,也是鼠标点击窗口就可以直接截取窗口的(千万不要自己去框选,这样是选不准的),但是有些截图软件也是默认有阴影的,但一般可以设置不要阴影,反正我们这个截图是不能要阴影的,否则会影响判断尺寸。

截取图片之后,用自带的“预览”打开,其实macOS双击图片默认就是用“预览”打开的,除非你修改过。


打开图片之后,用选择工具框选一下(打开后直接用鼠标划选就可以,因为默认就是选择工具),就可以知道它的尺寸,注意这是截图的实际尺寸
Xnip2021-09-24_17-16-16.jpg

如果用预览打开后,又用普通的截图工具去看这个宽度,看到的并不是截图的实际尺寸,而是等比例缩小过的,另外手工划选是不够准确的
Xnip2021-09-24_17-32-49.jpg

前面用预览打开的图中可以看到实际尺寸是720×1280,这个尺寸就是标准的9:16=0.5625,而我用普通的截图软件截的尺寸是363×642≈0.5654。

右击视频→显示简介,可以查看视频本身的尺寸,可以看到也是720×1280的,跟我们截的图完全一样,这说明我们在图片上看到的尺寸跟视频尺寸是一样的
image.jpg

我们再来看,如果我们想把这个抖音号截取掉,在预览里用选择工具框选的尺寸是720×132,这样我们就知道了y坐标是132,x坐标肯定是0,因为是从最左侧截起,所以我们现在就能确定ffmpeg中需要指定(0,132)为顶点
image.jpg

知道了顶点,还需要知道截取的宽高,我们再来用选择工具框选一下,发现我们要的宽高,宽就是原视频宽度,也就是720,高是1000(因为底部也会出现抖音号)
image.jpg

最后我们用ffmpeg -i来查看这个视频的比特率与帧率,发现都不高,毕竟是抖音上下载的,所以我们不用修改比特率和帧率了
image.jpg

根据我们得到的顶点坐标以及要截取的宽高,得到裁剪命令如下

ffmpeg -i input.mp4 -vf crop=720:1000:0:132 output.mp4

最终效果,裁剪出来的视频刚好是我们指定的720×1000,而且裁剪的起始位置(视频的左上角位置为起始位置)也确实是我们指定的(0,132)坐标,这样就完成了
image.jpg

:我知道抖音视频可以直接下载没有水印的,但是我这里只是一个演示而已,并不是说我搞这个就是为了裁剪抖音视频。


获取裁剪参数(复杂情况)

上边只是比较简单的情况,因为截图长宽跟视频长宽刚好一样,但是事实上,很多时候是不一样的,很多时候,我们截的图是一个等比例缩小的图。

比如我用我安卓机拍的视频,宽高为1080×1920,也是9:16=0.5625,但是我用播放器打开这个视频,然后对播放器窗口截图,发现截的图并不是1080×1920,原因是我电脑屏幕没有这么大,播放器的窗口肯定是等比例缩小了的,事实上比例还稍微有点不对(跟0.5625有点点出入,但是基本上没啥问题,因为前两位小数是一样的)。

又比如我用我安卓机录屏,录的视频尺寸是1080×2400,1080:2400=9/20=0.45
image.jpg

显然用播放器打开,截图也不可能截出这个尺寸,截到的都是等比例缩小的,我这边亲测我截到的图是742:1648=0.4502427184≈0.45
image.jpg

假设我们要把头部有名字的地方去掉,那么根据下图我们可以得到截取的顶点坐标为(0,164)
image.jpg

要截取的尺寸为:宽x高=742×1400
image.jpg

错误参数:按前面得到的数据,我们可以得到这样的命令,然而这个命令的参数是错的

ffmpeg -i input.mp4 -vf crop=742:1400:0:164 output.mp4

错误的原因是我们从截图里得到的尺寸跟视频实际尺寸不一样,所以我们要根据视频尺寸等比例放大才行。

视频原尺寸为:宽x高=1080×2400,我们截图得到的尺寸为:宽x高=742:1648

我们把原宽1080除以截图宽742看看:1080/742≈1.456,意思就是说,我们截图的一个像素,就相当于原视频中的1.456个像素;
再按这个方法看一下高:2400/1648≈1.456,意思就是说,截图的一个像素,相当于原视频中的1.456个像素,因为宽高是等比例缩小,所以无论是宽还是高,它的像素与原视频像素比都是相同的(当然会略有误差)。

现在我们来算一下:

  • 实际截取宽度:因为在截图中我们要截取的宽度为742(全宽),而这742个像素,每个像素都相当于原视频的1.456个,所以我们应该在原视频中截取742×1.456≈1080个像素;
  • 实际截取高度:因为在截图中我们要截取的高度为1400,而这1400个像素,每个像素都相当于原视频的1.456个,所以我们应该在原视频中截取1400×1.456≈2038个像素。
  • 实际顶点:因为实际顶点只有y轴有数值,所以只要计算y就好了,我们截图的顶点y值是164,由于截图的高一个像素相当于原视频1.456个像素,所以我们计算164个像素在原视频中应该是164×1.456≈238.78个像素,具体是239,还是238,可以尝试切一下看看,如果视频比较长,可以先从里面切出一小段(几秒钟时长就够),再来测试裁剪效果,在我这个例子中,由于我的y轴没有靠近一些不同颜色的边界,所以无论我取238还是289都不会影响效果,我这里就取238吧;

也就是说,我们实际裁剪出来的视频:宽x高=1080×2038,顶点为(0,238),得到如下裁剪命令

ffmpeg -i input.mp4 -vf crop=1080:2038:0:238 output.mp4

裁剪效果还不错,顶部的名字和底部左下角的小图标以及右下角的叉都被截掉了
Xnip2021-09-24_20-22-20.jpg


截取视频

比如我想截取出某个视频中第10秒到第21秒那一段,就叫截取!所以截取视频就像在视频剪辑软件的时间线里,用刀片工具把视频切割出需要的部分,删掉头尾,留下切割出来的部分并导出。

主要参数

  • -ss指定起始时间;
  • -t指定截取长度
  • -to指定截取结束时间点(比如-to 00:00:10表示从开头截到第9秒,第9秒结尾也是第10秒的开头,所以-to指定的时间是指截取到这个时间之前,而不包含这个时间本身);
  • -ss要放在-i之前,如果放在-i之后需要解码,会比较慢,但比较精确,建议在-i前后都放上-ss这样既精确又快,见:这里

截取视频(-i是input,-ss为截取起始时间,-t为截取的长度)

ffmpeg -ss 00:00:20.300 -t 00:00:18.500 -i 爱的代价.mp4 -c:v copy -c:a copy 爱的代价_clip.mp4

-ss-t都要放在-i前面(放在后面也可以但是会很慢,因为需要先做解码操作)。


截取视频(-i是input,-ss为截取起始时间,-to为截取结束时间)

ffmpeg -ss 00:00:05 -t 00:00:18 -i 爱的代价.mp4 -c:v copy -c:a copy 爱的代价_clip.mp4

该命令表示截取5-18秒时间段内的视频。


只指定起始时间,不指定长度,表示截取到末尾

ffmpeg -ss 00:33:45 -i /path/to/input.mp4 -c:v copy -c:a copy /path/to/output.mp4

只指定长度,不指定起始时间,表示从视频开头开始截

ffmpeg -t 00:00:20 -i /path/to/input.mp4  -c:v copy -c:a copy /path/to/output.mp4

从第5秒开始,截取20秒长的片段(-ss-t如果只写一个数字,则单位是秒)

ffmpeg -ss 5 -t 20 -i /path/to/input.mp4 -c:v copy -c:a copy /path/to/output.mp4

无论-ss-t,都用这个格式:时时:分分:秒秒.毫毫毫,比如-ss 00:00:20.300表示从0时0分20秒再过300毫秒处开始截取,而-t 00:00:18.500则表示截取长度为18秒再加500毫秒的长度。

当然,还可以是另一个格式,就是-ss 1 -t 5,只写一个数字,这个数字的单位是秒,所以这里表示从第1秒截取到第5秒。

:1s=1000ms,所以毫秒位置最大可写999,因为到了1000就进1了。

截取多段视频

假设我录了一个10秒长的视频,我想把其中的第5-7秒删掉,按截取视频的方法,我先截取1-4秒,然后截取8-10秒,最后把这两段合并起来,这样虽然可以达到我们的目的,但非常麻烦!

正确方法是使用select来“挑选”你需要的片段,select是一个视频过滤器,对应的音频过滤器为aselect(a指audio),使用select和aselect,我们可以直接“挑选”我们需要的片段,最后输出成一个文件,根本不需要先切割再合并。

下例是使用select和aselect分别拾取视频中的第0-16秒与73秒以后的视频

ffmpeg -i /path/to/input.mp4 -vf "select='between(t,0,16)+gte(t,73)',setpts=N/FRAME_RATE/TB" -af "aselect='between(t,0,16)+gte(t,73)', asetpts=N/SR/TB" -b:v 5000k /path/to/output.mp4

音频轨道和视频轨道是分开的,所以需要分别用select和aselect来拾取视频和音频对应的片段,但它们对应的时间都是一样的。

between、gte、lte等等用于选择你要的时间段(单位是秒,可以写小数);

setpts:用于设置视频的pts。pts是presentation time stamp,显示时间戳,简单的说,就是表示视频的某一帧应该在哪个时间显示;

asetpts:用于设置音频的pts,音频由于没有“帧”的说法,但它有采样率,其实也是对应某个音频片段在某个时间播放。

N/FRAME_RATE/TB:N是总帧数,其实这个式子原式是N/(FRAME_RATE*TB),TB是Time Base,其实就是帧时长(即每帧显示多长时间),理论上,帧率x帧时长,应该等于1秒才对,比如帧率是一秒30帧,帧时长肯定就是1/30秒,那30x(1/30)肯定就等于1秒,如果是这样为什么还要这么乘呢?因为有时候必须重新计算,具体我也不是很懂,可以看一下这里,反正这整个N/FRAME_RATE/TB的意思就是指定帧率为FRAME_RATE(FRAME_RATE是原视频的帧率变量)。

N/SR/TB:这个跟视频只有一个不同,就是SR,SR是sample rate,采样率,准确的说是音频采样率,而帧率其实可以理解为视频采样率,因为音频不像视频,有帧(一张张图)的说法,所以它对应的这个值是SR(采样率),但原理跟视频是一样的,只不过音频叫采样率,即N/SR/TB用于指定aselect挑选视频片段的时候使用原视频的采样率(即SR)。

音画同步

我们经常能遇到视频中音频和画面不同步问题(也叫音画不同步,即声音和画面不同步),那要怎么调整呢?

我们假设有一个唱歌视频“爱的代价.mp4”,它的声音比视频快了1秒,也就是你听到声音之后才看到它说话的口型,因此我们要把它的音频延迟1秒,命令如下

ffmpeg -i 爱的代价.mp4 -itsoffset 00:00:01.000 -i 爱的代价.mp4 -map 0:v -map 1:a -vcodec copy -acodec copy 爱的代价_sync.mp4

解释:ffmpeg是可以有多个输入的,像上述命令就用了两个-i表示有两个输入,而且这两个输入都是同一个视频,因为我们要从其中一个视频中抽取视频轨,而从另一个视频中抽取音频轨,然后再把这两个轨道合并在一起,因为只有把音频和视频分开了,我们才能调整它的同步问题。

  • -i 爱的代价.mp4 第一个视频输入;
  • (同步的关键)-itsoffset 00:00:01.000 -i 爱的代价.mp4 第二个视频输入,它前面的-itsoffset 00:00:01.000表示这个视频延迟1秒钟输入;
  • -map 0:v-map 1:a -map 0:v表示抽取第1个输入文件中的视频轨道数据(0:v中0表示第一个输入文件,v表示video,即视频轨),-map 1:a表示抽取第2个输入文件中的音频轨道数据(1:a中1表示第二个输入文件,a表示audio,即音频);
  • vcodec copy 指定视频编码(v表示video),copy意思是直接复制原来的编码,不进行转码处理,还可写成-c:v copy
  • -acodec copy 指定音频编码(a是audio的意思),copy意思是直接复制原来的编码,不进行转码处理,还可写成-c:a copy
  • 爱的代价_sync.mp4 指定输出文件名(可以带路径),其中的.mp4表示输入格式,ffmpeg会自动获取这个格式,当然也可以在它前面用-f mp4手动指定(f表示format)。
  • -map 0:v-map 1:a 的顺序决定输出文件中视频轨和音频轨的顺序,如果把它们的顺序调换过来也是没问题的,只不过音频轨变成了第一个轨道,但调换顺序不会改变它们获取视频轨或音频轨的来源,因为0:v1:a中的0和1,已经决定了它们从第一还是第二个输入文件中获取(之所以用0和1而不用1和2,是因为编程届下标都是从0开始的)。
  • -vcodec copy -acodec copy只用来指定输出文件的音频和视频编码,跟它们的顺序无法,比如-map 0:v -map 1:a表示视频轨在前,音频轨在后,但指定编码可以反过来写-acodec copy -vcodec copy,因为这个跟顺序是无关的,vcode就是指定视频编码,跟它写在前还是写在后没有关系。

由于第二个视频整体输入被延迟了1秒,所以从它里面提取出来的音频也比原来延迟了一秒,这样第一个视频轨和第二个音频轨合成之后的效果,就变成了音频轨延迟一秒的效果。

当然,如果你事先不知道延迟了多少,只能靠肉眼看和听,猜一下延迟了多少,然后再来测试转换后的效果,虽然音画同步在不转换编码的情况下很快就能完成,但如果文件太大,比如上G,如果直接用原文件测试,每测试一次就相当于复制出一个上G 的文件,对电脑硬盘不好,建议先从里面切出一小段来测试音画同步,测试成功后,再对整个文件进行操作。

关于-map
map在这里是“映射”的意思,在ffmpeg里,-map选项用于手动控制每个输出文件的流选择。

如下图所示,ffmpeg会给每个输入文件一个编码,根据编程届的惯例,下标当然都是从0开始的,于是,第一个输入的文件被编号为#0,第二个被编号为#1,……,以此类推,
image.jpg
同理,每个视频可能有很多个轨道,当然一个正常的视频,至少也有一个视频轨和一个音频轨,但是有些可能有多个音频轨(比如一部电影可能有不同语言),以及有多个字幕轨(比如一部电影可能有不同语言字幕)。

每个轨道也会从0开始编号,而且前面还会标记它们属于哪个输入,比如:Stream #0:0表示第一个输入文件中的第一个轨道(通常第一个轨道都是视频轨道),当然由于编程届都是以0为下标的,所以第一个都是0;又比如:Stream #1:1表示第二个输入文件中的第二个轨道,一般第二个轨道都是音频轨道,当然由于编程届都是以0为下标的,所以第二的下标是1,以此类推。

所以,-map 0:v其实可以写成-map 0:0,因为v代表video(视频),0:v意思是抽取输入文件中编号为0的文件(就是第一个-i指定的那个文件)中的视频轨,而视频轨我们知道就是0号轨道,所以这两个写法在这里是相同的,当然有些有画中画效果的视频,它可能有两个甚至多个视频轨,这个就需要你按需求选择其中的某个轨道。

同理,-map 1:a中的a表示audio(音频),表示抽取音频轨,其实也是可以写成-map 1:1的,因为其实我们是要从输入编号为1的输入文件中(其实就是第二个-i指定的那个文件)中的音频轨,而在编号为1的输入文件中,音频轨是1,所以在这里-map 1:a相当于-map 1:1,当然,有些视频中可能会有多个音频轨,比如多语言的电影,比如分轨录制的音乐(钢琴一个轨、吉他一个轨、人声一个轨等等),这些就要根据实际情况选择你需要的轨道了。

-map 0:v-map 1:a的顺序决定了输出轨道的内容,如果调换过来,则会变成音频在第一个轨道,视频在第二个轨道,这对播放没什么影响,不过一般还是视频在第一个轨道好点。

添加水印与画中画

添加水印本质上就是图片叠加到视频的某个位置上,如果把图片换成视频,也就是视频叠加到视频上,就变成“画中画”了。


把水印图片添加到视频左上角

ffmpeg -i /path/to/input.mp4 -i /path/to/watermark.jpg  -filter_complex "overlay=0:0" /path/to/output.mp4

-filter_complex 指定复杂过滤器
overlay=0:0 指定水印位置(水印图片左上角坐标),格式overlay=x:y,x、y是坐标,原点为视频的左上角,所以0:0表示水印的左上角刚好与视频的左上角重合。

注意:确定水印图片位置的本质,就是确定水印图片左上角那个点在视频中的坐标。


把水印添加到视频右下角

ffmpeg -i /path/to/input.mp4 -i /path/to/watermark.jpg  -filter_complex "overlay=main_w-overlay_w:main_h-overlay_h" /path/to/output.mp4

main_w ffmpeg变量,表示视频的宽度;
main_h ffmpeg变量,表示视频的高度;
overlay_w ffmpeg变量,表示要覆盖到视频上的图片的宽度;
overlay_h ffmpeg变量,表示要覆盖到视频上的图片的高度;

其实main_woverlay_w可分别用大写W和小写w来表示,同理main_hoverlay_h可以用大写H和小写h来表示,即上边命令可简化成

ffmpeg -i /path/to/input.mp4 -i /path/to/watermark.jpg  -filter_complex "overlay=W-w:H-h" /path/to/output.mp4

由于overlay指定的是水印图片左上角坐标,所以如果要放到右下角,实际上水印图片左上角的起始坐标是(视频宽-水印宽,视频高-水印高)。

如果你不想刚好靠到右上角边边,那就x,y分别减掉一些像素,比如10,这样就会变成(视频宽-水印宽-10,视频高-水印高-10)。


把水印添加到视频正中心

ffmpeg -i /path/to/input.mp4 -i /path/to/watermark.jpg  -filter_complex "overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2" /path/to/output.mp4

其实本质上都是计算水印图片左上角起始坐标应该在哪里,如果把一张图片放到一个视频的中心,毫无疑问,它的左上角坐标,x坐标为:视频宽的一半-水印图片宽的一半,写成数学表达式就是:视频宽/2-水印图片宽/2,合并一下,就是:(视频宽-水印图片宽)/2,而y坐标同理。


把水印添加到视频右上角

ffmpeg -i /path/to/input.mp4 -i /path/to/watermark.jpg  -filter_complex "overlay=main_w-overlay_w:0" /path/to/output.mp4

前面说过放到右下角,其实右上角,就是把右下角那句命令的y坐标变成0。


把水印添加到视频左下角

ffmpeg -i /path/to/input.mp4 -i /path/to/watermark.jpg  -filter_complex "overlay=0:main_h-overlay_h" /path/to/output.mp4

前面说过放到右下角,其实左下角,就是把右下角那句命令的x变成0。


同时添加两个水印(第一个添加到左上角、第二个添加到右下角)

ffmpeg -i /path/to/input.mp4 -i /path/to/bdlogo.jpg -i /path/to/googlelogo.jpg  -filter_complex "[0:v][1:v]overlay=0:0[flag]; [flag][2:v]overlay=W-w:H-h" -c:a copy /path/to/output.mp4

注意:图片属于无声音的静止视频,你用ffmpeg查看一个jpg图片的信息,你会发现它有一个视频轨道,视频格式为mjpeg

Input #0, image2, from '/Users/bruce/Downloads/bdlogo.jpg':
  Duration: 00:00:00.04, start: 0.000000, bitrate: 4997 kb/s
  Stream #0:0: Video: mjpeg (Baseline), yuvj420p(pc, bt470bg/unknown/unknown), 440x148 [SAR 144:144 DAR 110:37], 25 fps, 25 tbr, 25 tbn, 25 tb
  • 同时添加两个水印,本质上是先添加一个,再把添加后的结果再次添加第二个;
  • 同一滤镜链之间的不同滤镜用逗号隔开(在这里没有其它滤镜,所以没有体现出来)
  • 不同滤镜链之间用分号隔开,在这里体现出来了;
  • 滤镜链输入输出通过方括号标记命名;
  • [0:v][1:v]分别标记第一个输入文件和第二个输入文件中的视频轨(-i输入的文件从0开始算),v表示video,表示取它的视频轨;
  • [0:v][1:v]overlay=0:0[flag]表示把第二个输入文件([1:v])的视频轨覆盖到第一个输入文件([0:v])视频轨上,覆盖起始坐标为(0,0),后面的[flag]是一个自定义名称,用于标记这个操作的输出,你自己写成[abc]也可以,因为这个标记是让后面引用的,你标记的什么,后面就用什么就行;
  • [flag][2:v]overlay=100:75 [flag]表示第一个滤镜链的操作结果,[2:v]表示第三个输入文件的视频轨(由于从0开始数,所以第三个为2,v是video,表示取视频轨);
  • -c:a copy表示复制音频编码。注意不能复制视频编码,因为要覆盖一层图片上去,视频一定得重新编码,所以是无法复制的,如果用-c:v copy将会报错;
  • 假设视频和图片都只有一个轨道,则[0:v][1:v][2:v]可分别写成[0:0][1:0][2:0],冒号前面的数字表示第几个-i输入文件(从0开始数),冒号后面的数字表示轨道编号,可以用ffmpeg -i /path/to/video.mp4ffmpeg -i /path/to/image.jpg来查看它们的轨道编号;
  • 再啰嗦一遍:图片是一种只有一个视频轨的静止视频,用ffmpeg -i /path/to/image.jpg来查看一张图片的信息,可以发现它们有一个视频轨;
  • 按这个原理,你同时添加n个水印都可以,本质上是一个添加完了再添加下一个,用方括号标记一下输出,下一个用的时候引用一下上一个输出就行;

同时添加两个水印(简单写法)

ffmpeg -i /path/to/input.mp4 -i /path/to/watermark1.jpg -i /path/to/watermark2.jpg  -filter_complex "overlay=0:0, overlay=W-w:H-h" -c:a copy /path/to/output.mp4

前面的写法属于通用方法,现在这种写法属于简写,同样你也可以添加n个水印,只要-i输入图片的顺序对应好overlay指定位置的顺序就好了。


同时添加两个水印,并对水印进行缩放

ffmpeg -i /Path/to/input.mp4 -i /Path/to/bdlogo.jpg -i /Path/to/googlelogo.jpg  -filter_complex "[1:v]scale=90:-1[img1];[2:v]scale=iw/2:-1[img2];[0:v][img1]overlay=25:(H-h)/2[flag]; [flag][img2]overlay=W-h:H-h" -c:a copy /Path/to/output.mp4
  • 原理与前面说过的第一种同时添加两个水印的方法一样,其实就是多个操作的组合,只不过用标记来标记一下前面操作的输出;
  • [1:v]scale=90:-1[img1] [1:v]表示第二个文件的视频轨,scale=90:-1表示把该视频的宽设置为90像素,高按比例缩放,[img1]表示输出,只不过用方括号表示标记,可以在后面引用这个值;
  • [2:v]scale=iw/2:-1[img2]跟前面一样,只不过这次处理的是第二个视频,只不过这次是把宽的原尺寸除以2,高还是按比例缩放;
  • 处理好图片的缩放之后,后面overlay的部分可以参考上边说的,都是指定两个视频轨,第二个会覆盖在第一个上,overlay用于指定覆盖者的左上角坐标;

水印宽高设置为视频宽高的百分比

ffmpeg -i /path/to/input.mp4 -i /path/to/image.jpg -filter_complex "[1:v][0:v]scale2ref=oh*mdar:ih[logo-out][video-out];[video-out][logo-out]overlay=0:0" -c:a copy /path/to/output.mp4

本例是把水印的高设置为视频的高,宽自动按原比例缩放。

我们主要是要理解这一句

[1:v][0:v]scale2ref=oh*mdar:ih[logo-out][video-out]

这句是scale2ref滤镜的使用,它的官方文档在:FFmpeg-scale2ref

这个scale就是缩放,ref我个人猜是reference,就是参考、参照。你有没有想过,同一个水印,覆盖到不同视频上,它可能会有不一样的结果,最典型的就是分辨率越大的视频,你会感觉水印在这个视频里越小,可以参考这里

要想避免这种情况的发生,我们需要参考原视频的宽高来去调整水印的宽高,比如,让水印的高永远都是视频高度的十分之一,不管分辨率大的视频还是小的视频。所以reference的意思就是参考原视频大小来对图片进行缩放。

scale2ref滤镜的输入,是先图片,后视频,而输出刚好反过来,这是固定顺序,它用于参考视频的宽高来设置图片的宽高。

oh*mdar:ih 这个,如果熟悉点的应该看的出来,冒号前后分别表示宽和高,i表示input,所以ih就是input height,即输入源的高度,而oh自然就是output height,输出高度,现在主要的问题是,哪个是输入,哪个是输出?

首先,我们知道,输入在前,输出在后,这是ffmpeg命令的惯例,别说ffmpeg,其实所有的命令,只要涉及输入输出的,肯定是输入在前,输出在后的。

所以毫无疑问,在scale2ref=oh*mdar:ih前边的,就是输入,在它后边的就是输出。我们可以看到,它前边有[1:v][0:v]两个源,第一个是图片(因为数字1表示第二个-i,从零开始数,第二个就是1,而第二个-i是图片),而第二个则是视频(0表示第一个-i,因为从0开始数,第一个-i是视频)。

同样我们可以看到,输出也有两个:[logo-out][video-out],根据它们的名字就可以看出来,第一个输出[logo-out]是图片,第二个输出[video-out]是视频,这个是scale2ref过滤器的规定。

那问题来了,输入和输出都有两个,那ih到底指的是哪个输入的高呢?同理,oh指的是哪个输出的高呢?答案是:都是指最最靠近scale2ref=oh*mdar:ih这句命令的。对于输入,最靠近它的是[0:v],所以ih就表示视频的高度,而对于输出,[logo-out]最靠近它,oh是指输出图片的高度。

scale2ref本来就是设置图片宽高的,又因为oh*mdar:ih已经把图片的高设置为ih了,所以它的输出高度肯定也是ih,也就是说,oh其实是等于ih的,所以oh*mdar:ih其实也可以写成ih*mdar:ih(实测图片覆盖效果相同)。

mdar 是视频的显示宽高比(dar是display aspect ratio),也就是我们播放这个视频时它的宽高比(比如4:3、16:9等等),也可写成main_dar,是由(main_w / main_h) * main_sar计算而来;

sar 是sample aspect ratio的缩写(注意不是storage aspect ratio),中文应该理解为“样本宽高比”,“样本”是构成视频的帧(图片)的最小单位,其实就是一个像素,所以在视频中,SAR就是PAR,只不过是两种不同的说法,参见:Confused about PAR(Pixel Aspect Ratio) in video

关于SAR和DAR有以下等式(参见这里)

 SAR_x    width      DAR_x              width
------ x -------- = -------  ==> SAR x -------- = DAR
 SAR_y    height     DAR_y              height

SAR_x就是像素的宽度,SAR_y就是像素的高度,width就是视频横向的像素个数,height就是视频纵向的像素个数,比如1920×1080,就表示横向有1920个像素,纵向有1080个像素。

所以,其实我们可以把上面的等式分子分母分开来,那就有

像素宽 * 横向像素个数 = 播放时的宽
SAR_x * width = DAR_x

像素高 * 纵向像素个数 = 播放时的高
SAR_y * height = DAR_y

之所以会有这些概念,是因为NTSC和PAL电视制式编码以前是用电子管扫描的,现在要转成数字化,它们的等效像素并不是正方形的,而是长方形的。

比如一个704×576的视频,它播放出来是4:3,但按照我们普通的计算,768×576才是4:3,唯一的解释是:显示器的像素是长方形的,它的宽>高,说明横向704个像素加起来的宽度,相当于纵向768个像素加起来的宽度。

特别注意:网上有很多介绍PAR、SAR、DAR之间的关系的文章,大多数都说:PAR x SAR = DAR,然而这里的SAR是Storage Aspect Ratio,不是Sample Aspect Ratio。

这个等式要跟我前面那个等式对比的话,应该是这样

① SAR * (width/height) = DAR ➡ SAR指:Sample Aspect Ratio
   ⬇          ⬇          ⬇
② PAR *      SAR       = DAR ➡ SAR指:Storage Aspect Ratio

其实②中的PAR就是①中的SAR(S是sample,取样的意思,其实就是指单个像素),而②中的SAR(S是Storage)相当于①中的(width/height),也就是分辨率(因为存储的肯定是像素),至于DAR,跟前面等式中的DAR是一个意思,都是指视频播放出来的宽度。我当时研究这个都被这整疯了,查来查去,都对不上,后来在stackoverflow提问才明白:Confused about PAR(Pixel Aspect Ratio) in video


水印终极大招:把logo的高设置为视频高的十分之一,宽按比例缩放,这样无论什么分辨率的视频,logo相对视频的比例都是一样的

ffmpeg -i /path/to/input.mp4 -i /path/to/watermark.jpg -filter_complex "[1:v][0:v]scale2ref=oh*mdar:ih/10[logo-out][video-out];[video-out][logo-out]overlay=W-w-10:10" -c:a copy /Path/to/output.mp4
  • 主要是要理解scale2ref是用于把图片按视频的宽高做处理的,相关原理已经在前面讲过,这里就不讲了;
  • W-w-10:10,冒号前后分别是x,y坐标,单位是像素,y=10很容易看出来,可是x=W-w-10是什么鬼?大W是视频宽度,小w是图片宽度,由于定义图片就是定位它的左上角,而如果我想把logo放到视频右上角的话,logo的左上角x坐标就是:视频宽度-logo宽度,再减去10是要退回来一点,不让它刚好碰到边。

在某个时间段显示水印
典型的就是从抖音下载的视频的水印,一会儿在左上角,一会儿在右下角,实现原理是给overlay加个enable开关,只有在指定时间才执行overlay。

把watermark.jpg在1-5秒时加到左上角,6-10秒时加到右下角(已知input.mp4时长10s)

ffmpeg -i /path/to/input.mp4 -i /path/to/bdlogo.jpg -filter_complex "[1:v][0:v]scale2ref=oh*mdar:ih/10[logo-out][video-out];[logo-out]split [logo1][logo2];[video-out][logo1]overlay=10:10:enable='between(t,1,5)'[flag]; [flag][logo2]overlay=W-w-10:H-h-10:enable='between(t,6,10)'" -c:a copy /path/to/output.mp4

主要原理:前面的命令中,overlay只给了两个参数,也就是x,y坐标:overlay=x:y,其实它还有第三个参数,就是enable参数,我们就是通过enable参数来开关overlay(加水印)功能:overlay=x:y:enable=1

between(t,1,5) t是视频当前播放时间,意思是,如果播放的时间在第1秒和第5秒之间,那么就返回1(true),否则返回0(false),overlay的enable就可以根据这个值来决定是否加水印,between也可以两个参数between(t,5),这样就是不指定起始时间,表示从视频的0秒开始,显示5秒钟水印。除此之外,还有lte(less than equal,小于等于),gt(greater than)等可以用;

split 把一个流分成多个,比如:[logo-out]split [logo1][logo2]就是把scale2ref处理后的logo分成了两个流,因为有两个位置要打水印,如果不分成两个流,那么第一个overlay用完这个[logo-out]之后,它就没了,可是第二个overlay还要用这个经过scale2ref处理的logo呢,当然你也可以用scale2ref把源图片再处理一次,但这就是重复动作了,属于浪费资源,另外,这句split还可以写成[logo-out]split=2[logo1][logo2](split可以随意分割成多个流,不一定是两个)。split是分视频流,如果要分音频流,要用asplit(a是audio),因为图片属于静止视频,图片里是视频轨,所以图片也用split,这是split官方文档:16.31 split, asplit

scale2ref纯粹就是前面的知识,用于根据视频的宽高来确定水印图片的宽高。另外添加两个水印的方法前面也有,这个其实就是前面的综合应用。


透明水印:方法一样,只要你的图片是png图片有透明部分,那么用前面的方法添加上去,图片透明的部分自然就是透明的。


gif图水印:方法一样,只要你的图片gif动图,那么用前面的方法添加上去gif图也是会动的,只不过gif图会在视频播放停止后停止,因为gif图是无限循环的,如果让它一直播放,会导致视频无限大,所以只能在视频播放停止的时候让它也停止。


画中画:画中画其实就是把前面所有的图片换成视频就行,同样的操作。只不过,由于视频是有时间的,如果覆盖上去的视频比主视频时间长,那么主视频会停留在最后一帧,画中画视频会继续播放。

删除水印(去水印)

删除水印实例代码

ffmpeg -i /path/to/input.mp4 -vf delogo=988:18:269:108:1 -c:a copy /path/to/output.mp4
  • -vf 一路看下来的应该不用解释了吧,vf就是video filter的意思,表示指定一个视频过滤器,而现在指定的视频过滤器是delogo
  • delogo 是一个用于删除水印的视频过滤器,delogo其实就是delete logo的缩写,delete一般可写成del,所以就变成了del logo,只不过logo也是l开头,所以两个l合成一个,就变成了delogo;
  • delogo参数为delogo=x:y:w:h:show,(x,y)是要删除的logo的左上角那个点的坐标,w和h是要删除的logo的宽和高,show可以是1和0,1的话,被删除的logo位置会加上一个绿色的框,0的话就什么都不加,默认是0;有一种说法是show前面还有个t,用于表示添加的绿色框的边框宽度,但我实测报错,也许那是以前的用法,后来去掉了吧;
  • 注意,被截取的logo左上角坐标x和y任何一个都不能为0,否则会报错“Logo area is outside of the frame.”,原因是删除logo的原理是根据周围像素计算logo里面应该填充什么像素,如果你有一个坐标为0,那就说明它的左边或上边没有像素,没有像素就导致它无法计算填充像素;同理,就算你的坐标都不是0,但x坐标加上logo宽度刚好碰到右边的边,或者y坐标加上logo高度刚好碰到下边的边,它也会报错,根本原因就是因为删除logo需要根据四周的像素来计算填充,有一条边没有像素,它都无法计算。
  • 如果你的logo确实是在最边边上,那你只能先把视频用pad填充一下,删除logo后,再截取回原尺寸,不过由于填充的只是纯色,这会导致计算填充像素不准确,比如你填充黑色像素或白色像素,就会导致你的logo删除后,会拉丝,拉黑色的或白色的比,但这也是没办法的。
  • 由于无法直接在视频中去定位logo的起始坐标及宽高,我的做法是用Movist(macOS中的一个视频播放器)打开视频,然后用截图软件直接捕获窗口截图(注意不能自己框选,否则会不准确),然后用“预览.app”(macOS自带)打开图片,然后在用框选工具来测试出logo左上角坐标及宽高,但是这个还不能直接用,因为截图的宽高与视频肯定不同,这时你要转换一下(等比例转换)。

删除logo后的视频

删除指定时间段水印

前面删除水印的命令是这样的

ffmpeg -i /path/to/input.mp4 -vf delogo=988:18:269:108:1 -c:a copy /path/to/output.mp4

其实前面这条命令的delogo只是简写,我们可以指定参数名,变成

ffmpeg -i /path/to/input.mp4 -vf delogo=x=988:y=18:w=269:h=108:show=1 -c:a copy /path/to/output.mp4

我们看delogo的文档,貌似只有x,y,w,h,show这5个参数,但实际上,我们还可以用第6个参数,那就是enable,这个参数并没有写出来,也许是所有filter(过滤器)都具有的一个通用选项吧,但它确实可以用。

使用enable来控制删除指定时间段

ffmpeg /path/to/input.mp4 -vf "delogo=x=988:y=18:w=269:h=108:show=1:enable='between(t,2,4)'" -b:v 1000k /path/to/output.mp4

between(t,2,4)表示t(播放时间)在2-4秒之间,也就是2<t<4时,会返回1,否则返回0,而enable=1表示delogo被启用,enable=0表示禁用,这就可以在指定时间启用删除logo,其它时间都不启用,不启用就是不删除。注意between里的时间单位都是秒,如果是1分钟18秒,你可以写成78秒,可以写小数。

当然,你也不一定要用between,也可以用lt(小于), lte(小于等于), gt(大于), gte(大于等于)等等这些函数,用法都与between类似,只不过比between小一个参数,比如lte(t,10)表示在时间大于等于10秒的时候,返回1,时间单位都是秒,可以写小数。


事实上,上面的命令你也可以简写成

ffmpeg /path/to/input.mp4 -vf "delogo=988:18:269:108:1:enable='between(t,2,4)'" -b:v 1000k /path/to/output.mp4

而且一般情况下,我们都是把show设置为0的(也就是去除水印后不添加绿色框),这样我们可以去掉这个参数,写成

ffmpeg /path/to/input.mp4 -vf "delogo=988:18:269:108:enable='between(t,2,4)'" -b:v 1000k /path/to/output.mp4

虽然enable占了show的参数位置,但由于我们用enable=这样的形式,ffmpeg是可以识别出这个参数是enable而不是show。

颜色填充

delogo删除水印时,如果遇到水印刚好在边角上,也就是水印左上角坐标有任意一个为0的情况,会报错,这时我们就要先给它填充一点像素,让水印不是在边上,然后就可以删除logo了,删除logo后再裁剪掉填充的像素(裁剪回原尺寸),不需要先导出视频再裁剪,可以直接链式操作。

pad使用实例

ffmpeg -i /path/to/input.mp4 -vf pad=1940:1100:10:10:#FDDF88 -c:a copy /path/to/output.mp4
  • 我的视频尺寸是:1400×1076;
  • pad的语法是pad=w:h:x:y,w、h为填充后的宽高,x、y为原视频左上角在填充后的视频里的坐标;
  • 1940:1100:10:10:#FDDF88,表示填充后的视频宽高为1940×1100,原视频左上角坐标位于填充后的视频的(10,10)处,填充的颜色为#FDDF88,也可以写英文,比如:red、yellow、blue等等,不写颜色默认为黑色。
  • 其实1940×1100就是1920×1080各加了20像素,并且让原视频左上角坐标处于填充后视频的(10,10)处,能想像出来是什么样子吗?因为宽高各加了20,而现在原视频左上角坐标是(10,10),说明下边和右边各自还有10像素,也就相当于把视频加了一个10像素的边。

pad文档:11.168 pad


我的一个视频有两个顶到左边边上的logo,现在我要把这两个logo删掉,按道理应该这样写

ffmpeg -i /path/to/input.mp4 -filter_complex "delogo=0:40:70:100, delogo=0:180:300:200" -b:v 5000k -c:a copy /path/to/output.mp4

但实际上,上边命令会报错

[delogo @ 0x7fa7e39049c0] Logo area is outside of the frame.
[Parsed_delogo_0 @ 0x7fa7e39048c0] Failed to configure input pad on Parsed_delogo_0
Error reinitializing filters!
Failed to inject frame into filter network: Invalid argument
Error while processing the decoded data for stream #0:0
Conversion failed!

报错的原因,就是因为两个delogo指定的x坐标都是0,我前面说了,删除logo本质上是需要把删除掉的区域填充回来的,而填充需要根据它四周的像素来计算应该填充什么,只要有一边缺少像素,它就无法计算,无法计算就会报错。

我们需要做的就是利用pad的功能,将它的左边填充一点像素,让x不为0,由于我的视频是1390×1076的,所以我在x方向填充10个像素,让它变成1400×1076,而原视频相对填充后的视频,其实就是x轴为10,y还是不变

# 原命令:会报错
ffmpeg -i /path/to/input.mp4 -filter_complex "delogo=0:40:70:100, delogo=0:180:300:200" -b:v 5000k -c:a copy /path/to/output.mp4

# 添加pad的命令:填充白色
ffmpeg -i /path/to/input.mp4 -filter_complex "pad=1400:1076:10:0:#FFFFFF ,delogo=10:40:70:100, delogo=10:180:300:200, crop=1390:1076:10:0" -b:v 5000k -c:a copy /path/to/output.mp4

:由于pad、delogo、crop都是视频过滤器,所以-filter_complex也可以换成-vf-filter:v

可以看到,logo确实能去掉但由于左侧填充的是纯白色,所以计算的填充像素,也是白色从左侧往右边融合,这样不太好看

把填充颜色改成取自logo周围环境的颜色

# 添加pad的命令:填充颜色取自logo周围
ffmpeg -i /path/to/input.mp4 -filter_complex "pad=1400:1076:10:0:#597886 ,delogo=10:40:70:100, delogo=10:180:300:200, crop=1390:1076:10:0" -b:v 5000k -c:a copy /path/to/output.mp4

可以看到,去除效果好多了
16342996274962.jpg

并排合并视频

一般认为的合并视频,是把一个视频放在另一个视频的前面或后面,而我说的合并视频,是“并排合并”,可以左右并排或者上下并排,这样两个视频可以一起播放,这个需求来源于拍摄钢琴手型。

如下图,学习钢琴时,老师要看你的手型,需要从侧边角度和俯视角度两个角度拍摄
image.jpg
当然声音我是用的其中一个视频的声音,另一个视频只要调整成跟它同步就行(这个得用视频编辑软件来调,比如电脑版剪映)。

并排合并视频的原理是使用前面颜色填充以及添加水印和画中画里用到的视频过滤器,分别是padoverlay。基本原理是,把其中一个视频用pad填充扩大,扩大的空白区域刚好用来放另一个视频,然后用overlay把另一个视频放到这个扩大的空白区域里。

首先我们要知道两个视频的分辨率,即宽x高,我实验的两个视频分辨率都是:720×1280(前面是宽,后面是高,所以可以判断这是竖屏视频),既然都是竖屏视频,那最正常的是左右并排(因为竖屏你还上下并排,将会导致视频的高非常大,结果就是视频是一个竖着的长条形,你当然也可以这样竖着合并,只是这样合并的实际需求并不大),如果都是横屏视频,采用上下并排的方式会好点。

确定了左右并排后,两个720并排在一起就是1440,当然如果你中间想要有个间距也行,现在我们暂时不用间距,那就是1440,由于是左右并排,所以高是不变的,所以合并后的视频分辨率(宽x高)为1440×1280,所以首先,我要把第一个视频用pad扩大到1440×1280。

还记得pad怎么用吗?不记得可以看看颜色填充,所以pad参数应该是pad=1440:1280:0:0(最后一个颜色参数可以不要),有了pad参数,我们再看看pad之后我们要用的overlay参数,如果不记得,可以看看前面说过的添加水印和画中画,刚刚pad第一个视频的时候,是把视频放在左侧的,所以第二步就是把第二个视频用overlay的方式放到右侧。

最终代码如下

ffmpeg -i 哈农1左手左视.mp4 -i 哈农1左手俯视.mp4 -filter_complex "[0:v]pad=1440:1280:0:0[paded]; [paded][1:v]overlay=720:0[overlayed];[1:a]volume=13dB[volumeup]" -map '[overlayed]' -map '[volumeup]' -b:v 4000k 哈农1左手左视俯视_merged.mp4
  • 两个-i分别输入两个要合并的视频,没有路径表示你要先在终端里进入到视频所在目录,再运行上边的命令,否则就要带上路径,比如~/Downloads/哈农1左手左视.mp4~/Downloads/哈农1左手俯视.mp4
  • -filter_complex,complex是组合、合成的意思,filter_complex其实就是组合滤镜,-filter_complex后面的双引号里就是一个接一个的滤镜,里面可以同时使用多种滤镜,比如视频滤镜,音频滤镜等等,,滤镜之间用分号(;)隔开,本例中使用了padoverlay视频滤镜以及volume音频滤镜;
  • 滤镜结尾用[xxx]表示对滤镜的输出结果做标记,以方便后面引用,标记的名称是你自己起的,起什么名字都可以,只要后面引用的时候,用同一个名字即可,本例中[paded]就是对pad的输出做标记,[overlayed]就是对overlay滤镜的输出做标记,[volumeup]就是对volume滤镜的输出作标记;
  • -map xxx表示指定轨道,如果-map要引用标记轨道,则必须用单引号括住,比如-map '[overlayed]'就是指引用overlay的输出结果[overlayed]
  • 如果有多个map指定多个轨道,那么它些轨道最终都会合并到输出视频中,并且map出现的顺序就是轨道在输出视频中的顺序,本例中-map '[overlayed]' -map '[volumeup]'第一个是视频轨,第二个是音频轨,那么输出文件中第一条轨道(编号#0)就是视频轨,第二条轨道(编号#1)就是音频轨(虽然大多数情况下,视频轨都是在音频轨之前,但音频轨也是可以在视频轨前面的,这个对播放没有影响);
  • -b:v 4000k指定输出视频的比特率(bitrate)为4000kbps;
  • 哈农1左手左视俯视_merged3.mp4最终的输出文件,可以带上路径,比如~/Downloads/哈农1左手左视俯视_merged.mp4(macOS中,在win中自然就是D:\E:\这样的开头),没有路径表示输出到运行命令时,终端上所在的目录里(用pwd命令可以查看你的终端当前在哪个目录);
  • padoverlay的具体用法在颜色填充添加水印和画中画中已经讲过,如果忘了可以回去看看。

左右并排命令优化:其实,我们也可以不在命令里指定具体的宽高,而是用变量来获取

ffmpeg -i 哈农1左手左视.mp4 -i 哈农1左手俯视.mp4 -filter_complex "[0:v]pad=iw*2:ih:0:0[paded]; [paded][1:v]overlay=overlay_w:0[overlayed];[1:a]volume=13dB[volumeup]" -map '[overlayed]' -map '[volumeup]' -b:v 4000k 哈农1左手左视俯视_merged.mp4
  • pad里可用iw和ih表示pad前面那个参数([0:v])的宽(iw:input width)和高(ih:input height);
  • 在overlay中不能用iwih,而是用main_wmain_h分别表示[主视频]的宽和高,用overlay_woverlay_h分别表示[画中画视频]的宽和高(overlay的语法是:[主视频][画中画视频]overlay=画中画x坐标:画中画y坐标[输出标记]);
  • 本例画中画坐标我用的是overlay_w,因为它与主视频的宽度是一样的,那为什么我不用main_w呢?因为main_w[主视频]宽度,而此时[主视频]已经是[paded],也就是被填充后的,如果我用main_w那就得写成main_w/2否则它就会在视频有效区外边,造成真正要覆盖上去的位置没有覆盖到(没覆盖到就显示pad的颜色,由于我pad的时候没指定颜色,那默认就是默认);

上下并排:把pad的宽改为原视频宽度,高度改为原视频高度的两倍,把overlay的x坐标改为0,y坐标改为视频高度就可以

ffmpeg -i 哈农1左手左视.mp4 -i 哈农1左手俯视.mp4 -filter_complex "[0:v]pad=iw:ih*2:0:0[paded]; [paded][1:v]overlay=0:overlay_h[overlayed];[1:a]volume=13dB[volumeup]" -map '[overlayed]' -map '[volumeup]' -b:v 4000k 哈农1左手左视俯视_merged.mp4

两个长的视频上下并排就变成很窄的一个竖屏视频,实际上我们一般不会这么做,这里只是演示可以上下并排
image.jpg

裁剪填充去水印联合应用

ffmpeg -i /path/to/input.mp4 -vf "crop=1400:1076:600:4, pad=1410:1076:10:0:#FDDF88, delogo=10:40:70:100, delogo=10:180:300:200, crop=1400:1076:10:0" -b:v 5000k -c:a copy /path/to/merged-side-by-side.mp4
  • -vf是video filter的缩写,用于指定视频过滤器;
  • 引号内的全部是视频过滤器,用逗号分开,我们可以看到用了5次过滤器,分别是:crop、pad、delogo、delogo、crop;
  • 整体意思是:先用crop裁剪视频,把视频的黑边去掉,然后由于裁剪后logo挨着最左边,所以用pad给它的左边填充10个像素(注意填充的颜色是要删除的logo周围的颜色,这也是为什么要先裁剪掉黑边再填充的原因),然后分别删除两个logo,最后再用crop把pad填充的像素裁剪掉,变回原视频尺寸。
  • 注意crop和delogo的参数方向是反过来的,crop是w:h:x:y,而delogo是x:y:w:h,注意不要搞反了;
  • 注意,因为顺序是先crop,再pad,再delogo,所以删除logo的坐标以及宽高的计算,应该按裁剪并填充之后的视频宽高来算,不能按原始视频算坐标,否则肯定不对;最好是先用crop=720:1003:0:289, pad=740:1023:0:20:#EFE8EA这两个截出几秒视频,然后再用:删除水印(去水印)中的方法来确定水印坐标及宽高;
  • -b:v 5000k指定视频码率为5000k;
  • -c:a copy表示不改变音频编码,直接复制原来的音频编码,因为我们处理的是视频部分,跟音频无关;

给图片删除水印

我说过,图片其实就是一种静止的视频,它的格式是mjpeg,所以,delogo同样可以给图片去除水印

ffmpeg -i /path/to/input.jpg -filter_complex "[0:0]split=2[img1][img2],[img1]crop=2400:124:0:0[crop1],[img2]crop=2400:956:0:124[crop2],[crop1]pad=2400:1090:0:10:#AEB3B9[pad1],[pad1]delogo=10:10:1726:134[delogo1],[delogo1][crop2]overlay=0:134[overlay],[overlay]crop=1190:1076:602:14" -q:v 2 -huffman optimal /path/to/output.jpg

以上命令是给一张图片去除水印,由于要去除的水印距离我们需要的图像太近,如果直接去水印,我们需要的图像的颜色会渗入到水印中,所以我打算先把logo区域切开来(裁剪开)。

先用split把输入流分为[img1][img2](相当于把输入流复制出一份,一共两份),然后对[img1]进行裁剪,只截取图片上部(logo就在这里),对[img2]进行裁剪,切它的下部,这样就相当于把一张图片切成crop1(上部)+crop2(下部)。

然后,由于logo在crop1(上部),而且logo距离边太近,所以给它填充了logo附近颜色,并且填充的比较大,因为到时我要把下半部crop2合并进来,而合并,其实就是把crop1当主体,把crop2当成一个logo(只不过这个logo很大),用overlay来合并进来。

但目前发现的问题是,这样删除水印生成的图片质量不高(后面发现加个qscale 1可以变高),如果有谁知道怎么生成高质量图片,可以评论告诉我!

从视频中提取图片

每秒钟视频导出30导图片

ffmpegnb input.mp4 -r 30 image-%3d.jpg
  • -r n(n为正整数)用于指定帧率(r是rate的意思),表示1秒钟的视频你想导出多少导图片,-r 30就表示一秒钟导出30张图片,你可以把-r设置为比视频帧率大,也可以比视频帧率小,但是比视频帧率大就没什么意义了,比视频帧率小可能会错过一些好的图,具体值自己把握;
  • 导出的图片文件肯定要有名称,%d很多人应该都知道,表示1位整数,d是decimal(整数)的意思,%3d其实是表示导出的图片有3位数字,我们知道,三位数字最小是100,1-99都只有两位数,它会在前面补0补到3位长度。所以如果你生成的图片会超过999,你就要用%4d了;

其实提取图片可以在其它任何操作之后进行,

ffmpegnb -ss 22 -t 2 -i input.mp4 -vf "crop=1085:1728:177:188" -b:v 4000k -c:a copy -r 30 image-%3d.jpg

修剪+裁剪+音量+比特率+音画同步

假设现在有一个视频,需要掐头去尾,并且需要裁剪视频画面中的一部分,还需要加大音量,调整比特率,最后还要做音画同步。

这里除了音画同步无法同时做,其它的都可以同时做,也就是说这可以分成两个命令来操作,一个命令做修剪+裁剪+增大音量+调整比特率,另一个命令做音画同步。

修剪+裁剪+增大音量+调整视频比特率

ffmpeg -hide_banner -ss 00:00:15.000 -t 00:03:29.000 -i "/path/to/测试视频.mp4" -vf crop=1912:1076:244:4 -af volume=22dB -b:v 5000k "/path/to/测试视频_crop.mp4"
  • -hide_banner 不输出一大堆头部信息;
  • -ss 00:00:15.000 -t 00:03:29.000 掐头去尾,表示从15秒开始裁剪到第03分44秒,只不过这里-t是指定截取长度,所以需要自己用3分44减去15秒得出这个长度(-t可换成-to表示指定截取到哪个时刻);
  • -i "/path/to/测试视频.mp4" 指定输入文件;
  • -vf crop=1912:1076:244:4 裁剪视频,裁剪出的视频宽高为1912×1076,裁剪起始点(即视频左上角)坐标为(244,4);
  • -af volume=22dB 表示在原音量的基础上加大22分贝,一般来说,-28分贝的音频,它的音量我感觉挺合适;
  • -b:v 5000k 指定视频比特率为5000kbps;
  • "/path/to/测试视频_crop.mp4" 指定输出文件路径和文件夹名;
  • 视频文件的输入和输出路径,可以加双引号,也可以不加,但是如果遇到有空格的路径,不加双引号就会报错找不到路径,除非你把每个空格都用反斜杠\转义,但这样还不如用双引号方便;

音画同步

ffmpeg -hide_banner -i "/path/to/测试视频_crop.mp4" -itsoffset 00:00:00.700 -i "/path/to/测试视频_crop.mp4" -map 0:v -map 1:a -vcodec copy -acodec copy "/path/to/测试视频_crop_avsync.mp4"
  • -itsoffset 00:00:00.700 放在-i前面,表示这个-i输入的文件延迟700毫秒;
  • -map 0:v -map 1:a抽取第1个输入文件的视频轨和第二个输入文件的音频轨;
  • -vcodec copy -acodec copy 分别指定视频和音频编码,copy是指不转换编码,直接复制原来的编码;

我写了一个自动生成命令的脚本

<?php
/**
 * 把给定纯文本内容复制到系统剪贴板,兼容Mac/Win/Linux(只能普通文本内容,不支持富文本及图片甚至文件)
 * @param $content
 *
 * @return string|null
 */
function copyPlainTextToClipboard($content){
    //Mac和Win都自带,Linux(桌面系统)一般需要需要自己安装xclip(如Ubuntu: apt install xclip)
    $clipboard = PHP_OS=='Darwin' ? 'pbcopy' : (PHP_OS=='WINNT' ? 'clip' : 'xclip -selection clipboard');
    //echo不是php命令,而是shell命令,win/mac/linux都有echo这个命令的
    //在Mint(基于Ubuntu)系统中,发现在终端执行命令的方式调用上传,如果有使用shell_exec()则会导致终端不退出
    //但在Mac和Win就没这个问题
    $command = "echo '{$content}' | {$clipboard}";
    return shell_exec($command);
}

if(!isset($argv[1]) || !$argv[1]){
    exit("\n请输入视频文件路径!\n\n");
}
$videoPath = $argv[1];

//获致视频比特率
$cmd = '/usr/local/bin/ffprobe -hide_banner -i "'.$videoPath.'" 2>&1';
// echo "\n".$cmd."\n\n";exit;
$returnStr = shell_exec($cmd);
$arr = explode("\n",$returnStr);

$arr2 = [];
foreach($arr as $val){
    if(strpos(ltrim($val),'Video') !== false){
        $arr2 = explode(',', $val);
        break;
    }
}

$arr3 = [];
foreach($arr2 as $val){
    if(strpos(ltrim($val),'kb/s') !== false){
        $bitrateWithUnit = ltrim($val);
        break;
    }
}

$arr3 = explode(' ',$bitrateWithUnit);
$bitRate = (int)$arr3[0];
$bitRateOrigin = $bitRate;
if($bitRate/3<5000){
    if($bitRate<3000){
        $bitRate = $bitRate.'k';
    }else{
        if($bitRate/3<3000){
            $bitRate = '3000k';
        }else{
            $bitRate = ceil($bitRate/3).'k';
        }
    }
}else{
    $bitRate = '5000k';
}

//
$cmd = '/usr/local/bin/ffmpeg -hide_banner -i "'.$videoPath.'" -af volumedetect -f null /dev/null 2>&1';
// echo $cmd."\n";exit;
$returnStr = shell_exec($cmd);
$matches = [];
preg_match('/mean_volume\: (-\d{2}\.\d)/', $returnStr, $matches);
$meanVolume = round($matches[1]);
$volumeUpAmount = 0;
if($meanVolume < -28){
    $volumeUpAmount = -28 - $meanVolume;
}
// var_dump($meanVolume, $volumeUpAmount);

$videoPathLen = strlen($videoPath);
$videoPathWithoutExt = substr($videoPath, 0, $videoPathLen-4);
// $ext = substr($videoPath, $videoPathLen-4);

$content = '';
$volumeUp = $volumeUpAmount ? '-af volume='.$volumeUpAmount.'dB' : '';

//截取+裁剪+增大音量+修改比特率
$cmd5 = '/usr/local/bin/ffmpeg -hide_banner -ss 00:00:15.000 -t 00:03:29.000 -i "'.$videoPath.'" -vf crop=1912:1076:244:4 '.$volumeUp.' -b:v '.$bitRate.' "'.$videoPathWithoutExt.'_crop-volume-up.mp4"';$content .= "\n截取+裁剪+增大音量+修改比特率(原始视频比特率:{$bitRateOrigin} kb/s,原始平均音量:{$meanVolume}dB):\n{$cmd5}\n\n";

//先听到声音再看到口型(声音快了),把音频延迟700毫秒
$cmd7 = '/usr/local/bin/ffmpeg -hide_banner -i "'.$videoPathWithoutExt.'_crop-volume-up.mp4" -itsoffset 00:00:00.700 -i "'.$videoPathWithoutExt.'_crop-volume-up.mp4" -map 0:v -map 1:a -vcodec copy -acodec copy "'.$videoPathWithoutExt.'_crop-volume-up_avsync.mp4"';$content .= "\n把声音延迟700毫秒:\n".$cmd7."\n\n";

//先看到口型再听到声音(画面快了),把画面延迟700毫秒
$cmd8 = '/usr/local/bin/ffmpeg -hide_banner -itsoffset 00:00:00.700 -i "'.$videoPathWithoutExt.'_crop-volume-up.mp4" -i "'.$videoPathWithoutExt.'_crop-volume-up.mp4" -map 0:v -map 1:a -vcodec copy -acodec copy "'.$videoPathWithoutExt.'_crop-volume-up_avsync.mp4"';$content .= "\n把画面延迟700毫秒:\n".$cmd8."\n\n";

copyPlainTextToClipboard($content);
echo "\n命令已复制到剪贴板,请到编辑器中粘贴查看\n";

脚本使用方法

  • 1、新建一个php文件,比如ffmpegphp.php,然后把以上代码复制到文件内,保存;
  • 2、在终端中执行:php /path/to/ffmpegphp.php /path/to/测试视频.mp4,提示命令已复制到剪贴板后,然后你直接去编辑器里cmd+v就可以粘贴出脚本生成的命令;
  • 3、适当调整脚本生成的命令中的参数,即可复制命令去终端里执行,先执行第一条裁剪相关的命令,然后再复制音画步命令去执行就可以;
  • 3、更方便的方法:在~/.zshrc中添加一行(但这样就不能随意移动ffmpegphp.php文件的位置)
alias ffmpegphp="php /path/to/ffmpegphp.php"

重启终端,以后就可以直接用:ffmpegphp /path/to/测试视频.mp4的方式来生成命令了。

相关解释

  • 脚本以“-28”分贝为目标音量,如果目标音量低于-28分贝,则会计算需要增大多少分贝,并自动放置在命令中,如果大于等于-28分贝,则命令中不会有调整音量的选项;
  • 脚本会自动获取原视频比特率,并根据大小,把它的比特率除以3,或者除以2,如果你感觉不合适,可以根据原始比特率自己修改成合适的,因为我也输出了原始比特率;
  • 脚本会根据裁剪操作的输出文件名,自动作为音画同步命令的输入文件名,这样裁剪完成后,你直接复制音画同步命令到终端执行即可(当然你可能需要修改一下音频或视频延迟时间,毕竟每个视频需要的延迟时间不同);
  • 无论是裁剪还是音画同步,最终输出的文件路径,都是在原始视频所在的文件夹里,这样方便你查找;
  • 为什么不直接输出命令而是要粘贴到剪贴板?因为直接输出在终端,一个是不方便查看,另一个是你在终端执行一下第一条命令,第二条就找不到了,还不如先粘贴出来,还有就是你可能需要先修改一下参数再执行,而直接在终端中不方便修改参数;
  • 如果你经常有相同类型的视频需要处理,比如你要截取的大小和截取起始位置都相同,并且音频延迟时间也相同,则可以修改脚本中的参数,这样它每次输出的命令就直接是你想要的参数,你直接粘贴执行即可,无需再修改;
  • 执行两个命令后,最后得到的音画同步的文件名是以_crop-volume-up_avsync.mp4结尾的,然后你就可以删除原视频以及裁剪时生成的文件名以_crop-volume-up.mp4结尾的视频;
ffmpegphp /Users/bruce/Downloads/Screenrecorder-2021-09-30-09-57-46-430.mp4

以下是我执行上述的ffmpegphp命令后,在编辑器粘贴得到的命令

截取+裁剪+增大音量+修改比特率(原始视频比特率:16076 kb/s,原始平均音量:-50dB):
/usr/local/bin/ffmpeg -hide_banner -ss 00:00:15.000 -t 00:03:29.000 -i "/Users/bruce/Downloads/Screenrecorder-2021-09-30-09-57-46-430.mp4" -vf crop=1912:1076:244:4 -af volume=22dB -b:v 5000k "/Users/bruce/Downloads/Screenrecorder-2021-09-30-09-57-46-430_crop-volume-up.mp4"


把声音延迟700毫秒:
/usr/local/bin/ffmpeg -hide_banner -i "/Users/bruce/Downloads/Screenrecorder-2021-09-30-09-57-46-430_crop-volume-up.mp4" -itsoffset 00:00:00.700 -i "/Users/bruce/Downloads/Screenrecorder-2021-09-30-09-57-46-430_crop-volume-up.mp4" -map 0:v -map 1:a -vcodec copy -acodec copy "/Users/bruce/Downloads/Screenrecorder-2021-09-30-09-57-46-430_crop-volume-up_avsync.mp4"


把画面延迟700毫秒:
/usr/local/bin/ffmpeg -hide_banner -itsoffset 00:00:00.700 -i "/Users/bruce/Downloads/Screenrecorder-2021-09-30-09-57-46-430_crop-volume-up.mp4" -i "/Users/bruce/Downloads/Screenrecorder-2021-09-30-09-57-46-430_crop-volume-up.mp4" -map 0:v -map 1:a -vcodec copy -acodec copy "/Users/bruce/Downloads/Screenrecorder-2021-09-30-09-57-46-430_crop-volume-up_avsync.mp4"

拼接视频

最近小侄子老要看挖土机,电视上也不好找这种片,所以就下载了一些,另外由于电视上的动画片很多都要花钱,而且有些老的片,一些英文片也不好找,所以我还下载了很多其它动画片,但由于家里的电视盒子不会自动播放下一个视频,所以我就把所有集拼接成一个视频,反正每集也不长。

合并视频官方文档:Concatenating media files

方式一:创建合并列表文件方式

ffmpeg -f concat -safe 0 -i mylist.txt -c copy /path/to/output.mp4

-safe 0我也不知道什么意思,在帮助文档里没查到。

mylist.txt里的内容如下

# 这是注释,每一行是一个文件,可带路径
file '/path/to/1.mp4'
file '/path/to/2.mp4'

出现问题:我试过这样合并出一个文件,能合并但有一部分丢了声音,不知道为什么,不过官方文档也有写这种方法:Concatenating media files,但是用下面的方式二就没问题。


方式二:先转换成.ts文件再合并。

把多个mp4视频合并成一个

# 先进入要合并的视频所在目录,然后把所有视频按顺序,转成1.ts、2.ts、…、n.ts
# 如果是h265,需要把-vbsf的值改为:hevc_mp4toannexb
ffmpeg -i 挖掘机麦克斯第1集.mp4 -c copy -vbsf h264_mp4toannexb 1.ts
ffmpeg -i 挖掘机麦克斯第2集.mp4 -c copy -vbsf h264_mp4toannexb 2.ts
ffmpeg -i 挖掘机麦克斯第3集.mp4 -c copy -vbsf h264_mp4toannexb 3.ts

# 再合成ts文件
ffmpeg -i "concat:1.ts|2.ts|3.ts" -c copy -absf aac_adtstoasc 挖掘机麦克斯1-3集合并.mp4

其中-vbsf video bit stream filter,已弃用,改为-bsf:v(v表示video),-absf是audio bit stream filter的缩写,已弃用,改为-bsf:a(a表示audio)。


批量生成转换合并命令

<?php
    /**
     * Created by PhpStorm.
     * User: Bruce Xie
     * Date: 2021-02-09
     * Time: 14:38
     */

    $mergedName = '小猪佩奇第一季52集合并';

    $concatArr = [];
    $cmdArr = [];
    for($i = 1; $i <= 52; $i++){
        $num = $i<10 ? '0'.$i : $i;
        $cmdArr[] = 'ffmpeg -i '.$num.'.小猪佩奇S01E'.$num.'.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb '.$num.'.ts';
        $concatArr[] = $num . '.ts';
    }
    $cmdStr = join(" &&n",$cmdArr);
    $concatStr = join('|',$concatArr);
    $mergeCmd = 'ffmpeg -i "concat:'.$concatStr.'" -acodec copy -vcodec copy -absf aac_adtstoasc '.$mergedName.'.mp4';

    $contentToCopy = $cmdStr . " &&n" . $mergeCmd;

    $clipboard = PHP_OS=='Darwin' ? 'pbcopy' : (PHP_OS=='WINNT' ? 'clip' : 'xclip -selection clipboard');
    $command = "echo '{$contentToCopy}' | {$clipboard}";
    shell_exec($command);
    echo 'Done! 请直接到终端中粘贴并回车运行!';

输出结果如下

ffmpeg -i 01.小猪佩奇S01E01.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 01.ts &&
ffmpeg -i 02.小猪佩奇S01E02.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 02.ts &&
ffmpeg -i 03.小猪佩奇S01E03.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 03.ts &&
ffmpeg -i 04.小猪佩奇S01E04.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 04.ts &&
ffmpeg -i 05.小猪佩奇S01E05.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 05.ts &&
ffmpeg -i 06.小猪佩奇S01E06.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 06.ts &&
ffmpeg -i 07.小猪佩奇S01E07.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 07.ts &&
ffmpeg -i 08.小猪佩奇S01E08.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 08.ts &&
ffmpeg -i 09.小猪佩奇S01E09.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 09.ts &&
ffmpeg -i 10.小猪佩奇S01E10.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 10.ts &&
ffmpeg -i 11.小猪佩奇S01E11.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 11.ts &&
ffmpeg -i 12.小猪佩奇S01E12.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 12.ts &&
ffmpeg -i 13.小猪佩奇S01E13.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 13.ts &&
ffmpeg -i 14.小猪佩奇S01E14.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 14.ts &&
ffmpeg -i 15.小猪佩奇S01E15.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 15.ts &&
ffmpeg -i 16.小猪佩奇S01E16.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 16.ts &&
ffmpeg -i 17.小猪佩奇S01E17.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 17.ts &&
ffmpeg -i 18.小猪佩奇S01E18.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 18.ts &&
ffmpeg -i 19.小猪佩奇S01E19.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 19.ts &&
ffmpeg -i 20.小猪佩奇S01E20.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 20.ts &&
ffmpeg -i 21.小猪佩奇S01E21.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 21.ts &&
ffmpeg -i 22.小猪佩奇S01E22.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 22.ts &&
ffmpeg -i 23.小猪佩奇S01E23.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 23.ts &&
ffmpeg -i 24.小猪佩奇S01E24.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 24.ts &&
ffmpeg -i 25.小猪佩奇S01E25.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 25.ts &&
ffmpeg -i 26.小猪佩奇S01E26.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 26.ts &&
ffmpeg -i 27.小猪佩奇S01E27.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 27.ts &&
ffmpeg -i 28.小猪佩奇S01E28.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 28.ts &&
ffmpeg -i 29.小猪佩奇S01E29.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 29.ts &&
ffmpeg -i 30.小猪佩奇S01E30.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 30.ts &&
ffmpeg -i 31.小猪佩奇S01E31.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 31.ts &&
ffmpeg -i 32.小猪佩奇S01E32.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 32.ts &&
ffmpeg -i 33.小猪佩奇S01E33.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 33.ts &&
ffmpeg -i 34.小猪佩奇S01E34.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 34.ts &&
ffmpeg -i 35.小猪佩奇S01E35.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 35.ts &&
ffmpeg -i 36.小猪佩奇S01E36.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 36.ts &&
ffmpeg -i 37.小猪佩奇S01E37.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 37.ts &&
ffmpeg -i 38.小猪佩奇S01E38.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 38.ts &&
ffmpeg -i 39.小猪佩奇S01E39.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 39.ts &&
ffmpeg -i 40.小猪佩奇S01E40.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 40.ts &&
ffmpeg -i 41.小猪佩奇S01E41.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 41.ts &&
ffmpeg -i 42.小猪佩奇S01E42.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 42.ts &&
ffmpeg -i 43.小猪佩奇S01E43.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 43.ts &&
ffmpeg -i 44.小猪佩奇S01E44.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 44.ts &&
ffmpeg -i 45.小猪佩奇S01E45.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 45.ts &&
ffmpeg -i 46.小猪佩奇S01E46.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 46.ts &&
ffmpeg -i 47.小猪佩奇S01E47.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 47.ts &&
ffmpeg -i 48.小猪佩奇S01E48.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 48.ts &&
ffmpeg -i 49.小猪佩奇S01E49.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 49.ts &&
ffmpeg -i 50.小猪佩奇S01E50.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 50.ts &&
ffmpeg -i 51.小猪佩奇S01E51.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 51.ts &&
ffmpeg -i 52.小猪佩奇S01E52.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb 52.ts &&
ffmpeg -i "concat:01.ts|02.ts|03.ts|04.ts|05.ts|06.ts|07.ts|08.ts|09.ts|10.ts|11.ts|12.ts|13.ts|14.ts|15.ts|16.ts|17.ts|18.ts|19.ts|20.ts|21.ts|22.ts|23.ts|24.ts|25.ts|26.ts|27.ts|28.ts|29.ts|30.ts|31.ts|32.ts|33.ts|34.ts|35.ts|36.ts|37.ts|38.ts|39.ts|40.ts|41.ts|42.ts|43.ts|44.ts|45.ts|46.ts|47.ts|48.ts|49.ts|50.ts|51.ts|52.ts" -acodec copy -vcodec copy -absf aac_adtstoasc 小猪佩奇第一季52集合并.mp4

分离人声和伴奏

请看:使用spleeter分离人声和伴奏


参考:
ffmpeg原理
ffmpeg中文文档
ffmpeg 音量调整
ffmpeg 获取帮助的命令
ffmpeg命令行map参数的使用
用ffmpeg解决音画不同步问题
ffmpeg stream offset命令(-itsoffset)无法正常工作
滤镜命名规则及使用libavfilter对视频尺寸进行裁切
FFmpeg-overlay
Limiting the output bitrate
Aspect Ratio – Understanding the Information and Using the Filter
利用FFmpeg切割视频(建议看)
利用FFMPEG实现音频视频升降调
音视频加速减速

打赏
订阅评论
提醒
guest

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x

扫码在手机查看
iPhone请用自带相机扫
安卓用UC/QQ浏览器扫

ffmpeg常用命令