讓 AI 會製作字卡與剪輯影片的能力 ( Pillow / FFmpeg )
嗨,大家好,今天我們用 Pillow(圖片處理套件)和 FFmpeg(處理影音的開源專案)製作字卡,再把字卡疊進影片,兩個都免費,一人管一半。Pillow 負責把標題畫成一張透明背景的 PNG(支援透明的圖片格式)字卡,FFmpeg 負責把字卡疊進影片,再把背景音樂混進聲音軌。文章後面附一張規格清單,可以直接拿去請 AI 寫程式。
照著做完,你手上會有三個檔案,一張透明背景的 title.png,一支字卡第 1 秒出現,第 4 秒消失的 output.mp4,還有一支混進背景音樂的 final.mp4。
Pillow 是什麼,在字卡裡負責哪一段?
Pillow 是 Python(程式語言)的圖片處理套件,在影片裡負責製作字卡。官方文件說它是 PIL(Python 早期的圖片處理函式庫)的友善分支,PIL 是 Fredrik Lundh 在 1995 年發表的。
做字卡的時候,Pillow 會做這幾件事。
- 載入字型,量一行字有多寬
- 畫字,加描邊和陰影
- 存成一張透明背景的 PNG
安裝的時候套件叫 Pillow,程式裡卻是寫 from PIL import,這是沿用 PIL 的名字,兩個指的是同一套。跑下面兩行,第二行印得出版本號就裝好了。
pip install Pillow
python -c "import PIL; print(PIL.__version__)"
授權在 PyPI(Python 套件的官方下載站)標的是 MIT-CMU(一種寬鬆的開源授權)。目前最新是 13.0.0,2026 年 10 月 16 日發佈,需要 Python 3.11 以上,現在用 pip(Python 的套件安裝工具)安裝會拿到這一版。這篇的程式是在 Windows 11,Python 3.12.10,Pillow 12.2.0 跑的,13.0.0 沒有實跑過,它的版本說明列出的移除項目,沒有碰到這篇用的畫字和合成功能。
想跟這篇的環境完全一樣,安裝時改成 pip install Pillow==12.2.0。Mac 和 Linux 的字型路徑不一樣,下面的程式沒有在那兩個系統上跑過。
FFmpeg 是什麼,在影片裡負責哪些事?
FFmpeg 是處理影音的開源專案,在字卡流程裡負責影片和聲音,我們用的是它的命令列工具 ffmpeg。這篇會用到 FFmpeg 的三件事。
- 把一張風景照片做成一支 5 秒的影片
- 把字卡疊進畫面
- 把背景音樂混進原本的聲音軌
官方下載頁寫明 FFmpeg 只提供原始碼,Windows 的執行檔要從它連到的 gyan.dev(提供 Windows 版 FFmpeg 的網站)或 BtbN(另一個提供編譯好版本的開發者)下載。下載頁上最新的穩定版是 9.0.2,2026 年 9 月 18 日發佈,這篇是用 gyan.dev 的 8.1 跑的,9.0.2 沒有實跑過。
ffmpeg -version
那授權怎麼算?官方 Legal(法律授權說明)頁寫的是 LGPL(允許其他程式連結使用的開源授權)2.1 以上,用到選配的 GPL(散布時要求連原始碼一起公開的開源授權)元件時,整套 FFmpeg 都會變成 GPL。gyan.dev 的完整版執行 ffmpeg -L,印出來的是 GPL 第 3 版以上,這些授權條款管的是散布 FFmpeg 程式本身。
Pillow 和 FFmpeg 怎麼分工,讓 AI 做出字卡?
分工就是一句話,Pillow 管一張圖上的字,FFmpeg 管影片時間軸上的事,兩邊交接的只有一張透明 PNG。所以改字只要重跑 Python,改秒數或換音樂只要改 FFmpeg 指令,請 AI 改程式時也可以一次只動一邊。
整套流程拆成六步,哪一步歸誰做,對照這張表就好。
| 工作 | 誰來做 | 怎麼做 |
|---|---|---|
| 畫字,描邊,陰影 | Pillow | 畫在一張全透明的圖上,存成 PNG |
| 字太長自動縮字 | Pillow | 量寬度,超過 1208 像素就縮 |
| 字卡第 1 到第 4 秒出現 | FFmpeg | overlay 加 enable |
| 混進背景音樂 | FFmpeg | amix 混兩條聲音 |
| 整張字卡淡入 | FFmpeg | fade 處理那張 PNG |
| 3D 金屬字這類素材 | AI 生成單張圖 | 驗收透明度後再疊 |
表上會動的效果和聲音全在 FFmpeg 那一欄,因為 Pillow 只輸出靜態圖。整張字卡淡入用的是 FFmpeg 的 fade(淡入淡出濾鏡),靜態圖要先加 -loop 1 變成一段畫面,再用 fade=in:alpha=1 只淡入透明度。
真的 3D 金屬字和官方 logo(標誌)的手繪字形,也不要用 Pillow 硬做。Pillow 官方說明寫 ImageDraw(畫圖模組)提供的是簡單的 2D 圖形,疊幾層陰影可以做出假立體,可是光源方向是固定的,做不出金屬反光。logo 就從官方原圖切下來,去背裁好,當成另一張透明圖層疊上去。
怎麼用 Pillow 做出透明字卡?
用 Pillow 做透明字卡,就是先挑字型,再量字,最後把字畫在一張全透明的圖上。這邊用 Windows 內建的微軟正黑體粗體,檔案在 C:/Windows/Fonts/msjhbd.ttc。副檔名 ttc(字型集合檔)的意思是一個檔案裡裝了好幾套字,載入時要用 index(編號)指定第幾套,可以先跑這段把它們列出來。
from PIL import ImageFont
FONT_PATH = "C:/Windows/Fonts/msjhbd.ttc"
for i in range(10):
try:
print(i, ImageFont.truetype(FONT_PATH, 20, index=i).getname())
except OSError:
break
跑完會看到兩行,0 ('Microsoft JhengHei', 'Bold') 和 1 ('Microsoft JhengHei UI', 'Bold')。0 是 Microsoft JhengHei(微軟正黑體)的粗體,1 是介面用的版本,我們用 0。
字型挑好,接著算字最寬可以到多寬。影片是 1280x720,四周留短邊的 5% 當安全邊距,720 × 0.05 = 36 像素。左右各扣 36,1280 − 72 = 1208,下面這段程式會照這個寬度,做出一張白字,黑色描邊,右下帶柔和陰影的字卡,存成 title.png。
from PIL import Image, ImageDraw, ImageFont, ImageFilter
FONT_PATH = "C:/Windows/Fonts/msjhbd.ttc" # 微軟正黑體粗體
FONT_INDEX = 0
TEXT = "今天天氣真好"
VIDEO_W, VIDEO_H = 1280, 720
SAFE = int(min(VIDEO_W, VIDEO_H) * 0.05) # 安全邊距 36px
MAX_W = VIDEO_W - SAFE * 2
size = 96
while True:
font = ImageFont.truetype(FONT_PATH, size, index=FONT_INDEX)
stroke = max(2, round(size * 0.06))
probe = ImageDraw.Draw(Image.new("L", (1, 1)))
l, t, r, b = probe.textbbox((0, 0), TEXT, font=font, stroke_width=stroke)
if r - l <= MAX_W or size <= 24:
break
size -= 4 # 太寬就縮字
pad = 32
card = Image.new("RGBA", (r - l + pad * 2, b - t + pad * 2), (0, 0, 0, 0))
shadow = Image.new("RGBA", card.size, (0, 0, 0, 0))
ImageDraw.Draw(shadow).text((pad - l + 6, pad - t + 6), TEXT, font=font, fill=(0, 0, 0, 160))
card = Image.alpha_composite(card, shadow.filter(ImageFilter.GaussianBlur(6)))
ImageDraw.Draw(card).text((pad - l, pad - t), TEXT, font=font, fill="white",
stroke_width=stroke, stroke_fill="black")
card.save("title.png")
print("字級", size, "描邊", stroke, "字卡尺寸", card.size)
跑完,終端機會印出字級 96,描邊 6,字卡尺寸 652x162,資料夾裡也多了一張 title.png。描邊抓字級的 6%,96 × 0.06 = 5.76,四捨五入成 6 像素。這邊要注意,畫字的位置要扣掉 textbbox(量文字外框的函式)回傳的左和上,因為字形本身有一點偏移,直接從 0 開始畫,字會歪幾個像素。
圖|title.png 的背景是透明的,字的外圍有一圈黑邊
接著試試字太長的情況,我們把 TEXT(程式裡放標題文字的變數)換成 23 個字的長標題再跑一次。字級從 96 開始,每次減 4,減了 11 次,停在 52 像素,字卡變成 1266x118。
96 縮到 52,字小了將近一半,整行確實放進畫面了。字卡的 1266 比 1208 寬,是因為程式在文字四周各留了 32 像素的透明邊,文字本身 1266 − 64 = 1202 像素,還在上限內。
背景比較花的時候,白字黑描邊還是可能被吃掉,這時候可以在字後面墊一塊半透明的黑底框。下面這段用 rounded_rectangle(畫圓角矩形的函式)把同一行字畫在圓角黑底上,fill(填色參數)最後一個數字 160 是透明度,0 是全透明,255 是完全不透明。
from PIL import Image, ImageDraw, ImageFont
FONT = ImageFont.truetype("C:/Windows/Fonts/msjhbd.ttc", 72, index=0)
TEXT = "今天天氣真好"
PAD_X, PAD_Y, RADIUS = 40, 24, 24
l, t, r, b = ImageDraw.Draw(Image.new("L", (1, 1))).textbbox((0, 0), TEXT, font=FONT)
w, h = r - l + PAD_X * 2, b - t + PAD_Y * 2
card = Image.new("RGBA", (w, h), (0, 0, 0, 0))
draw = ImageDraw.Draw(card)
draw.rounded_rectangle((0, 0, w - 1, h - 1), radius=RADIUS, fill=(0, 0, 0, 160))
draw.text((PAD_X - l, PAD_Y - t), TEXT, font=FONT, fill="white")
card.save("title-box.png")
print("底框字卡尺寸", card.size)
跑完會印出底框字卡尺寸 512x112。這邊要注意,這段的字級固定是 72,沒有自動縮字,標題長的話,把前面那段量寬度的迴圈搬過來用。
怎麼用 FFmpeg 把字卡疊進影片,再混進音樂?
FFmpeg 這邊先用 overlay(疊圖濾鏡)疊字卡,再用 enable(指定作用時間的參數)指定時間。手邊沒有影片也沒關係,第一行用一張風景照片做一支 5 秒,1280x720 的影片,聲音是 FFmpeg 內建的 440 赫茲測試音,第二行才是真正把字卡疊上去的指令。
照片用的是 Wikimedia Commons(維基共享資源)上的 Lake_Mountain_Landscape.jpg,作者 Bonnie Moreland,授權是 CC0(放棄著作權的公眾領域宣告),可以商用,也不用標出處。下載 1280 寬的版本是 1280x853,crop=1280:720 會從正中間裁出 1280x720。-loop 1 讓這張照片一直重複成畫面,-t 5 截在 5 秒,換成自己的照片也可以。
ffmpeg -loop 1 -i 1280px-Lake_Mountain_Landscape.jpg -f lavfi -i sine=frequency=440:duration=5 -vf crop=1280:720 -t 5 -r 30 -c:v libx264 -pix_fmt yuv420p -c:a aac -shortest input.mp4
ffmpeg -i input.mp4 -i title.png -filter_complex "[0:v][1:v]overlay=(W-w)/2:60:enable='between(t,1,4)'" -c:a copy output.mp4
第二行裡的 W 和 w 長得很像,意思卻不一樣。FFmpeg 官方的 overlay 說明寫明,大寫 W 和 H 是主影片的寬高,小寫 w 和 h 是疊上去那張圖的寬高。
(W-w)/2 也就是水平置中,1280 − 652 = 628,628 ÷ 2 = 314,字卡左邊從 314 像素開始,上緣離頂端 60 像素。enable='between(t,1,4)' 讓濾鏡只在第 1 到第 4 秒作用,其他時間畫面原樣通過。
跑完會得到一支 output.mp4,長度 5 秒,聲音軌還在。抽三格畫面出來看,第 0.5 秒和第 4.5 秒都沒有字卡,第 2 秒字卡置中出現在上方。聲音沒有消失,靠的是 -c:a copy,這個參數讓聲音直接複製過去,不重新編碼。
圖|第 2 秒抽出來的畫面,字卡離上緣 60 像素,背景照片 Bonnie Moreland,CC0
黑底框的版本換一張雲多的照片來試,第二行的 title.png 換成 title-box.png,1280 − 512 = 768,768 ÷ 2 = 384,底框從 384 像素開始。
圖|雲層亮暗不一,墊了黑底框字還是看得清楚,背景照片 www.Pixel.la Free Stock Photos,CC0
字卡疊好,再把背景音樂混進去。手邊沒有音樂檔,一樣先讓 FFmpeg 做一段 5 秒,660 赫茲的測試音當背景音樂,再用 amix(混音濾鏡)把它跟影片原本的聲音混在一起。
ffmpeg -f lavfi -i sine=frequency=660:duration=5 -c:a aac music.m4a
ffmpeg -i output.mp4 -i music.m4a -filter_complex "[0:a][1:a]amix=inputs=2:duration=first[a]" -map 0:v -map "[a]" -c:v copy -c:a aac final.mp4
-map 0:v 拿第一個檔的畫面,-map "[a]" 拿混好的聲音,-c:v copy 讓畫面直接複製不重新編碼,所以 final.mp4 第 2 秒的畫面跟 output.mp4 一格不差。
長度交給 duration=first,讓聲音跟著影片走。amix 預設是跟最長的那一條,音樂比影片長的話,聲音就會比畫面長。
這邊還有一個音量的坑。amix 預設會把每一條聲音的音量平均分攤,output.mp4 的平均音量是 −21.1 dB(分貝),混完的 final.mp4 變成 −24.1 dB,小了 3 dB。想保住原本的音量可以加 normalize=0 直接相加,實測平均是 −18.0 dB,可是最大音量也衝到 −5.4 dB,兩條聲音都很大聲時容易破音。
做字卡最常遇到哪三個錯?
這三個錯裡,有兩個程式不會停下來,一個是字變方塊,一個是先轉 RGB(不含透明度的色彩模式)再存 JPG(不支援透明的圖片格式)出現的黑框,都要打開 title.png 或疊上影片才看得到。第三個是照舊教學寫的程式直接報錯。
先看字型。字變成一排方塊,代表字型檔裡沒有那些中文字。拿 Arial(沒有中文字的英文字型)的 arial.ttf 畫重和點兩個字,出來是兩個一模一樣的空框。
換成有中文的字型,再確認 ttc 的 index 有選對就好。index 選到不存在的編號,會跳 OSError: invalid argument,路徑打錯則是 OSError: cannot open resource。
接著是存檔格式,字卡直接存成 JPG 會報錯 OSError: cannot write mode RGBA as JPEG,意思是 RGBA(含透明度的色彩模式)的圖不能存成 JPG。先轉 RGB 再存,錯是不報了,透明的地方卻全部變成黑色。拿這張 JPG 去疊影片,畫面上方會多出一塊 652x162 的黑底。
要疊影片的字卡一律存 PNG。真的需要 JPG,像是拿去當封面,就先墊一張白底再轉。
from PIL import Image
card = Image.open("title.png")
white = Image.new("RGBA", card.size, "white")
Image.alpha_composite(white, card).convert("RGB").save("title.jpg", quality=92)
還有一個錯會在照舊教學做的時候出現。網路上不少範例用 draw.textsize() 量字,這個 textsize(舊版量文字大小的函式)在 Pillow 10.0.0 已經移除,官方的 Deprecations(棄用與移除清單)有寫,在 12.2.0 跑會出現 AttributeError: 'ImageDraw' object has no attribute 'textsize'。改用 textbbox() 量寬高,只量寬度就用 textlength(量文字寬度的函式)。
請 AI 寫字卡程式時要給它哪些條件?
請 AI 寫製作字卡的程式前,先把分工和驗收方法寫成清單,程式交回來時才有東西可以一條一條對。前面的程式,也可以照這張清單請 AI 寫。
我在 被資遣後開一人公司 那篇寫過自己的分法,題目和網站長什麼樣子自己決定,寫程式和整理資料交給 AI。下面這張清單就是先把字卡長什麼樣子寫清楚,前四條是字卡程式,第五條和第六條是 FFmpeg 指令,第七條是接下來的驗收程式。
1. Pillow 只負責輸出一張透明背景的 PNG,不要碰影片
2. 字型路徑 C:/Windows/Fonts/msjhbd.ttc,index 0
3. 影片 1280x720,文字寬度不能超過 1208 像素,也就是左右各留 36 像素,太寬就自動縮字
4. 白字黑描邊,描邊約字級的 6%,右下加一層模糊陰影
5. FFmpeg 用 overlay 疊上去,水平置中,離上緣 60 像素,第 1 到第 4 秒出現,聲音直接複製
6. 背景音樂用 amix 混進原本的聲音,長度跟著影片,畫面直接複製
7. 交回程式時附驗收,字卡要是 RGBA,透明度最小 0,最大 255
AI 把程式交回來之後,疊影片之前,我們先跑這段驗收字卡。
from PIL import Image
card = Image.open("title.png")
print(card.mode, card.size)
if card.mode == "RGBA":
print("透明度", card.getchannel("A").getextrema())
印出 RGBA (652, 162) 和 透明度 (0, 255),代表圖裡同時有全透明和不透明的像素,是一張真的透明字卡。要是只印出 RGB 和尺寸,沒有透明度那一行,或透明度最小值不是 0,就是背景沒有透明,疊上去那一塊會變成黑底,蓋住底下的畫面。
3D 金屬字這類 Pillow 做不到的素材,可以請 AI 生成單張圖再疊上去,這一步沒有附實測,拿到圖一樣先跑上面那段驗收。
Pillow 就是一個讓你的 AI 直接寫字卡的工具
整套做法的重點有三個,字卡一律存透明背景的 PNG,疊進影片靠 overlay 加 enable 指定秒數,混音樂用 amix,要留意它預設會把音量平均分攤。規格寫成清單交給 AI,程式交回來先跑那段驗收,確認背景真的是透明的。
舉個例子,標題用前面程式裡的 TEXT,也就是今天天氣真好,畫面是 1280x720 的雪山湖景。跑一次字卡程式,資料夾裡會多一張 652x162 的 title.png,再跑 overlay 那行,字卡就從第 1 秒停到第 4 秒,最後跑 amix 那行,拿到一支混好背景音樂的 final.mp4。換下一集只要改 TEXT 那一行重跑,字型和描邊比例都跟上一集一樣。
常見問題
Pillow 可以免費拿來做商業影片的字卡嗎?
可以。Pillow 在 PyPI 上標示的授權是 MIT-CMU,屬於寬鬆授權,可以免費使用。字型另有授權,微軟正黑體這類系統字型能不能放進商業影片,要另外看字型本身的條款。
FFmpeg 疊上字卡之後,影片的聲音會不見嗎?
不會。這篇的疊圖指令最後有一段參數,專門把聲音原封不動複製到新影片,測試片疊完字卡後聲音軌還在,長度一樣是 5 秒。改指令時別把那一段刪掉就好。
標題字太長,字卡超出影片怎麼辦?
讓程式自己縮字。這篇的字卡程式從 96 像素開始量,寬度超過 1208 像素就每次減 4,23 個字的長標題最後縮到 52 像素,文字寬度剛好在 1208 像素以內。
這篇有幫助嗎?
按個讚讓我知道,或分享給需要的朋友。
用 Google 登入・登入後累積冬筆點數讚記在 的帳號・我的帳號