電影節拍影片風格
核心哲學
視覺主導時序,音訊跟隨。 動畫和 ASCII 場景以其自然運行時長播放,不會為了配合音訊長度而被截斷、加速或停滯在最終畫面上。節拍之間的空隙是刻意設計,而非需要填補的空白。
- TTS: 每個鏡頭一句話,追求最高信噪比,沒有填充式旁白。
- 視覺: 完整的自然運行時長,讓動畫自由呼吸。
- 剪輯: 僅使用硬切,無淡入淡出。
- 音樂: 使用者提供音軌,在語音段落自動進行閃避。
- 檢查點: 每個製作階段完成後,將素材交付使用者審查,再繼續下一階段。
專案結構
~/videos/[project-name]/
project.md # 動態文件 — 分鏡腳本、決策、修改記錄
beats/ # 個別節拍素材
beat-01/
visual.mp4 # 生成的視覺素材(含動態的圖片或動畫)
audio.mp3 # 此節拍的 TTS 音檔 (僅限 MP3 — 請參閱關鍵規則)
note.txt # 此節拍內容的一行說明
beat-02/
...
music/ # 使用者提供的背景音樂曲目
output/ # 中間及最終渲染檔
第一階段:節拍腳本
建立 project.md,包含節拍明細。每個節拍 = 一個視覺 + 一句 TTS。
撰寫節拍
TTS 規則:
- 每個鏡頭一句話,句號。
- 最高信噪比 — 每個字都有其存在的價值。
- 如果一個想法需要兩句話,那就分成兩個節拍。
- 目標:每段音檔 5-15 個單詞。越短越好。
- 不要有開場填充詞(「那麼…」、「現在讓我們來看…」)。直接切入重點。
視覺規則:
- 描述視覺畫面呈現什麼 — 而非持續多久。
- 視覺的運行時長由其自然完成決定,而非 TTS 長度。
- 圖片剪輯:靜態圖片搭配 Ken Burns 動態(緩慢縮放/平移)。運行時長:至少 4-6 秒。
- 動畫剪輯 (p5js/manim/ascii):完整播放動畫。不論需要多久 — 8 秒、12 秒、15 秒都可以。
- 節拍持續時間 = max(視覺持續時間, 音訊持續時間) + 暫停。
project.md 中的節拍模板:
# [專案名稱]
## 概念
[一段話 — 說明這部影片的主題與想傳達的感受]
## 音樂
- 曲目:[使用者將提供的音樂檔案名稱或描述]
- 氛圍:[描述]
## 節拍
### 節拍 01
**視覺:** [類型: image | p5js | manim | ascii] — [提示/描述]
**TTS:** "一句簡潔有力的台詞。"
**狀態:** [ ] visual [ ] audio [ ] approved
### 節拍 02
...
## 備註
[決策、修改、使用者回饋的運行記錄]
剪輯類型
| 類型 | 工具 | 運行時長 | 最適合用於 |
|---|---|---|---|
image | image_generate + Ken Burns | 4-6 秒 | 照片、場景、人像、定場鏡頭 |
p5js | p5js 技能 | 自然時長 (8-15 秒) | 生成式藝術、粒子、資料視覺化、流場 |
manim | manim-video 技能 | 自然時長 (8-20 秒) | 數學動畫、圖表、技術講解 |
ascii | ascii-video 技能 | 自然時長 (6-12 秒) | 終端機美學、復古運算、駭客主題 |
caption | ffmpeg drawtext | 4-8 秒 | 深色背景上置中顯示 TTS 文字 — 將焦點拉至文字本身 |
關鍵:在製作任何 p5js/manim/ascii 節拍之前,請先使用 skill_view(name) 載入技能。 這些技能包含品牌色板、字體規則和實作模式,必須嚴格遵守。
第二階段:腳本批准關卡
暫停。將 project.md 呈現給使用者。在獲得明確批准之前,請勿生成素材。
在 project.md 的備註部分更新批准時間戳記。
第三階段:生成視覺素材
根據類型生成所有視覺素材。儲存為 beats/beat-XX/visual.mp4。
圖片節拍 (VHS 濾鏡 Ken Burns)
- 透過
image_generate生成圖片 - 對圖片套用 VHS 濾鏡(顆粒、掃描線、暗角、色差):
python3 scripts/vhs-filter.py \
--input beat-XX-image.png \
--output beat-XX-vhs.png \
--seed 42 \
--intensity 1.0
需求: pip install numpy Pillow (numpy 用於陣列效果,Pillow 用於圖片輸入輸出)。
強度等級:0.7 (輕微 — 僅顆粒 + 暗角),1.0 (標準 — 所有效果),1.5 (強烈 — 強色差、明顯掃描線)。預設值為 1.0。
- 對套用 VHS 濾鏡的圖片應用 Ken Burns 動態以產生 MP4:
ffmpeg -loop 1 -i beat-XX-vhs.png -t 5 \
-vf "zoompan=z='min(zoom+0.001,1.3)':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=125:s=1920x1080:fps=25" \
-c:v libx264 -pix_fmt yuv420p -r 25 beats/beat-XX/visual.mp4
為什麼先處理 VHS,然後才做 Ken Burns? zoompan 濾鏡會模糊顆粒和掃描線。先在靜態圖片上套用 VHS,然後再加上動態。在經過 VHS 濾鏡處理的圖片上疊加 Ken Burns 動態,能營造出一種生活化的類比感 — 就像透過有點老舊的 CRT 觀看舊影片。
動畫節拍 (p5js/manim/ascii)
先載入相關技能,再進行生成。將輸出儲存為 beats/beat-XX/visual.mp4。
解析度: 所有視覺素材必須為 1920x1080,25fps。調整 manim 輸出尺寸:
ffmpeg -y -i raw-manim.mp4 \
-vf "scale=1920:1080:flags=lanczos,setsar=1:1,fps=25" \
-c:v libx264 -pix_fmt yuv420p -r 25 beats/beat-XX/visual.mp4
批次處理: 每 6 個一組進行 image_generate 呼叫。如果被標記,則中性改寫提示詞並重試一次。
VHS 濾鏡參考
VHS 濾鏡 (scripts/vhs-filter.py) 對單張圖片套用以下效果:
| 效果 | 作用 | 視覺結果 |
|---|---|---|
| 色差 | R/B 通道偏移 (0-2px) | 邊緣出現色彩條紋,像未對準的 CRT |
| 類比噪訊 | 每通道 ±8 階顆粒 (按強度縮放) | 陰影與中間調呈現底片質感 |
| 掃描線 | 每第四行調暗至 96% | CRT 磷光柵格疊層 |
| CRT 暗角 | 邊緣變暗 (依強度 15-35%) | 圓形螢幕角落,像老電視 |
對於圖片: 所有效果以單一隨機種子套用一次。色差和噪訊是可見的;抖動/追蹤線條因為只有單格而較為細微。
注意: 此濾鏡僅適用於圖片 — 不處理影片輸入。要產生動態,請先對靜態畫面套用濾鏡,然後再加上 Ken Burns/zoompan。
第三點五階段:視覺檢查關卡
暫停。將每個視覺片段傳送給使用者審查。
使用 MEDIA: 路徑包含螢幕截圖或 MP4。依照編號呈現:
節拍 01: [MEDIA 路徑] — [簡短描述]
節拍 02: [MEDIA 路徑] — [簡短描述]
...
詢問:「在我們繼續處理音訊之前,有任何需要修改的地方嗎?」
在使用者批准視覺素材之前,請勿生成 TTS。 將回饋記錄在 project.md 的備註中。
如果需要修改,僅重新生成受影響的節拍並重新進行檢查。
第四階段:生成 TTS 音訊
生成所有 TTS 音檔 — 每個節拍一個。使用 text_to_speech 工具,輸出路徑為 .mp3。儲存為 beats/beat-XX/audio.mp3。
關鍵:音訊格式始終使用 MP3。 OGG 在 Telegram 傳遞和 ffmpeg 串接操作中存在編解碼器相容性問題。將 TTS 提供者設定為 OpenAI 並以 MP3 輸出。
規則:
- 每次呼叫一句話。不要串接。
- 生成後使用 ffprobe 測量每個音檔的持續時間。
- 在 project.md 中每個節拍旁記錄持續時間。
ffprobe -v error -show_entries format=duration -of csv=p=0 beats/beat-XX/audio.mp3
第四點五階段:音訊檢查關卡
暫停。將音訊片段呈現給使用者。
套用語音濾鏡 (必做)
對每個 TTS 片段套用乾/濕位元粉碎鏈 — 這是我們的標誌性語音。完整技術與陷阱請參閱 references/dry-wet-bitcrush.md。
提供兩種方法:
A) 靜態混音 (簡單、一致):
# 步驟 1:透過 wav 中繼檔建立粉碎版本 (保持時長)
ffmpeg -y -i beats/beat-XX/audio.mp3 \
-af "aresample=resampler=soxr:osr=2000,aformat=sample_fmts=u8,aresample=resampler=soxr,lowpass=f=2500" \
temp-crushed.wav
# 步驟 2:混合乾 + 濕訊號,並加上壓縮膠合
ffmpeg -y -i beats/beat-XX/audio.mp3 -i temp-crushed.wav \
-filter_complex "[0:a]volume=0.7[dry];[1:a]volume=0.3[wet];[dry][wet]amix=inputs=2:duration=first:normalize=0,acompressor=threshold=0.05:ratio=4:attack=10:release=100[out]" \
-map "[out]" beats/beat-XX/audio-filtered.mp3
rm temp-crushed.wav
B) 動態混音 (訊號退化效果 — 建議使用):
使用 Python 腳本 (scripts/vibey-mix.py),透過零階保持升取樣實現真實位元粉碎,音高正確。
需求: pip install numpy pydub (numpy 用於陣列處理,pydub 用於 MP3 讀寫)。
鎖定參數 (dec5-bit8 變體):
- 抽取: 5 倍 (丟棄每 5 個樣本,然後透過零階保持重複)
- 量化: 8 位元 (固定)
- 乾/濕基線: 80% 乾 / 20% 濕
- 衰減模式: 大約每 1.2 秒,乾訊號降至約 35%,濕訊號升至約 65% (持續時間約 0.25 秒)
- 僅作用於濕訊號的顫音: 4Hz 三角波,深度 70% (濕訊號在 30-100% 之間脈動)
- 拍擊延遲: 80ms,-18dB (約 0.12 音量),套用於最終混音
python3 scripts/vibey-mix.py beats/beat-XX/audio.mp3
cp beats/beat-XX/audio-dynamic.mp3 beats/beat-XX/audio-filtered.mp3
鏈路分解: 乾淨的乾訊號與極端的數位偽影 (5 倍抽取、8 位元量化經零階保持升取樣) 分層混合,週期性的衰減會提高濕訊號電平以產生「傳輸中退化」效果,濕訊號上的顫音提供節奏性脈動,拍擊延遲增加空間感。保持鏈路結構完整 — 僅在必要時調整個別參數。
在所有後續階段使用 audio-filtered.mp3。保留原始 audio.mp3 以供參考。
將決策記錄在 project.md 的備註中。
第五階段:合成節拍
對於每個節拍,將視覺與音訊以硬切 (無轉場) 合併。節拍持續時間由視覺和音訊中較長者決定。
如果視覺 >= 音訊:
音訊在整個視覺期間播放。無需填充。
ffmpeg -i beats/beat-XX/visual.mp4 -i beats/beat-XX/audio-filtered.mp3 \
-c:v libx264 -pix_fmt yuv420p -c:a aac \
-shortest -r 25 output/beat-XX-composed.mp4
如果音訊 > 視覺:
視覺保持其最終畫面以填滿剩餘時間。對影片使用 tpad:
AUDIO_DUR=$(ffprobe -v error -show_entries format=duration -of csv=p=0 beats/beat-XX/audio-filtered.mp3)
ffmpeg -i beats/beat-XX/visual.mp4 -i beats/beat-XX/audio-filtered.mp3 \
-vf "tpad=stop_mode=clone:stop_duration=$AUDIO_DUR" \
-c:v libx264 -pix_fmt yuv420p -c:a aac \
-t $AUDIO_DUR -r 25 output/beat-XX-composed.mp4
如果未套用音訊濾鏡,則使用 audio.mp3 代替 audio-filtered.mp3。
節拍之間暫停