影片製作技能
對於由程式碼製作的動態圖形——動態文字、解說影片、發表會影片、動畫圖表、可選擇視覺風格的片尾文字——請使用
motion-graphics技能。此技能適用於原始影片、照片、音訊以及 ffmpeg 編輯。
使用 ffmpeg(核心引擎)和 Python(Pillow 處理影格/圖形,MoviePy 進行高階編輯)以程式方式建構影片。始終先規劃、再建構、最後驗證。
1. 規劃先行
在撰寫任何指令前,先確定這些事項:
| 決策 | 預設值 | 備註 |
|---|---|---|
| 長寬比 | 16:9(1920x1080) | Reels/Shorts/TikTok 用 9:16(1080x1920),動態牆用 1:1(1080x1080) |
| 影格率 | 30 fps | 電影感用 24,螢幕錄影/遊戲畫面用 60 |
| 長度 | 依要求 | Reels/Shorts 通常 ≤ 60–90 秒 |
| 格式 | MP4(H.264 + AAC) | 幾乎到處可播;僅在指定時才用 WebM;僅用於短循環才用 GIF |
| 音訊 | 可選 | 音樂、旁白或無聲 |
撰寫簡短的鏡位清單:每個場景 → 持續時間、視覺、文字、轉場、音訊。
2. 檢查工具鏈
which ffmpeg ffprobe || (apt-get update && apt-get install -y ffmpeg)
python3 -c "import PIL, moviepy" 2>/dev/null || pip install pillow moviepy --break-system-packages
fc-list | head # 查看可用的字型
3. 標準編碼設定
除非使用者指定其他方式,否則每次最終渲染都使用這些設定:
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -movflags +faststart \
-c:a aac -b:a 192k
-pix_fmt yuv420p— 在手機、QuickTime 和瀏覽器上播放的必要條件。-movflags +faststart— 讓影片在完全下載前就可以開始播放。-crf 18= 接近無損(檔案較大),-crf 28= 檔案較小,畫質較低。-preset veryfast可保持快速渲染;只有在要求最佳畫質時才使用slow。- 寬度和高度必須是偶數才能在 H.264 中使用。
3b. 快速渲染——使用者正在等待
完整渲染是最慢的步驟,因此每個請求請規劃一次完整渲染:
- 編碼前先預覽。 以最終尺寸抽取或繪製幾個影格——最忙碌的時刻、第一個和最後一個影格——並檢查構圖、文字大小和顏色。對於照片影片:
scripts/slideshow.py OUT.mp4 photos… --preview可在幾秒鐘內產生聯絡表單。 - 場景,而非單一巨大圖表。 將每個場景編碼成獨立檔案,使用相同的尺寸、fps 和編碼器設定,然後用 concat demuxer 和
-c copy將它們合併,最後再用-c:v copy加入聲音。之後修正一個場景只需要重新編碼該場景。 - 轉場效果存在於場景檔案內部(在其邊緣淡入/淡出)。對兩個已完成的檔案進行交叉淡出會強制進行第二次完整編碼。
- 節省靜態畫面的成本。 使用
loop濾鏡將圖片解碼一次並重複使用,而不是使用-loop 1(後者會為每一影格再次解碼圖片):ffmpeg -framerate 30 -i still.png -vf "format=yuv420p,loop=-1:1,trim=duration=4,setpts=PTS-STARTPTS" -c:v libx264 -preset veryfast -crf 20 -pix_fmt yuv420p still.mp4 - 絕不使用真實名稱保留未完成的檔案。 寫入到
name.part.mp4,只有當 ffmpeg 以狀態碼 0 退出時才重新命名——即使渲染中斷,該檔案仍可播放,只是內容會被截斷。 - 除非使用者特別要求,否則以輸出尺寸和 30 fps 繪製影格。當逐影格截取網頁畫面時,儲存為 JPEG(品質約 90),而不是 PNG——寫入和讀取速度快得多。
在 10 張照片 → 1080p 上測量:舊的單一 -loop 1 + xfade 圖表耗時 13.2 秒;slideshow.py --crossfade 耗時 6.4 秒,場景檔案路徑(含音樂)耗時 8.9 秒。
修改完成的影片:
| 變更 | 做法 |
|---|---|
| 修改某個場景的內容 | 重新渲染該場景檔案,再用 -c copy 重新合併 |
| 更換音樂 / 修剪結尾 | -c:v copy(新音樂:-map 0:v -map 1:a) |
| 燒錄字幕、製作較小副本 | 對完成的檔案重新編碼一次 |
| 每格都不同的變更 | 無法避免地需要一次完整渲染 |
4. 食譜
4.0 用單一指令製作圖片幻燈片
python3 scripts/slideshow.py out.mp4 photos/*.jpg --seconds 3 --fade 0.5 --music song.mp3 # 快速,透過黑畫面淡入淡出
python3 scripts/slideshow.py out.mp4 photos/*.jpg --crossfade # 真實交叉淡出
python3 scripts/slideshow.py out.mp4 photos/*.jpg --zoom --size 1080x1920 # Ken Burns 效果,直向
4.1 手動從圖片製作幻燈片(含淡入淡出轉場)
# 每張圖片顯示 3 秒,0.5 秒交叉淡出,縮放/補邊至 1080p
# 每張靜態圖片只解碼一次,並用 loop 濾鏡重複使用(比 -loop 1 快 2 倍)
F="scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1,format=yuv420p,loop=-1:1,trim=duration=3,setpts=PTS-STARTPTS,fps=30"
ffmpeg -framerate 30 -i img1.jpg -framerate 30 -i img2.jpg -framerate 30 -i img3.jpg \
-filter_complex "\
[0]$F[a];[1]$F[b];[2]$F[c];\
[a][b]xfade=transition=fade:duration=0.5:offset=2.5[ab];\
[ab][c]xfade=transition=fade:duration=0.5:offset=5[out]" \
-map "[out]" -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -movflags +faststart slideshow.mp4
片段 n 的偏移量 = (前面所有持續時間的總和)−(n × 淡出持續時間)。
4.2 Ken Burns 效果(在靜態圖片上緩慢縮放)
# d = 輸出影格數(30 fps 下 150 格 = 5 秒);不需要 -loop,zoompan 會產生影格
ffmpeg -i photo.jpg -vf "scale=3840:-2,zoompan=z='min(zoom+0.0008,1.2)':d=150:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=1920x1080:fps=30" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p zoom.mp4
先將圖片放大到 3840 可以避免 zoompan 在小圖片上顯示的抖動現象。
4.3 文字覆疊 / 標題卡片
ffmpeg -i input.mp4 -vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:\
text='我的標題':fontsize=72:fontcolor=white:box=1:[email protected]:boxborderw=20:\
x=(w-text_w)/2:y=h-150:enable='between(t,0,4)'" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a copy titled.mp4
對於包含撇號、表情符號或非拉丁文字(如印地語等)的文字,請使用支援該文字系統的字型,透過 Pillow 將文字渲染成 PNG,然後再疊加它:
ffmpeg -i input.mp4 -i title.png -filter_complex "overlay=(W-w)/2:H-h-100:enable='between(t,0,4)'" ...
4.4 從無到有建立純色標題卡片
ffmpeg -f lavfi -i color=c=0x111827:s=1920x1080:d=3:r=30 \
-vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:text='第一章':fontsize=96:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p card.mp4
4.5 修剪片段
# 精確修剪(重新編碼):從 00:00:05 開始,持續 10 秒
ffmpeg -ss 00:00:05 -i input.mp4 -t 10 -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a aac trimmed.mp4
# 快速修剪(不重新編碼,在最近的關鍵影格處切斷)
ffmpeg -ss 00:00:05 -i input.mp4 -t 10 -c copy trimmed_fast.mp4
4.6 合併片段
相同的編碼/解析度/fps → concat demuxer(不重新編碼):
printf "file 'a.mp4'\nfile 'b.mp4'\nfile 'c.mp4'\n" > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4
不同來源 → 先標準化(scale, fps, audio rate)或使用 concat 濾鏡:
ffmpeg -i a.mp4 -i b.mp4 -filter_complex \
"[0:v]scale=1920:1080,setsar=1,fps=30[v0];[1:v]scale=1920:1080,setsar=1,fps=30[v1];\
[v0][0:a][v1][1:a]concat=n=2:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a aac joined.mp4
(如果某個片段沒有音軌,請加入靜音音軌:-f lavfi -i anullsrc=r=48000:cl=stereo 並使用 -shortest。)
4.7 加入背景音樂
# 影片下的音樂,結尾淡出,在影片結束時停止
# st = 影片長度減去淡出長度(此處為 30 秒影片,3 秒淡出)——使用 ffprobe 取得長度
ffmpeg -i video.mp4 -i music.mp3 -filter_complex \
"[1:a]volume=0.3,afade=t=out:st=27:d=3[m]" \
-map 0:v -map "[m]" -c:v copy -c:a aac -shortest with_music.mp4
# 將音樂混合到現有說話聲之下
ffmpeg -i video.mp4 -i music.mp3 -filter_complex \
"[1:a]volume=0.15[m];[0:a][m]amix=inputs=2:duration=first[a]" \
-map 0:v -map "[a]" -c:v copy -c:a aac mixed.mp4
4.8 字幕 / 隱藏式字幕
撰寫一個 .srt 檔案:
1
00:00:00,000 --> 00:00:03,000
歡迎觀看這部影片。
2
00:00:03,000 --> 00:00:06,500
以下是我們將要涵蓋的內容。
燒錄字幕:
ffmpeg -i input.mp4 -vf "subtitles=subs.srt:force_style='FontSize=24,PrimaryColour=&HFFFFFF,OutlineColour=&H000000,BorderStyle=1,Outline=2'" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a copy captioned.mp4
4.9 為社交媒體調整大小(直向 9:16)
# 使用模糊背景填充,而非黑色邊條
ffmpeg -i landscape.mp4 -filter_complex \
"[0]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=20[bg];\
[0]scale=1080:-2[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a copy vertical.mp4
4.10 加速 / 減速
# 2 倍速(影片 + 音訊)
ffmpeg -i in.mp4 -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]" fast.mp4
4.11 影片轉 GIF(高品質)
ffmpeg -i in.mp4 -vf "fps=15,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 out.gif
4.12 從程式碼渲染影格(動畫、圖表、動態圖形)
當 ffmpeg 濾鏡不夠用時,用 Python 繪製每個影格並將其拼接起來:
from PIL import Image, ImageDraw, ImageFont
import os, subprocess
W, H, FPS, SECONDS = 1920, 1080, 30, 4
os.makedirs("frames", exist_ok=True)
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 96)
for i in range(FPS * SECONDS):
t = i / (FPS * SECONDS)
img = Image.new("RGB", (W, H), (17, 24, 39))
d = ImageDraw.Draw(img)
x = int(-600 + t * (W + 600)) # 文字橫向滑過
d.text((x, H // 2 - 48), "你好!", font=font, fill=(255, 255, 255))
img.save(f"frames/{i:05d}.png")
subprocess.run(["ffmpeg", "-y", "-framerate", str(FPS), "-i", "frames/%05d.png",
"-c:v", "libx264", "-preset", "veryfast", "-crf", "23",
"-pix_fmt", "yuv420p", "-movflags", "+faststart", "animation.mp4"], check=True)
使用緩動函數(例如 t*t*(3-2*t))以獲得更平滑的運動效果。
4.13 MoviePy(快速高階編輯)
from moviepy import VideoFileClip, TextClip, CompositeVideoClip, concatenate_videoclips, AudioFileClip
clip1 = VideoFileClip("a.mp4").subclipped(0, 5)
clip2 = VideoFileClip("b.mp4").subclipped(2, 8)
video = concatenate_videoclips([clip1, clip2], method="compose")
music = AudioFileClip("music.mp3")
video = video.with_audio(music.subclipped(0, min(video.duration, music.duration))) # 音樂可能較短
video.write_videofile("out.mp4", fps=30, codec="libx264", audio_codec="aac", preset="veryfast")
(顯示的是 MoviePy 2.x 的 API;1.x 使用 subclip/set_audio。請用 pip show moviepy 檢查版本。)
5. 交付前驗證
絕不在檢查前交付影片:
# 持續時間、解析度、編碼、fps
ffprobe -v error -show_entries format=duration:stream=codec_name,width,height,r_frame_rate -of default=nw=1 out.mp4
# 在關鍵時刻抽取靜態影格並檢查
ffmpeg -ss 1 -i out.mp4 -frames:v 1 check_1s.png
ffmpeg -i out.mp4 -vf "fps=1/3,scale=480:-1,tile=4x2" -frames:v 1 contact_sheet.png
檢查清單:
- 持續時間與規劃相符
- 解析度和長寬比正確;沒有不想要的黑色邊條或拉伸
- 文字清晰可讀,在安全邊距內(距離邊緣約 5%;對於 9:16,避開底部約 15% 的區域,因為那裡會放置應用程式 UI)
- 轉場正確落在預期位置
- 音訊存在(如果預期有)、沒有爆音、結尾乾淨
- 可在手機上播放:H.264、
yuv420p、偶數尺寸
6. 常見陷阱
| 問題 | 解決方案 |
|---|---|
| 影片無法在 iPhone/QuickTime 上播放 | 加入 -pix_fmt yuv420p |
| "width not divisible by 2" | 使用 scale=1280:-2 而非 -1 |
使用 -c copy 修剪後開頭出現黑影格 | 重新編碼,或在 -i 之前放置 -ss 然後重新編碼 |
| 合併後影音不同步 | 在合併前標準化 fps 和音訊取樣率(-ar 48000) |
| xfade 錯誤 "inputs differ" | 所有輸入必須有相同的尺寸、fps、setsar=1 和像素格式 |
drawtext 在 ' 或 : 處中斷 | 對其進行跳脫處理,或使用 textfile= / Pillow 渲染的 PNG 疊加 |
| 非英文文字顯示為方塊 | 使用支援該文字系統的字型(例如 Noto Sans Devanagari) |
| 檔案大小過大 | 提高 CRF(26–28),降低 fps 或解析度 |
| 渲染速度太慢 | 使用 -preset veryfast 或 ultrafast;在初稿階段降低解析度 |
7. 工作流程摘要
- 規劃 — 長寬比、fps、長度、鏡位清單、音訊。
- 收集素材 — 圖片、片段、音樂、字型;用
ffprobe檢查它們。 - 快速初稿 — 使用低解析度 /
ultrafast預設來檢查時間點。 - 建構最終版本 — 使用標準編碼設定(第 3 節)。
- 驗證 — 使用 ffprobe + 聯絡表單 + 檢查清單(第 5 節)。
- 交付 — 傳送最終檔案,並附上一行摘要(持續時間、解析度、內容說明)。