我之前寫過一篇鉅細靡遺的如何透過 FFmpeg 將 SRT 字幕檔寫入到 MP4 影片檔中文章,也寫過一篇如何使用 FFmpeg 進行圖片壓縮與製作家庭影片文章,我覺得已經涵蓋了許多應用情境了。前陣子嘗試用 Gemini API 翻譯國外知名的 Podcast 節目,想說把翻譯好的轉錄稿直接跟 MP3 聲音檔結合,配一張圖片,就可以輸出個含字幕的 MP4 影片了,方便我邊聽、邊看字幕、邊學英文,誰知道 AI 問個老半天都問不出來。最終我還是搞定了這個需求,這篇文章來記錄一下重要的背景知識與觀念。
需求描述
我的需求很簡單:
- 我有一個 MP3 格式的聲音檔:
input.mp3
- 我有一個 JPG 格式的圖片檔:
image.jpg
- 我有一個 ASS 格式的字幕檔:
input.ass
- 我希望可以透過
ffmpeg
輸出一個 MP4 影片檔,畫面是唯一的一張靜態圖片,聲音要是 MP3 聲音檔的內容,畫面上要有 ASS 字幕。
先說,我嘗試過 ChatGPT, Claude, Gemini, phind, LLaMA 3 70B, ... 等大語言模型(LLM),無論用了多少提示工程技巧,所有提示詞都失效了,目前沒有一個 LLM 可以成功的產生正確的命令。但是,每個 LLM 都可以產生出各種「超逼真」的命令,你真的照著執行的話,也真的會產生 MP4 影片檔,只是產生出來的 MP4 影片全部都是有問題的,字幕全部都無法順利的顯示在影片中!
有興趣也想挑戰看看的,可以下載我的範例檔案,這份壓縮檔中的 input.mp3
是一個 10 秒的音檔,而 input.ass
裡面有 3 段字幕,每段顯示 3 秒鐘。大家可以試試看是否能透過 LLM 輸出正確的命令:ffmpeg-test.zip
透過 AI 產生的錯誤答案
我必須要先聲明一下,FFmpeg 是一套地獄級的命令列工具,參數不但多,還千變萬化,正常人不太可能知道這麼多跟影像處理有關的背景知識,所以很難學,如果你有很特殊的使用情境,不見得真的能夠很快搜尋到答案。但 AI 呢?簡單的用法應該都可以直接找到答案,但像我這次的需求,就很難透過提示找到答案,而且驗證答案唯一的方法,就是把查到的命令執行一遍看看。另一方面,因為 FFmpeg 的參數大部分都很難理解,而且 AI 講的東西不見得是對的,所以完成這個需求的過程真的是蠻痛苦的,會處在一個高度不確定的狀態下。
我先列出幾個 LLM 產生的「錯誤」答案,如果你照著 AI 提供的建議來執行 ffmpeg
命令,都無法產生正確的影片。
我的 Prompt 基本上是這樣:
1. 我有一個 MP3 格式的聲音檔:`input.mp3`
2. 我有一個 JPG 格式的圖片檔:`image.jpg`
3. 我有一個 ASS 格式的字幕檔:`input.ass`
4. 我希望可以透過 `ffmpeg` 輸出一個 MP4 影片檔,畫面是唯一的一張靜態圖片,聲音要是 MP3 聲音檔的內容,畫面上要有 ASS 字幕。
-
ChatGPT 4
ffmpeg -loop 1 -framerate 1 -i image.jpg -i input.mp3 -vf subtitles=input.ass -c:v libx264 -preset veryslow -crf 0 -c:a copy -shortest output-gpt-4.mp4
可以成功執行命令,有輸出一個 10 秒影片,但輸出的影片只會出現第一段字幕,時間到達第二段字幕時,並不會切換字幕,所以會讓第一段字幕從頭顯示到尾。
-
Gemini
這是我執行時得到的命令:
ffmpeg -loop 1 -i image.jpg -t 60 -vf scale=iw:-1 -i input.mp3 -i input.ass -c:v libx264 -c:a copy -c:s mov_subtitles -b:v 2M -pix_fmt rgb24 -vf subtitles=input.ass output-gemini.mp4
這是個無法執行的命令,錯誤訊息是:
Option vf (set video filters) cannot be applied to input url input.mp3 -- you are trying to apply an input option to an output file or vice versa. Move this option before the file it belongs to.
-
LLaMA 3 70B
ffmpeg -framerate 1 -loop 1 -i image.jpg -i input.mp3 -vf ass=input.ass -c:v libx264 -c:a aac -shortest output-llama3-70b.mp4
可以成功執行命令,有輸出影片,也有字幕,但是影片的長度卻大於音檔的 10 秒,總共輸出了一個 25 秒的影片檔。
-
Claude 3 Sonnet
ffmpeg -loop 1 -i image.jpg -i input.mp3 -vf subtitles=input.ass -c:v libx264 -tune stillimage -c:a aac -b:a 192k -pix_fmt yuv420p -shortest output-claude-3-sonnet.mp4
上面這段可以成功執行命令,有輸出影片,也有字幕,但是影片的長度卻大於音檔的 10 秒,總共輸出了一個 25 秒的影片檔。
以下是某位網友透過 Claude 3 Sonnet 產生的命令:
ffmpeg -loop 1 -i image.jpg -i input.mp3 -i input.ass -c:v libx264 -tune stillimage -c:a copy -c:s mov_text -shortest -pix_fmt yuv420p output-claude-3-sonnet-2.mp4
上面這段可以成功執行命令,有輸出一個 9 秒的影片,也有字幕,但是字幕只會出現 2 段,影片播放到第 5 秒的時候就會提前結束。
-
Phind 70B
ffmpeg -loop 1 -i image.jpg -i input.mp3 -vf ass=input.ass -c:v libx264 -c:a aac -strict experimental -b:a 192k output-phind-70b.mp4
可以成功執行命令,但程式不會停止,他會產生無限時間長度的影片,永遠不會停止,直到你的磁碟空間被填滿,所以是有問題的。
-
Perplexity
ffmpeg -loop 1 -i image.jpg -i input.mp3 -i input.ass -c:v libx264 -c:a aac -b:a 128k -c:s ass -map 0:v:0 -map 1:a:0 -map 2:s:0 -shortest output-perplexity.mp4
這是個無法執行的命令,原因出在 -c:s ass
不支援,會顯示 Could not find tag for codec ass in stream #2, codec not currently supported in container
錯誤訊息。
總之,我個人嘗試了好幾次,都是有問題的結果!
解決方案
我先說說我自己從 Super User 網站查到的最終結果,命令如下:
ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.ass'" -pix_fmt yuv420p -c:v libx264 -r 24000/1001 -c:a copy -map 0:a -map 1:v -shortest output.mp4
在這篇回答中,作者是這樣解釋的:
FFmpeg 通常會處理定時媒體的序列,例如影片、音訊或影像序列。但是當輸入單一影像時,它會將其視為一個持續時間為 1/fps
的畫面,不過一般的影片的 fps
(frame per seconds) 通常為 25
。因此,沒有影片畫布可以繪製字幕。將輸入迴圈選項 (-loop 1
) 新增到圖片中,會告訴 FFmpeg 從中產生一個不會結束(無限時間)的影片串流。
所以我的理解是這樣的:
-i input.mp3
是一個有時間長度的媒體,長度為 10
秒。
-i image.jpg
是一個沒有時間長度的圖片,前面搭配 -loop 1
就可以賦予圖片一個時間 (重複播放),加長到無限久。
-vf "ass='input.ass'"
單純只是加入 ASS 字幕資訊到「影片串流」中,因為我們已經把「圖片」加長到無限久,所以字幕才有機會被顯示。
-c:a copy
的 -c
代表 Codec (解碼器),而 :a
則是指音訊的解碼器,而 copy
是直接複製音訊串流到輸出,不進行重新編碼。
-shortest
則是蠻關鍵的參數,他會去判斷「影像」與「聲音」的時間長度,自動選擇一個最短的時間為最後輸出的影片長度。
至於其他參數都是多餘的,因為 FFmpeg 都有預設值,如果不介意的話,其他參數都可以刪除。
所以一個最簡短的命令如下:
ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.ass'" -c:a copy -shortest output3.mp4
各種錯誤的用法
依據我的需求,我這邊就列出幾個錯誤的命令,並嘗試解釋錯誤的原因。
-
拿掉 -loop 1
會怎樣?
以下命令會輸出一個沒有時間的影片,簡單講就是無法輸出正常的影片,因為「圖片」本身沒有「時間」長度,若輸出時搭配使用 -shortest
參數的話,最短的時間就是這張圖片,所以輸出的影片時間長度就會為 0,那就有問題了!
ffmpeg -y -i input.mp3 -i image.jpg -vf "ass='input.ass'" -c:a copy -shortest output3.mp4
-
拿掉 -shortest
會怎樣?
以下命令會輸出一個無限時間長度的影片,簡單講就是無法輸出正常的影片,因為 -loop 1 -i image.jpg
參數會導致「影像」的部分永遠不會停止,所以輸出影片的部分就不會停止,直到硬碟空間不夠才會發生錯誤,然後 FFmpeg 程式終止:
ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.ass'" output3.mp4
因此,當要輸出 MP3 加上靜態圖片且又要輸出字幕的話,就勢必一定要加上 -shortest
參數才能正常運作。
不過,有一個例外,那就是你可以加上 -t 10
參數,直接指定輸出影片的時間長度,這樣就真的不用特別加上 -shortest
參數了!
ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.ass'" -t 10 output4.mp4
這種用法不太方便,因為你要先知道聲音檔的長度,才能這樣設定,所以不太理想!
-
同時拿掉 -loop 1
與 -shortest
會怎樣?
那如果我不但拿掉 -loop 1
參數,連同 -shortest
參數一起移除,就會導致一種詭異的現象。
因此,輸出影片時:
- 影像時會以圖片為主,畫面不會動 (反正本來就是靜態圖片,不動沒關係)
- 聲音會以 MP3 為主,最終的影片會正常播放聲音
- 整體輸出的 MP4 影片,總時間長度為「最長」的輸入串流(Input Stream), 也就是 10 秒
ffmpeg -y -i input.mp3 -i image.jpg -vf "ass='input.ass'" -c:a copy output3.mp4
這樣的命令,整個輸出的 MP4 只會有一個 Key Frame,但影片可以正常播放,唯一的問題就在於「字幕」無法變動,因為字幕寫入到 MP4 後,其實應該變成「動畫」才對,是會動的畫面,但影片只有一個 Key Frame,完全不會動了,因此「字幕」就不會動,所以感覺就是只有第一個字幕會顯示,後面都不會看到任何字幕變化。
不過,只要你不打算輸出「字幕」的話,這個命令其實完全是合理的,輸出影片不會有任何問題!
ffmpeg -y -i input.mp3 -i image.jpg -c:a copy output3.mp4
因此,當要輸出 MP3 加上靜態圖片並且加上字幕的話,就勢必一定要加上 -loop 1
與 -shortest
參數才能正常運作。
-
拿掉 -c:a copy
會怎樣?
如果我們都有加上 -loop 1
與 -shortest
參數,但唯獨把 -c:a copy
參數移除會怎樣?
ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.ass'" -shortest output3.mp4
上述命令會輸出一個 25.8
秒的影片,超詭異。我一直無法理解為什麼會輸出一個 25.8
秒的影片,我都加上 -shortest
了,不是應該會選用「最短時間」的輸入串流(Input Stream)嗎?為什麼移掉 -c:a copy
之後就變成是 25.8
秒了呢?這多出的秒數是怎麼來的?其最終輸出的 mp4 影片,聲音很正常,影像正常,字幕也正常,就是影片時間被是被拉長了,怪!😕
我非常確定 -loop 1 -i image.jpg
參數,是指定對圖片 image.jpg
進行持續循環,不斷重複顯示這張圖像,所以時間是無限久。因此,這個問題的癥結點可能出在 FFmpeg 對「聲音檔」的處理了。從我的範例中,我的聲音檔是 MP3 格式,而 FFmpeg 預設的 MP3 解碼器為 mp3float
,沒有指定 -c:a
(聲音的 Codec 外掛) 的結果就是,輸出的影片會被強制改用 AAC Codec 進行轉換,也就是從 MP3 轉成 AAC 格式,此時轉換可能會有失真的狀況 (純臆測),導致原本應該輸出 10 秒的結果,變成了輸出 25.8 秒的狀況。不過這僅僅是我個人猜測而已,不確定對不對!🔥
我把 -c:a
參數加回去,並將 copy
改為 aac
來當成 Codec,也會導致輸入為 25.8
秒的狀況:
ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.ass'" -c:a aac -shortest output3.mp4
當您使用 -c:a copy
時,這告訴 FFmpeg 直接複製原始音訊串流,而不進行重新編碼。這樣通常會保持音訊串流的完整性!
網友的答案
我在臉書的粉絲團上有一則貼文讓網友提交透過 LLM 解題的方法,結果想不到有一位網友 Jian-tai Tsai
提供了一份他透過 Gemini 嘗試,其結果竟然也是正確的!
ffmpeg -loop 1 -i image.jpg -i input.mp3 -c:v libx264 -tune stillimage -c:a copy -pix_fmt yuv420p -vf "ass=input.ass" -shortest output-gemini-2.mp4
他的提示詞意外的簡單:ffmpeg create a video with image and audio and add subtitles
完整的提示與回應在此: https://gemini.google.com/share/8c0ebf0d0b66
我額外解釋一下幾個參數:
-c:v libx264
是指定 Video Codec 為 libx264
,而 -tune stillimage
則是特別針對只有「靜態圖片」的影片進行畫面的最佳化。
-pix_fmt yuv420p
則是指定影像部分的像素格式(Pixel Format),也就是使用 YUV 4:2:0 planar
格式的意思。這格式在影像壓縮的領域中很常見。
基本上,上述這兩個參數都可以省略不用。
額外補充說明
根據我這次的需求,其實還有很多 FFmpeg 的參數可以用,我再多整理一些可能會用到的參數說明:
-
指定輸出影片的 FPS
指定 -r 24000/1001
參數可以設定輸出影片的 FPS 為 23.976
frames per second 常用於電影與影片製作。
-
指定使用 SRT 或 WebVTT 字幕格式
先看看 ASS 字幕的語法
ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.ass'" -c:a copy -shortest output3.mp4
再來看看 SRT 或 WebVTT 字幕的語法
ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.vtt'" -c:a copy -shortest output3.mp4
ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.srt'" -c:a copy -shortest output3.mp4
詳見 如何透過 FFmpeg 將 SRT 字幕檔寫入到 MP4 影片檔中 文章
總結
本篇文章主要是想整理 FFmpeg 的各項參數所代表的意義,所以才寫的這麼長,這是我吸收消化資訊的方式,透過文字加以釐清觀念。
以下我針對幾個我未來可能遇到的情境進行使用方式的整理:
-
輸入: JPEG + MP3 , 輸出: MP4
ffmpeg -y -i "input.mp3" -i "image.jpg" -c:a "copy" output.mp4
-
輸入: JPEG + MP3 + ASS (字幕) , 輸出: MP4
ffmpeg -y -i "input.mp3" -loop "1" -i "image.jpg" -vf "ass='input.ass'" -c:a "copy" -shortest output.mp4
-
輸入: JPEG + MP3 + VTT (字幕) , 輸出: MP4
ffmpeg -y -i "input.mp3" -loop "1" -i "image.jpg" -vf "subtitles='input.vtt'" -c:a "copy" -shortest output.mp4
最後我分享一個我微調過的 AI 提示詞:
你是一個精通 FFmpeg 的影音處理專家,以下是我的需求:
1. 我有一個 MP3 格式的聲音檔:`input.mp3`
2. 我有一個 JPG 格式的圖片檔:`image.jpg`
3. 我有一個 ASS 格式的字幕檔:`input.ass`
4. 我希望可以透過 `ffmpeg` 輸出一個 MP4 影片檔,畫面是唯一的一張靜態圖片,聲音要是 MP3 聲音檔的內容,畫面上要有 ASS 字幕。
請問 ffmpeg 命令要如何執行?
用這組提示詞,想不到 Claude 3 Sonnet 竟然可以回答出正確答案!👍
ffmpeg -loop 1 -i image.jpg -i input.mp3 -vf "ass=input.ass" -c:v libx264 -tune stillimage -c:a copy -pix_fmt yuv420p -shortest output.mp4
同一組提示詞,在 ChatGPT 4 與 phind 都會使用 -c:a aac
參數,但這會導致輸出的影片長度不正確。而 Gemini 依然會產生無效的命令。
相關連結