AI 工具

讓 AI 會製作字卡與剪輯影片的能力 ( Pillow / FFmpeg )

最後更新於 冬筆 15 分鐘

嗨,大家好,今天我們用 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 開始畫,字會歪幾個像素。

用 Pillow 做出的透明影片字卡上,有白色粗體字和黑色描邊 圖|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 秒的畫面上方置中,出現 Pillow 做的白字字卡 圖|第 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 那一行重跑,字型和描邊比例都跟上一集一樣。

其他用 AI 寫程式處理素材的做法會放在 AI 工具 分類,剪輯和設計軟體本身的操作放在 創作工具 分類。

常見問題

Pillow 可以免費拿來做商業影片的字卡嗎?

可以。Pillow 在 PyPI 上標示的授權是 MIT-CMU,屬於寬鬆授權,可以免費使用。字型另有授權,微軟正黑體這類系統字型能不能放進商業影片,要另外看字型本身的條款。

FFmpeg 疊上字卡之後,影片的聲音會不見嗎?

不會。這篇的疊圖指令最後有一段參數,專門把聲音原封不動複製到新影片,測試片疊完字卡後聲音軌還在,長度一樣是 5 秒。改指令時別把那一段刪掉就好。

標題字太長,字卡超出影片怎麼辦?

讓程式自己縮字。這篇的字卡程式從 96 像素開始量,寬度超過 1208 像素就每次減 4,23 個字的長標題最後縮到 52 像素,文字寬度剛好在 1208 像素以內。

↑ ↓ 選擇・Enter 前往・Esc 關閉

冬筆點數

今天可以領 10 點

每天第一次登入冬筆就能領一次,點數不會過期。

點數規則