生成式电影
生成一段 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 或镜像曲线、连续线条肖像或签名。
- 动态文字拼贴,例如按节拍盖章的借词、标语或地名。
- 用本地数字倒计时,然后发射或事件,接着是打击。
- 首尾呼应:结尾将所有章节串在贯穿线索上,如同珠子,然后返回开场图像。
测试每个章节:如果它可以被一张库存照片替代,请重新构思。
调色板。 使用一种纸色、一种夜色和五种以该文化自身材料命名的墨水。SŪTRA 使用了靛蓝、朱红、姜黄、粉色和孔雀绿。其他例子:
- 日本:墨、朱红、蓝靛、金茶、抹茶。
- 墨西哥:胭脂虫红、玉米黄、塔拉韦拉蓝、翡翠绿、万寿菊橙。
- 太空:范塔黑、钠橙、氢-α 红、氧青。
交替使用明亮和黑暗的章节,并让高潮章节在每个小节变换颜色。
字体系统。 使用 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>。
非常规工具。 选择几种:
- 打印样稿平视显示器:套准标记、墨水色样、“图版 03 / 08 · 名称”标签、使用古代数字系统的章节编号、使用本地数字的年份进度,以及带有章节珠子的进度线。
- 主要字体上的 Riso 套印不准(
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") # 根音频率(赫兹);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(阿拉伯音阶)+ 框架鼓 + 类似乌德琴的拨弦(
buzz=0.8);in_sen(日本音阶)+ 类似古筝的拨弦(buzz=0)+ 钟声;dorian(多利亚音阶)+ 持续音 + 干净的拨弦。 - 每个视觉事件都有一个对应的声音。 擦除对应上升音,高潮对应轰隆声加闪光,剪切落在 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 # 平视显示器的前景颜色
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)], # (节拍, 颜色1, 颜色2[, 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 像素的边距。平视显示器的角落和底部区域(y ≈ 960–1040)被保留。字幕放置在 y ≈ 975,从 x ≈ 560 开始,或放在场景的空区域。
- 大字号要足够大:主要字形 300–800 像素。字幕为 18–22 像素等宽字体。
- 对比度。 交替使用纸质和夜色场景。在深色背景上使用
riso(..., dark=True)。 - 在拼贴中使用
fit_size()处理长单词,使用text_circle()处理印章。 - 平视显示器可以显示章节名称、图版编号、通过数字映射的本地数字以及进度线。当技能文件夹中有
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 帧,具体取决于场景复杂度;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;该卡片允许用户播放和下载它。同时提及全质量文件。如果会话与用户的计算机关联,通过device_commit_files提供将name_full.mp4保存到其某个文件夹的选项;或者如果它小于 30 MB,则直接发送。 - 要只重新渲染一个固定的部分,对受影响的帧运行
python3 film.py chunk <f0> <f1> part.mp4,然后按照run()的方式使用 ffmpeg 连接并合成各部分。
致最终用户的消息
提供影片的标题和一行关于其概念的文字,章节为紧凑列表,音乐信息,以及任何近似值(如“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=...)
电影脚本的命令行界面:
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 as np
import cairo
from PIL import Image, ImageFont, ImageDraw
W, H, FPS = 1920, 1080, 30
PI = math.pi
BPM = 96
BEAT = 60 / BPM
def set_tempo(bpm):
global BPM, BEAT
BPM, BEAT = bpm, 60 / bpm
def hx(h):
h = h.lstrip("#")
r
…
[为翻译而截断]