﻿# 如何透過 FFmpeg 將 MP3 音檔、圖片與 ASS 字幕寫入到 MP4 影片中

我之前寫過一篇鉅細靡遺的[如何透過 FFmpeg 將 SRT 字幕檔寫入到 MP4 影片檔中](/post/2024/02/20/FFmpeg-Write-SRT-to-MP4-video-file)文章，也寫過一篇[如何使用 FFmpeg 進行圖片壓縮與製作家庭影片](/post/2022/10/10/Make-Family-Video-using-FFmpeg)文章，我覺得已經涵蓋了許多應用情境了。前陣子嘗試用 [Gemini API](https://ai.google.dev/) 翻譯國外知名的 Podcast 節目，想說把翻譯好的轉錄稿直接跟 MP3 聲音檔結合，配一張圖片，就可以輸出個含字幕的 MP4 影片了，方便我邊聽、邊看字幕、邊學英文，誰知道 AI 問個老半天都問不出來。最終我還是搞定了這個需求，這篇文章來記錄一下重要的背景知識與觀念。

![ffmpeg-image-mp3-ass](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/ffff757cd1acd9119b77-7199988c-0c17-4d50-b334-118a07ea9c43.webp)

### 需求描述

我的需求很簡單：

1.  我有一個 MP3 格式的聲音檔：`input.mp3`
2.  我有一個 JPG 格式的圖片檔：`image.jpg`
3.  我有一個 ASS 格式的字幕檔：`input.ass`
4.  我希望可以透過 `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](https://github.com/doggy8088/i/files/15208099/ffmpeg-test.zip)

### 透過 AI 產生的錯誤答案

我必須要先聲明一下，[FFmpeg](https://www.ffmpeg.org/) 是一套地獄級的命令列工具，參數不但多，還千變萬化，正常人不太可能知道這麼多跟影像處理有關的背景知識，所以很難學，如果你有很特殊的使用情境，不見得真的能夠很快搜尋到答案。但 AI 呢？簡單的用法應該都可以直接找到答案，但像我這次的需求，就很難透過提示找到答案，而且驗證答案唯一的方法，就是把查到的命令執行一遍看看。另一方面，因為 FFmpeg 的參數大部分都很難理解，而且 AI 講的東西不見得是對的，所以完成這個需求的過程真的是蠻痛苦的，會處在一個高度不確定的狀態下。

我先列出幾個 LLM 產生的「錯誤」答案，如果你照著 AI 提供的建議來執行 `ffmpeg` 命令，都無法產生正確的影片。

我的 Prompt 基本上是這樣：

```
1. 我有一個 MP3 格式的聲音檔：`input.mp3`
2. 我有一個 JPG 格式的圖片檔：`image.jpg`
3. 我有一個 ASS 格式的字幕檔：`input.ass`
4. 我希望可以透過 `ffmpeg` 輸出一個 MP4 影片檔，畫面是唯一的一張靜態圖片，聲音要是 MP3 聲音檔的內容，畫面上要有 ASS 字幕。
```

1.  ChatGPT 4
    
    ```bash
    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 秒影片，但輸出的影片只會出現第一段字幕，時間到達第二段字幕時，並不會切換字幕，所以會讓第一段字幕從頭顯示到尾。
    
2.  Gemini
    
    這是我執行時得到的命令：
    
    ```bash
    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.
    
3.  LLaMA 3 70B
    
    ```bash
    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 秒的影片檔。
    
4.  Claude 3 Sonnet
    
    ```bash
    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 產生的命令：
    
    ```bash
    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 秒的時候就會提前結束。
    
5.  Phind 70B
    
    ```bash
    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
    ```
    
    > 可以成功執行命令，但程式不會停止，他會產生無限時間長度的影片，永遠不會停止，直到你的磁碟空間被填滿，所以是有問題的。
    
6.  Perplexity
    
    ```bash
    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](https://superuser.com/) 網站查到的[最終結果](https://superuser.com/a/1181122/95798)，命令如下：

```bash
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 從中產生一個**不會結束**(無限時間)的影片串流。

所以我的理解是這樣的：

1.  `-i input.mp3` 是一個有時間長度的媒體，長度為 `10` 秒。
2.  `-i image.jpg` 是一個沒有時間長度的圖片，前面搭配 `-loop 1` 就可以賦予圖片一個時間 (重複播放)，加長到**無限久**。
3.  `-vf "ass='input.ass'"` 單純只是加入 ASS 字幕資訊到「影片串流」中，因為我們已經把「圖片」加長到**無限久**，所以字幕才有機會被顯示。
4.  `-c:a copy` 的 `-c` 代表 Codec (解碼器)，而 `:a` 則是指**音訊的解碼器**，而 `copy` 是直接複製音訊串流到輸出，不進行重新編碼。
5.  `-shortest` 則是蠻關鍵的參數，他會去判斷「影像」與「聲音」的時間長度，自動選擇一個最短的時間為最後輸出的影片長度。

至於其他參數都是多餘的，因為 FFmpeg 都有預設值，如果不介意的話，其他參數都可以刪除。

所以一個最簡短的命令如下：

```bash
ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.ass'" -c:a copy -shortest output3.mp4
```

### 各種錯誤的用法

依據我的需求，我這邊就列出幾個錯誤的命令，並嘗試解釋錯誤的原因。

1.  拿掉 `-loop 1` 會怎樣？
    
    以下命令會輸出一個**沒有時間**的影片，簡單講就是無法輸出正常的影片，因為「圖片」本身沒有「時間」長度，若輸出時搭配使用 `-shortest` 參數的話，最短的時間就是這張圖片，所以輸出的影片時間長度就會為 0，那就有問題了！
    
    ```bash
    ffmpeg -y -i input.mp3 -i image.jpg -vf "ass='input.ass'" -c:a copy -shortest output3.mp4
    ```
    
2.  拿掉 `-shortest` 會怎樣？
    
    以下命令會輸出一個**無限時間長度**的影片，簡單講就是無法輸出正常的影片，因為 `-loop 1 -i image.jpg` 參數會導致「影像」的部分永遠不會停止，所以輸出影片的部分就不會停止，直到硬碟空間不夠才會發生錯誤，然後 FFmpeg 程式終止：
    
    ```bash
    ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.ass'" output3.mp4
    ```
    
    因此，當要輸出 MP3 加上靜態圖片且又要輸出字幕的話，就勢必一定要加上 `-shortest` 參數才能正常運作。
    
    不過，有一個例外，那就是你可以加上 `-t 10` 參數，直接指定輸出影片的時間長度，這樣就真的不用特別加上 `-shortest` 參數了！
    
    ```bash
    ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.ass'" -t 10 output4.mp4
    ```
    
    這種用法不太方便，因為你要先知道聲音檔的長度，才能這樣設定，所以不太理想！
    
3.  同時拿掉 `-loop 1` 與 `-shortest` 會怎樣？
    
    那如果我不但拿掉 `-loop 1` 參數，連同 `-shortest` 參數一起移除，就會導致一種詭異的現象。
    
    -   圖片沒有時間長度
    -   聲音長度為 10 秒
    
    因此，輸出影片時：
    
    -   影像時會以圖片為主，畫面不會動 (反正本來就是靜態圖片，不動沒關係)
    -   聲音會以 MP3 為主，最終的影片會正常播放聲音
    -   整體輸出的 MP4 影片，總時間長度為「最長」的輸入串流(Input Stream)， 也就是 10 秒
    
    ```bash
    ffmpeg -y -i input.mp3 -i image.jpg -vf "ass='input.ass'" -c:a copy output3.mp4
    ```
    
    這樣的命令，整個輸出的 MP4 只會有一個 Key Frame，但影片可以正常播放，唯一的問題就在於「字幕」無法變動，因為字幕寫入到 MP4 後，其實應該變成「動畫」才對，是會動的畫面，但影片只有一個 Key Frame，完全不會動了，因此「字幕」就不會動，所以感覺就是只有第一個字幕會顯示，後面都不會看到任何字幕變化。
    
    不過，只要你不打算輸出「字幕」的話，這個命令其實完全是合理的，輸出影片不會有任何問題！
    
    ```bash
    ffmpeg -y -i input.mp3 -i image.jpg -c:a copy output3.mp4
    ```
    
    因此，當要輸出 MP3 加上靜態圖片並且加上字幕的話，就勢必一定要加上 `-loop 1` 與 `-shortest` 參數才能正常運作。
    
4.  拿掉 `-c:a copy` 會怎樣？
    
    如果我們都有加上 `-loop 1` 與 `-shortest` 參數，但唯獨把 `-c:a copy` 參數移除會怎樣？
    
    ```bash
    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` 秒的狀況：
    
    ```bash
    ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.ass'" -c:a aac -shortest output3.mp4
    ```
    
    > 當您使用 `-c:a copy` 時，這告訴 FFmpeg 直接複製原始音訊串流，而不進行重新編碼。這樣通常會保持音訊串流的完整性！
    

### 網友的答案

我在臉書的粉絲團上[有一則貼文](https://www.facebook.com/will.fans/posts/pfbid02HqMs12HGU1S8joqUJrHzochW2PzVjBxKHqC2pRBJmnLgtP8ziVoNmHVCyYoZ8o4Dl)讓網友提交透過 LLM 解題的方法，結果想不到有一位網友 `Jian-tai Tsai` 提供了一份他透過 Gemini 嘗試，其結果竟然也是正確的！

```bash
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](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 字幕的語法
    
    ```bash
    ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.ass'" -c:a copy -shortest output3.mp4
    ```
    
    再來看看 SRT 或 WebVTT 字幕的語法
    
    ```bash
    ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.vtt'" -c:a copy -shortest output3.mp4
    ```
    
    ```bash
    ffmpeg -y -i input.mp3 -loop 1 -i image.jpg -vf "ass='input.srt'" -c:a copy -shortest output3.mp4
    ```
    
    > 詳見 [如何透過 FFmpeg 將 SRT 字幕檔寫入到 MP4 影片檔中](/post/2024/02/20/FFmpeg-Write-SRT-to-MP4-video-file) 文章
    

### 總結

本篇文章主要是想整理 FFmpeg 的各項參數所代表的意義，所以才寫的這麼長，這是我吸收消化資訊的方式，透過文字加以釐清觀念。

以下我針對幾個我未來可能遇到的情境進行使用方式的整理：

1.  輸入: JPEG + MP3 , 輸出: MP4
    
    ```bash
    ffmpeg -y -i "input.mp3" -i "image.jpg" -c:a "copy" output.mp4
    ```
    
2.  輸入: JPEG + MP3 + ASS (字幕) , 輸出: MP4
    
    ```bash
    ffmpeg -y -i "input.mp3" -loop "1" -i "image.jpg" -vf "ass='input.ass'" -c:a "copy" -shortest output.mp4
    ```
    
3.  輸入: JPEG + MP3 + VTT (字幕) , 輸出: MP4
    
    ```bash
    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](https://claude.ai/) 竟然可以回答出正確答案！👍

```bash
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](https://chatgpt.com/) 與 [phind](https://www.phind.com/) 都會使用 `-c:a aac` 參數，但這會導致輸出的影片長度不正確。而 [Gemini](https://gemini.google.com/) 依然會產生無效的命令。

### 相關連結

-   [FFmpeg](https://www.ffmpeg.org/)
-   [ffmpeg Documentation](https://ffmpeg.org/ffmpeg-all.html)
-   [FFmpeg Filters Documentation](https://ffmpeg.org/ffmpeg-filters.html)
-   [video - I want to merge image audio and subtitle with ffmpeg - Super User](https://superuser.com/a/1181122/95798)
-   [ffmpeg - reddit](https://www.reddit.com/r/ffmpeg/)
    -   [FFmpeg useful links](https://www.reddit.com/r/ffmpeg/comments/916iy8/ffmpeg_useful_links/)
-   [Video Production Stack Exchange](https://video.stackexchange.com/)
