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

[圖片過大可能無法展示,可以直接點開連結檢視]http://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

[圖片過大可能無法展示,可以直接點開連結檢視]http://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命令列工具實際操作一番嗎?

我正在參與掘金技術社群創作者簽約計劃招募活動,點選連結報名投稿