生成式電影
生成一部長度 45–120 秒、解析度 1920×1080、30 fps 的 MP4 影片,其中每一幀均由程式碼繪製(使用 pycairo + Pillow),每一個音符均為合成(使用 numpy/scipy)。無使用任何素材庫、AI 生成影像或授權音樂。參考作品為 SŪTRA(印度文明,87 秒)。風格類似孔版印刷校樣,一條紅線貫穿十個章節,並以 96 BPM 的節拍網格為基礎。
品質來自於以下三點。請遵循全部三點:
- 每個章節有一個點子,而非一張圖片。 每個場景應展現一個機制(例如被行走的城鎮網格、誦經的辮子、生長的碎形、被飛行的軌跡)。請不要只是說明一個名詞。
- 影像和聲音共用一個網格。 所有時序皆以節拍為單位。先創作音樂,然後所有剪輯、彈出和閃光都必須落在音樂事件上。
- 強烈且具體的美術指導,不能是「企業說明」風格:印刷校樣的金屬色、套色不準的油墨、原生書寫系統和數字、打字機字幕以及階梯式擦拭。
0 · 設定(每次工作階段一次)
pip install --break-system-packages pycairo fonttools # numpy, scipy, Pillow 通常已存在
which ffmpeg && python3 -c "from PIL import features; print('raqm', features.check('raqm'))"
Raqm 必須為 True,因為它為天城文、阿拉伯文、坦米爾文等複雜書寫系統提供字形塑形功能。建立一個工作目錄。如果此技能資料夾中有 scripts/,請將 engine.py 和 audio_kit.py 複製到工作目錄。否則,請完全依照附錄 A 和 B 的內容輸出它們。這些檔案已測試過,請勿改寫。字型會在首次使用時從 google/fonts GitHub 儲存庫下載,沙盒環境允許此操作。
建立任務清單:概念 → 音樂 → 場景 → 測試影格 → 渲染 → 品質檢查 → 交付。
1 · 概念(在編寫任何程式碼之前)
研究。 在網路上搜尋所有將出現在螢幕上的日期、數字、名稱和聲明。在不肯定的日期前加上「c.」。將圖表標示為「示意圖」或「未按比例繪製」。保持事實精簡且精確,字幕不超過 12 個字。
貫穿物。 找到一個在每個章節中都出現的視覺元素,並以此為影片命名。在 SŪTRA 中,它是一條紅線:走過街道、變成聲波、畫出 kolam、然後飛過月球軌跡。其他範例:
- 海洋史:一個單一的浪峰。
- 咖啡:一滴。
- 運算:一個位元。
- 公司:其產品的核心手勢。
片名。 選擇一個單詞,理想情況下是主題本身的語言,並在螢幕上顯示字典風格的註解:sūtra (n.) 1. 線 2. 規則 3. 將事物連接在一起的東西。
章節。 使用 6–10 個章節,每個章節圍繞一個不同的視覺點子建構。可參考以下題材:
- 自我構建的圖表:城鎮網格、路線圖、軌道或文字的家譜樹。
- 作為藝術的數字:帕斯卡/謝爾賓斯基三角形、螺旋擴展的圓周率、二進位計數器或呈倍增序列的物體。
- 遞迴或碎形:神廟中的神廟,或樹中的樹。
- 實際配樂的示波器(
wave_at(f)),非常適合關於語音或音樂的章節。 - 一條不間斷的線條畫:kolam 或鏡像曲線、連續線條肖像或簽名。
- 動態文字拼貼,例如借詞、口號或地名,在節拍上蓋印。
- 以原生數字倒數,然後發射或事件,然後命中。
- 書擋:結尾將所有章節串在貫穿線上,然後回到開場影像。
測試每個章節:如果它可以用一張庫存照片取代,請重新思考。
色票。 使用一張紙色、一個夜色和 5 種以該文化自身材料命名的油墨。SŪTRA 使用了 indigo、sindoor、haldi、rani 和 mor(孔雀)。其他範例:
- 日本:sumi、shu 朱紅、ai 靛藍、kincha 金、matcha。
- 墨西哥:cochineal、maize、talavera 藍、jade、marigold。
- 太空:vantablack、sodium 橙、hydrogen-alpha 紅、oxygen 青。
交替使用淺色和深色章節,並讓高潮章節在每個小節變換色票。
字體系統。 使用 4–5 個家族。選擇一個緊湊的展示字體作為主要文字、主題的原生書寫系統字體、一個等寬字體用於字幕和標籤,以及一個柔軟的斜體襯線字體用於詩句。使用原生數字作為運行年份或計數器。有用的 google/fonts 路徑(get_font(name, path, axes)):
| 角色 | 路徑 | 軸參數 |
|---|---|---|
| 展示 | ofl/anton/Anton-Regular.ttf | – |
| 等寬 | ofl/spacemono/SpaceMono-Regular.ttf, …-Bold.ttf | – |
| 斜體襯線 | ofl/fraunces/Fraunces-Italic[SOFT,WONK,opsz,wght].ttf | [144,380,100,1] |
| 粗襯線 | ofl/fraunces/Fraunces[SOFT,WONK,opsz,wght].ttf | [144,900,100,1] |
| 天城文 | ofl/tirodevanagarisanskrit/TiroDevanagariSanskrit-Regular.ttf | – |
| 阿拉伯文 | ofl/notonaskharabic/NotoNaskhArabic[wght].ttf | [700] |
| 坦米爾文/孟加拉文/泰文 | ofl/notosanstamil/NotoSansTamil[wdth,wght].ttf(相同模式) | [700,100](wght, wdth 順序遵循字型定義) |
| 中日韓 | ofl/notoserifjp/NotoSerifJP[wght].ttf | [700] |
| 婆羅米文 | ofl/notosansbrahmi/NotoSansBrahmi-Regular.ttf | – |
使用 font_axes(name) 檢查軸參數順序。如果路徑回傳 404,請使用 WebFetch 瀏覽 https://github.com/google/fonts/tree/main/ofl/<family>。
非傳統裝置。 選擇數個:
- 印刷校樣 HUD:套色標記、油墨色票、「PLATE 03 / 08 · NAME」標籤、以古數字系統顯示的章節編號、以原生數字顯示的運行年份,以及帶有章節珠子的進度線。
- 主要文字上的套色不準(
riso())。 - 網點填充。
- 垂直巨型文字。
- 圓形排列的文字(印章或封條)。
- 打字機字幕。
- 以兩種油墨執行的階梯式或虹膜擦拭。
- 在接縫處的故障顫動,並與音訊顫動匹配。
- 高潮處的白色閃光。
- 影片顆粒感。
絕對不要:
- 置中淡入的標題卡。
- 作為裝飾的漸層、光暈或鏡頭眩光。
- 表情符號或剪貼畫。
- 受版權保護的角色、商標或真人人臉的繪圖。
- 作為裝飾的國旗。
- 刻板印象圖像。
2 · 以節拍為單位的時間線
使用 90–110 BPM。一個章節為 8–16 個節拍。96 BPM × 136 個節拍 ≈ 87 秒。首先撰寫一個表格,如同 SŪTRA 的範例:
| 節拍 | 章節 | 畫面 | 音樂 |
|---|---|---|---|
| 0–12 | 開場 | 一個點脈動、伸展成一條線、片名文字逐拍彈出 | 持續低音膨脹,每個文字伴隨鐘聲,漸升 |
| 12–28 | 1 | 城鎮網格組裝;線條走過街道 | 手鼓節奏 + 撥奏旋律 |
| 28–40 | 2(呼吸) | 音訊的示波器 + 文字辮子 | 持續低音、吟唱、稀疏 |
| 40–52 | 3 | 旋轉的輪子、文字蓋印、譜系樹 | 更活躍的鼓、漸升、在 51.5 處顫動 |
| 52–64 | 4 | 星軌加速、數字螺旋 | 襯墊 + 鐘聲琶音、大漸升、閃光 |
| 64–80 | 高潮 | 巨大的主要字符、碎形每拍生長 4 行、每小節變換色票 | 大鼓、拍手、低音、主旋律、重擊 |
| 80–92 | 5 | 遞迴結構依深度生長 | 半速;tihai 落在 92 = 剪輯 |
| 92–108 | 6 | 一筆畫,然後文字拼貼 | 柔和節奏,然後完整節奏 |
| 108–120 | 終曲 | 原生數字倒數 → 軌跡 → 著陸重擊 | 鐘聲、轟鳴、漸升、重擊 |
| 120–136 | 結尾 | 線上的珠子、片名返回、點 | 吟唱 + 襯墊 + 旋律、淡出 |
能量曲線為:安靜開場 → 構建 → 呼吸 → 構建 → 在最令人驚訝的點子上達到高潮 → 半速 → 終曲重擊 → 安靜書擋。
3 · 音樂先行 — audio.py
from audio_kit import *
T = Track(bpm=96, beats=136, root=138.59, scale="raga_bhairavi") # root Hz; SCALES 包含多種文化
T.drone_bed(0, 132, level=lambda b: 0.16 if 12 <= b < 120 else 0.3)
T.hand_drum(12, 28) # keherwa 風格的預設模式
T.melody(12, [(2, 4, 1), (3, 5, 1.2, 6), (4, 7, 2.2)]) # (節拍, 音階度數, 持續時間, 滑音目標度數)
T.riser_into(64, 4); T.at("fx", boom(), 64, 0.55) # 進入高潮
T.groove(64, 80); T.stutter(51.5)
T.tihai(92) # 3× 短語精確落在節拍 92
T.mixdown("music.wav", fade_beats=(133, 136)) # 也會寫入 env.npy / wave.npy
- 匯流排為 drone、perc、kick、bass、mel、fx 和 pad。使用
T.at(bus, signal, beat, gain, pan)處理任何一次性音效。 - 樂器:
drone, pluck (buzz=0 為乾淨音色), bell, voice, pad, sub, kick, clap, hat, drum_tone, drum_bass, drum_slap, drum_muted, riser, boom, rumble。 - 根據文化匹配音階和樂器。例如:hijaz + frame drum + oud 風格的撥奏(
buzz=0.8);in_sen + koto 風格的撥奏(buzz=0)+ 鐘聲;dorian + drone + 乾淨撥奏。 - 每個視覺事件都有一個聲音。 擦拭伴隨漸升、高潮有重擊加上閃光、剪輯落在 tihai 或大鼓重擊上、文字彈出伴隨鐘聲。
- 檢查結果:
- 響度:
ffmpeg -i music.wav -af ebur128=framelog=quiet -f null -應顯示約 −16 到 −14 LUFS。 - 頻譜圖:
ffmpeg -i music.wav -lavfi showspectrumpic=s=1600x500:scale=log:fscale=log spec.png,然後讀取圖片並檢查章節形狀。
- 響度:
4 · 場景 — film.py
from engine import *
PAPER, NIGHT, INK = hx("F1E6CF"), hx("0A0D26"), hx("14121C")
INK1, INK2, INK3 = hx("1B2A6B"), hx("EE3B1E"), hx("FFB21A")
get_font("anton", "ofl/anton/Anton-Regular.ttf")
get_font("mono", "ofl/spacemono/SpaceMono-Regular.ttf")
# 在模組層級使用帶種子的亂數產生器預先計算幾何圖形(確保跨工作進程的確定性)
def s_intro(ctx, b, f): # b = 節拍(浮點數), f = 影格
bg(ctx, NIGHT)
riso(ctx, lambda c: text(ctx, "TITLE", "anton", 400, c, W/2, 700, ax=.5, sc=back(ph(b, 4, .5))),
PAPER, INK2, dark=True)
return PAPER # HUD 的前景色
def hud(ctx, b, fg): reg_marks(ctx, fg, (INK1, INK2, INK3))
SCENES = [(0, s_intro), (12, s_ch1)] # (起始節拍, 函數)
run(SCENES, 136, bpm=96, name="myfilm", hud=hud,
wipes=[(12, INK2, INK3)], # (節拍, c1, c2[, iris_wipe])
glitches=[(51.5, 52.15)], flashes=[(64, WHITE, .5)], fade_out=134.6)
場景規則:
- 無狀態。 每個值都是
b的函數,因此影格可以跨處理程序以任意順序渲染。 - 入場 使用
back(ph(b, start, 0.4))實現彈出、eo()實現滑入、eio()實現鏡頭移動和線條繪製。項目之間間隔 0.2–1 個節拍。 - 節奏生活 來自
pulse(b)(每個節拍從 1→0)用於彈跳和環狀大小、env_at(f)用於響度、wave_at(f)用於示波器。 - 即時繪製線條。 使用
path_partial(ctx, pts, cumlen(pts), frac),並在返回的尖端放置一個發光珠子。 - 構圖。
- 每個場景保持一個主要元素、一個輔助圖表和一個字幕。
- 保持 110 px 的邊距。HUD 角落和底部帶狀區域(y ≈ 960–1040)保留。字幕放置在 y ≈ 975,從 x ≈ 560 開始,或在場景的空白區域。
- 大字要大:主要字符 300–800 px。字幕為 18–22 px 等寬字體。
- 對比。 交替使用淺色和深色場景。在深色背景上使用
riso(..., dark=True)。 - 對拼貼中的長詞使用
fit_size(),對印章使用text_circle()。 - HUD 可以顯示章節名稱、版號、透過數字對應表顯示的原生數字,以及進度線。當技能資料夾中有
examples/sutra/film.py時,其hud()和場景是完整的參考;其examples/sutra/contact_sheet.png顯示了最終外觀。
5 · 測試影格 → 聯絡表 → 修正(請勿跳過)
python3 film.py test 2 6 10 17 25 34 45 58 66 75 86 96 104 110 114 118 125 134
讀取 sheet.png 並檢查:
- 重疊或裁剪的文字。
- 方塊字或缺少的字形。對每個字型 × 字串組合執行
missing_glyphs(font, string)。例如,Fraunces 缺少 → π ∞ ṅ,Tiro Devanagari 缺少 → ≈ π。 - 在縮圖大小下無法閱讀的字幕。
- 章節開頭的空白影格。
- 錯誤的事實或打字錯誤。
- 什麼都沒移動的節拍。
迭代直到每個影格都可以當作海報。通常需要 2–3 輪。
6 · 渲染、品質檢查、交付
nohup python3 film.py render > render.log 2>&1 &,然後使用 Bashsleep迴圈輪詢(每次超時 ≤ 590 秒)。它會跨所有核心並行渲染。根據場景複雜度,總體速度約為 2–10 fps;SŪTRA 在 2 核心上耗時約 20 分鐘。- 輸出為
name_full.mp4(CRF 18,完整品質)和name.mp4,一個二次編碼的重新壓縮檔,保持在 29 MB 以下,以符合 30 MB 的聊天上傳限制。 - 品質檢查:
ffprobe -count_frames以確認影格數量和時長。ffmpeg -i name_full.mp4 -vf "fps=1/3.5,scale=384:216,tile=5x5" -frames:v 1 final.png,然後讀取它。- 抽查 2–3 個全尺寸影格,包括壓縮後的一個。
- 下載: 使用 SendUserFile(
display: "render")傳送name.mp4;卡片讓使用者可以播放和下載。同時提及完整品質的檔案。如果工作階段連結到使用者的電腦,提供將name_full.mp4儲存到其某個資料夾的選項,或如果檔案小於 30 MB 則直接傳送。 - 若要僅重新渲染某個修正過的片段,請執行
python3 film.py chunk <f0> <f1> part.mp4處理受影響的影格,然後使用 ffmpeg 將片段連接和混流,如同run()所做的那樣。
最終訊息給使用者
提供影片的片名和一行概念、章節的簡潔列表、音樂的說明,以及任何近似值(「c.」日期、示意圖)。請勿重述步驟。
附錄 A — engine.py
"""engine.py — 生成式印刷風格影片工具包 (pycairo + Pillow/raqm + ffmpeg)。
影片腳本執行: from engine import *
然後將場景定義為函數 scene(ctx, b, f) -> fg_color (b = 節拍, f = 影格)
並在底部呼叫 run(SCENES, TOTAL_BEATS, bpm=..., wipes=..., hud=...)
影片腳本的 CLI:
python film.py test 4 12.5 30 # 在這些節拍處渲染 PNG + 聯絡表 (sheet.png)
python film.py render # 完整並行渲染 -> film_full.mp4 + film.mp4 (<29 MB)
"""
import math, os, sys, subprocess, shutil
import numpy
…
[為翻譯而截斷]