FFmpeg创作GIF表情包教程来了!还不快说声多谢乌蝇哥?!

语言: CN / TW / HK

theme: channing-cyan highlight: a11y-light


前言

FFmpeg是许多音视频入门书籍都会推荐学习的一套多媒体框架,其集封装、解封装、编码、解码、播放和滤镜等多项功能于一身,堪称音视频领域的「瑞士军刀」。

今天,我们将不再遵循常规教程的套路,而是将以表情包界名垂青史的名场面、电影《旺角卡门》中的经典片段——「吔屎啦你」为讲解素材,通过GIF表情包创作的场景化教学,来讲解FFmpeg命令行工具的实际运用。

若本教程成功地激起了你对FFmpeg命令行工具的探索兴趣,记得~~说声多谢乌蝇哥~~点赞、收藏、评论三连支持一下。

Hello, GIF!

首先,举一个最简单的例子,即,直接把一个任意格式的视频片段转为GIF图像:

ffmpeg -i as_tears_go_by.mp4 as_tears_go_by.gif

as_tears_go_by.gif

[图片过大可能无法展示,可以直接点开链接查看]https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/963871ab49594cc38f4bf00dd0e8a6ae~tplv-k3u1fbpfcp-watermark.image?

-i 选项用于指定任意数量的输入流,可以是本地文件也可以是网络文件。

而输出流也可以是任意数量的,其指定的方式则相对粗暴得多,只要是命令行中无法解释为「选项」的内容,都会被FFmpeg命令行工具视为输出流

大多数情况下,FFmpeg命令行工具会根据输入输出流的扩展名去自动检测格式,如果需要强制指定输入或输出流的格式,可以使用-f 选项,比如:

ffmpeg -i as_tears_go_by.mp4 -f gif as_tears_go_by.gif

效果都是一样的。

名场面往往就是那几秒

在诞生这些表情包名场面的电影中,你经常会发现有那么一群“合影党”,喜欢把播放进度拖动到表情包画面出现的前后几秒,然后在公屏上打上“合影留念”的弹幕(说的是不是你?),仿佛一开始就是奔着这几秒去看这部电影的。

而我们创作表情包的第一步,自然也是先截取出这些全片中最精彩的片段。

要截取视频中的特定位置与时长的片段,我们可以用以下命令行实现:

ffmpeg -i {input} -ss {position} -t {duration} {output}

其中,

-ss 选项用于定位到指定的视频位置,可以是「HH:MM:SS」这种格式,也可以是「2.3」这种表示第2.3秒的格式。

-t 选项用于表示截取的视频时长,也同样支持以上2种时间格式。

例如,「吔屎啦你」这一名场面发生在上述视频片段的第11秒,持续时间约为2.3秒,那我们就可以这样子编写命令行:

ffmpeg -i as_tears_go_by.mp4 -ss 00:11 -t 2.3 as_tears_go_by-trim.gif

as_tears_go_by-trim.gif

[图片过大可能无法展示,可以直接点开链接查看]https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a1a602c8d219433eaab0cfecaea6755f~tplv-k3u1fbpfcp-watermark.image?

到位了到位了哈!可是有一个问题出现了,尽管经过截取处理,转出的GIF图像文件仍有11.6MB,作为一个表情包来讲,实在是太大了。

gif格式和mp4格式的大小对比.png

而相同时长的MP4视频文件大小却只有664KB,为什么两者能相差这么大呢?这其实跟二者采用的压缩算法有关,后面系列文章中会专门讲到,这里就先不展开说了。

斗图,讲究一个「快」字

你肯定有过这样的经历,本来和暧昧对象在微信上聊得好好的,不知怎的突然就没话题了。与其继续尬聊下去,我们更多会选择开启「斗图」模式,来缓解和过渡这一尴尬的时刻。

斗图除了考验你表情包弹药库的存量之外,表情包连发的“攻速”也很重要。

想象一下,对方的表情包如机关枪般“哒哒哒”密集地发来,而你的表情包却因为GIF格式下的文件过大,到了对方的聊天面板还要转圈下载好一阵,气势上就输了一大截。

因此,压缩表情包文件的大小,很重要。

压缩大法第一式——缩放

ffprobe是FFmpeg提供的多媒体信息查看工具,我们可以先使用ffprobe来查看上一步中截取了名场面,并转换了格式后的GIF图像信息:

ffprobe as_tears_go_by-trim.gif

Input #0, gif, from 'as_tears_go_by-cut.gif': Duration: 00:00:02.32, start: 0.000000, bitrate: 40101 kb/s Stream #0:0: Video: gif, bgra, 1920x1080 [SAR 64:64 DAR 16:9], 25 fps, 25 tbr, 100 tbn

可以看到,由于我们是直接将视频片段转为GIF图像的,文件的尺寸大小高达1920x1080!和一般的静态图片一样,GIF格式的文件大小也是受尺寸大小影响的,尺寸越大相应的文件也就越大,但作为表情包我们往往不需要追求如此高清的效果。

因此,我们压缩工作的第一步,就是缩减GIF文件的尺寸。

可以使用以下命令行来实现:

ffmpeg -i {input} -s {WxH} {output} -s 选项用于指定输出文件的尺寸大小,格式上是「宽度(W)x高度(H)」

例如,我们可以拿到前面的GIF图像文件,将其宽高均缩放至原先的1/6: ffmpeg -i as_tears_go_by-cut.gif -s 320x180 as_tears_go_by-scale.gif

as_tears_go_by-scale.gif

gif格式缩放后的大小.png

可以看到,经过缩放后的文件大小已减少至1.6M,约为原先大小的1/7,效果还是比较明显的。

压缩大法第二式——抽帧

GIF是连续的动态图像,可以视作是一张一张完整的图像按一定速率播放,然后利用人眼的视觉残留效应所形成的效果。每秒钟依次播放的图像数量叫做帧率,单位是fps(frame per second,帧每秒)。

前面我们用ffprobe工具查看后可得知,输出的GIF图像的帧率是25fps,这是由于我们是直接将视频片段转为GIF图像的,而电影中的常见帧率为24/25帧,因此输出的GIF图像也保留了相同的帧率。

帧率越高,GIF图像的动效相对就越流畅,但相应的要储存的图片帧也会更多,可能导致文件大小直线上升。通过减少帧数,牺牲一点连贯性,可以显著优化文件的大小。

可以使用以下命令行来实现:

ffmpeg -i {input} -r {fps} {output}

-r 选项用于指定帧率,支持整数和分数格式。

例如,我们可以将上一步帧率为25fps的GIF图像减少至8fps:

ffmpeg -i as_tears_go_by-scale.gif -r 8 as_tears_go_by-frameextract.gif

帧率减少后的GIF图像效果如下:

as_tears_go_by-frameextract.gif

gif格式抽帧后的大小.png

可以看到,经过抽帧处理后的GIF图像大小得到了进一步的压缩。

压缩大法第三式——裁剪

裁剪同样是为了缩减GIF图像的尺寸,只不过和单纯的缩放相比,裁剪还有去除冗余元素、突出目标主体的附加效果。

借助FFmpeg命令行工具实现对图像/视频的裁剪,我们需要用到Filter(滤镜、过滤器)模块。

滤镜模块提供了许多音视频特效处理的功能,比如crop(裁剪)、scale(缩放)、overlay(叠加)、rotate(旋转),trim(截取)等。可以说,前面几个步骤提到的功能,都有相应的滤镜可以实现。

如果要为视频类型的输入流指定使用的滤镜,需要使用-vf 选项。可指定的滤镜按输入输出流的数量和类型可分为「简单滤镜」和「复杂滤镜」两种。

所谓「简单滤镜」,指的是那种刚好只有一个输入流、一个输出流的的滤镜,且两者都是同一类型的情况:

简单滤镜流程图.png

相对的,「复杂滤镜」指的则是那些具有多个输入流/输出流,或者输出流类型与输入流类型不同的情况:

复杂滤镜流程图.png

比如我们现在要实现的裁剪功能,就是一个典型的「简单滤镜」:

ffmpeg -i as_tears_go_by-frameextract.gif -vf "crop=180:180:100:0" as_tears_go_by-crop.gif

这条命令行的完整释义如下: - 输入一个名为“as_tears_go_by-frameextract”的GIF图像文件 - 创建一个提供裁剪功能的视频滤镜,用于过滤输入流。 - 裁剪区域的宽度为180,高度为180,从以左上角为原点、x轴向右偏移量为100、y轴向下偏移量为0的位置开始裁剪。 - 将裁剪完成的内容输出到一个名为“as_tears_go_by-cut”的GIF图像文件

经过裁剪后输出的GIF图像效果如下:

as_tears_go_by-crop.gif

gif格式裁剪过后的大小.png

更骚的操作

FFmpeg滤镜的强大之处,在于它可以通过不同滤镜的排列组合,实现各种各样复杂的功能

FFmpeg滤镜共包含以下3个层级:

filter -> filterchain -> filtergraph 也即 滤镜 -> 滤镜链 -> 滤镜图

多个滤镜可以串联成一条滤镜链,多条滤镜链可以组合成一个滤镜图

我们可以基于FFmpeg的官方示例进行改造,实现“裁剪出视频的左半部分,并镜像叠加到视频的右半部分”的效果,来演示一下三者的关系,以及如何结合使用,流程图如下:

示例的完整流程图.png

  1. 首先,使用split滤镜将输入流分割为两个流[main]和[tmp] (你可以理解为copy了一份);
  2. 将其中一个流[tmp]先通过crop滤镜裁剪出左半部分;
  3. 将步骤2的输出再经过hflip滤镜进行水平翻转,并输出为[flip];
  4. 把步骤3的输出[flip]叠加到[main]的右半部分。

这个流程使用命令行实现如下:

ffmpeg -i as_tears_go_by-crop.gif -vf "split[main][tmp];[tmp]crop=iw/2:ih:0:0,hflip[flip];[main][flip]overlay=W/2:0" as_tears_go_by-graph.gif

是不是看着这么多的参数有点懵圈了?不要怕,这个涉及到滤镜模块的语法,我们一个一个来解释:

首先,[main][tmp][flip]是为输入输出流所打的标签,可以任意命名,打标签是可选的,为了连接其他滤镜时方便使用。

split、crop、hflip、overlay都是具体使用的滤镜,滤镜的各种参数在=号后面指定。

滤镜的参数之间用冒号:分隔,可以是纯值的形式,也可以是“键=值”的形式,还可以是二者混用的形式。

比如crop滤镜的参数也可以这样指定:crop=w=iw/2:h=ih:x=0:y=0,iw和ih这两个变量分别指的是输入帧的宽度和高度。

同一滤镜链内的不同滤镜之间用逗号,分隔。

比如[tmp]crop=iw/2:ih:0:0,hflip[flip]这一滤镜链中就包含crop和hflip这两个滤镜,两者之间使用逗号,分隔。

同一滤镜图内的不同滤镜链之间用分号;分隔。

比如上面的滤镜图就包含split;crop,flip;overlay三条滤镜链,彼此之间使用用分号;分隔。

最终输出的效果如下——忍法·双头嘲讽:

as_tears_go_by-graph.gif

嗯......看着有点诡异呢!😨

表情包的经典二创

一个表情包之所以经久不衰,除了表情包本身很有“梗”之外,网友们富有想象力的“二次创作”,也是表情包能再次迸发出生命力的原因之一。

比如让人忍俊不禁的「欲吔又止」:

欲吔又止.png

这种类似连环画的表情包叙事感很强,如果采用FFmpeg来创作的话,可以分为以下几步进行:

视频截图

手动截图

手动截图的工作需要先借助前面提到的-ss 选项,定位到视频片段指定的位置,然后再借助-vframe 选项来输出指定数量的视频帧,最后当然还要再对输出的视频帧的进行一波同样的裁剪和缩放。

具体的命令行示例如下:

ffmpeg -i as_tears_go_by.mp4 -ss 00:13 -vframes 1 -vf "crop=ih:ih:iw/4:0" -s 180x180 as_tears_go_by-screenshot.jpg

得到的处理后的视频截图如下:

手动截图.png

定时截图

但如果你想偷一下懒,不想一张张手动去截,我们也可以借助fps滤镜实现来定时截图,然后再从输出的图片集里找符合预期的截图即可,命令行如下:

ffmpeg -i as_tears_go_by.mp4 -vf "fps=2,crop=ih:ih:iw/4:0" -s 180x180 screenshot/out%d.jpg

此命令行执行后,每过0.5秒就会生成一张JPG格式的图片,并进行一波同样的裁剪和缩放。

定时截图.png

从中我们最终挑出以下四张截图作为素材:

定时截图精选1.png

定时截图精选2.png

定时截图精选3.png

定时截图精选4.png

添加文字

添加文字的工作同样可借助滤镜功能完成,使用到的滤镜是「drawtext」滤镜。

我们先拿前面经过裁剪之后生成的GIF图像来做下实验:

ffmpeg -i as_tears_go_by-crop.gif -vf "drawtext=fontsize=30:text='吔屎啦你':fontcolor=white:x=25:y=100:fontfile=JingNanYuanMoTi/KNFONTYUANMO-2.otf" as_tears_go_by-text.gif

添加文字后的效果如下:

as_tears_go_by_text.gif

fontsize、fontcolor、text等从字面意义就可以知晓其作用的参数我就不再赘述了,这里需要提到的是,如果默认的字体风格不符合预期,drawtext滤镜也支持使用「fontfile」参数来指定所采用的字体文件,比如上面的otf文件。

以下是挑选出的4张截图添加文字后的效果:

添加文字后的截图1.jpg

添加文字后的截图2.jpg

添加文字后的截图3.jpg

添加文字后的截图4.jpg

多宫格处理

多宫格处理的工作实际就是前面所提到的「复杂滤镜」的使用场景,我们需要整合多个输入流(此处是静态图),并拼接到一个画布上输出。

先丢出处理的命令行:

ffmpeg -i 1.jpg -i 2.jpg -i 3.jpg -i 4.jpg -filter_complex "nullsrc=size=360x360[base];[base][0:v]overlay=0:0[tmp1];[tmp1][1:v]overlay=180:0[tmp2];[tmp2][2:v]overlay=0:180[tmp3];[tmp3][3:v]overlay=180:180" -frames:v 1 output.jpg

命令行很长,但是了解过前面的滤镜语法以后,相信也不难理解,我们来一步步解释:

多宫格流程图.png

  • 首先,通过-i 选项输入4张待处理的静态图素材;
  • 其次,通过-filter_complex 选项创建一个复杂滤镜;
  • 该复杂滤镜首先创建了一个360x360的空白画布,并指定输出流的标签为[base];
  • [0:v]表示取第一个输入流(第1张静态图),然后使用overlay滤镜,将第1张静态图叠加到左上角原点的位置(也即左上角),并指定叠加处理输出流的标签为[tmp1];
  • 继续使用overlay滤镜,将第2张静态图叠加到以左上角原点,x轴向右偏移量为180、y轴向下偏移量为0的位置(也即右上角),并指定叠加处理输出流的标签为[tmp2];
  • 继续使用overlay滤镜,将第3张静态图叠加到以左上角原点,x轴向右偏移量为0、y轴向下偏移量为180的位置(也即左下角),并指定叠加处理输出流的标签为[tmp3];
  • 继续使用overlay滤镜,将第4张静态图叠加到以左上角原点,x轴向右偏移量为180、y轴向下偏移量为180的位置(也即右下角);
  • 将全部处理完成的内容输出到一个名为“output.jpg”的文件

最终产出的效果图如下:

多宫格.jpg

命令行的优势

可能有人想说了,我干嘛要去写这么一大串晦涩难懂的命令行呢?要创作表情包,使用有可视化界面的软件操作不香吗?

诚然,可视化界面有可视化界面的优势,像前面添加文字的操作,鼠标或手指点点拖拖就可以完成了。但是别忘了,许多所谓的拥有可视化界面的软件,其只不过是在命令行工具上披一层皮而已

换句话说,只要熟悉了FFmpeg命令行工具的使用之后,我们就完全可以自己做一个拥有可视化界面,而功能底层使用FFmpeg命令行来实现的软件。

比如在Android平台上,我们就可以通过手动将FFmpeg源码编译成so库,并抽取ffmpeg编解码工具的相关文件,借助NDK开发自行封装成一个FFmpeg命令行工具库,然后集成到我们的App中去,从而实现一个功能丰富的视频编辑工具App。(后续文章将讲到,敬请关注)。

看到这里,你还不想打开FFmpeg命令行工具实际操作一番吗?

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿