95 秒宣传片:每一帧都是程序画的
《最后的狼踪》的 95 秒宣传片,不是把截图拖进剪辑软件再配字幕:画面由 Python 用 Pillow 逐帧合成,2850 帧渲染完再交给 ffmpeg 编码。这篇写它怎么搭起来,也写踩过的坑。
一、为什么不录屏,改成画
片子要在 95 秒里讲清一个兵棋:棋盘、规则、人机流程、自动化脚本。录屏当然更快,但窗口边框、鼠标轨迹、动画节奏、分辨率都不可控,后期还得遮掉桌面和任务栏;而程序画的每一帧,版式、颜色、字号都由我定。
更实际的限制是:沙箱里起不了 Chromium(原因写在踩坑里),想加新素材只能靠已有截图或人工录屏。既然素材受限,不如把画面也一起接管过来。
于是先定一条视觉底子:底色渐变加六边形点阵(呼应 16×28 的战场),中心亮、边缘暗,再叠胶片颗粒、扫描线和暗角。截图、文字、光效都往这条底子上放,95 秒看起来才像一部片子,而不是十五张幻灯片。
二、时间轴与分镜:95 秒拆成 15 段
分镜表是整部片的骨架。95 秒被切成 15 段,每段有确定的起止秒数;渲染时按帧号判断当前落在哪一段(第 n 帧对应 n ÷ 30 秒),再画那一段的内容。下面是压缩过的分镜表:
| 时间(秒) | 段落与画面 |
|---|---|
| 0.0–1.4 | 黑场:一道琥珀光横扫 |
| 1.4–5.2 | 狼眼:黑暗中浮起一对发光的眼,缓慢呼吸 |
| 5.2–8.8 | 主标题:「最后的狼踪」砸入,冲击闪白,光条展开 |
| 8.8–12.6 | 规格:不对称战术兵棋 / 16×28 / 12 回合 |
| 12.6–19.2 | 对峙:左「狼群」右「牧民」逐条对位 |
| 19.2–25.2 | 战场:六边形棋盘截图 + HUD 角标 |
| 25.2–31.4 | 攻击预演:战果列截图 |
| 31.4–36.6 | 平台:AI 对战平台界面 |
| 36.6–44.8 | 对局实录:8 秒实机片段,配三条战况标注 |
| 44.8–53.0 | 人机对战流程:选模式 → 挑边 → 四步部署 |
| 53.0–62.0 | 操作便利:行动浮卡特写 + 五条键位 |
| 62.0–71.0 | 回放:点算子看属性 |
| 71.0–80.0 | 剧本自定义:剧本编译器 + 三条说明 |
| 80.0–89.0 | 自动化脚本:终端面板 + 四个参数 |
| 89.0–95.0 | 收尾:标题回落 + ONE MAP. TWO SIDES. NO SECOND CHANCE. |
真正写起来,难点不在段落里画什么,而在段与段怎么接。标题是砸入,狼眼是慢呼吸,终端参数是逐条揭示;这些动作都要跨帧算进度,不能只画静止一帧。15 段里,前面交代它是什么,后面演示怎么玩。
全片唯一一段实拍素材是 36.6–44.8 秒的对局实录:从对局录像里抽 8 秒,裁掉浏览器和任务栏,只留棋盘,再按暗色调一遍。这一段的任务是证明「它真的能下」,其余画面交给程序画。
三、Pillow 逐帧合成:一帧就是一张画布
渲染脚本是 film.py。它的循环很朴素:对每一帧,先按帧号算出时间,查时间表得到当前段落和段落内进度,然后在 1920×1080 的画布上从底色、点阵、截图、文字、光效一层层往上叠,最后存成 JPG。每一次修改都不是重录一段,而是改一行绘制参数再重渲染。
字体有明确分工:simhei 给标题,msyhbd 给小标题,msyh 给正文(包括中文终端),consola 只给纯英文小字。这个分工后来救了一次命——Consolas 一遇到中文就是方块。
渲染前先跑 --preview 抽几张帧看构图:字幕有没有压到边、截图和底子脱不脱节;确认没问题再 --all 全量出 2850 帧。先预览省掉的返工,比多看几遍代码管用。
四、截图调色:按明暗自适应
片里不少画面来自兵棋已有的界面截图,亮度不统一:有的偏亮,有的深色面板几乎全黑。统一加亮或压暗都不行,只能按明暗自适应——亮图压暗、降饱和,暗图提亮。否则暗色界面会被压死,糊成一块。
实机片段单独处理:先用 ffmpeg 从对局录像里抽 8 秒,crop 掉浏览器和任务栏,只留 830×775 的棋盘区域,再调亮度和色相,让它跟程序画的底色贴近。
ffmpeg -ss 26 -t 8 -i 对局片段.mp4 -vf "crop=830:775:400:150,eq=brightness=-0.10:saturation=0.72:contrast=1.10,colorbalance=bs=0.07:bm=0.05" -r 30 game/f%04d.png
五、ffmpeg 编码:2850 帧变成两个文件
编码分两步。主片先出:按 30 fps 读入 frames 里的连续 JPG,先做 hqdn3d 降噪,再交给 libx264(preset slow、crf 23.8、yuv420p、+faststart),得到 1920×1080 的版本。
ffmpeg -framerate 30 -i frames/f%05d.jpg -vf "hqdn3d=2:2:4:4" -c:v libx264 -preset slow -crf 23.8 -pix_fmt yuv420p -movflags +faststart 最后的狼踪_宣传片.mp4
网页版从主片再压一次:lanczos 缩到 1280×720、crf 21,体积 19.7 MB,嵌进网页不心疼流量。主片 93.2 MB,刚好在 GitHub Pages 单文件 100 MB 以内;网页版对 Cloudflare Pages 的 25 MB 限制也够。
六、踩过的坑
- 颗粒是码率杀手。crf 18 那一版有 649 MB;改成 crf 23.8 加 hqdn3d,降到 93 MB,肉眼几乎看不出差别。
- 慢速推拉别用 zoompan。它按整数像素裁切,每帧挪不到 1 px 就变成逐帧抖动,上一版就栽在这里;后来改用淡入淡出和逐条揭示。
- 中文不能进 Consolas。终端面板里的中文会全变成方块,必须换 CJK 字体。
- 沙箱里起不了浏览器。Chromium 内部 IPC 需要命名管道,报 Mojo platform_channel Check failed 拒绝访问,加 --single-process 也一样。所以要加新素材,只能靠已有截图或人工录屏。
七、成片规格与怎么重建
最后交付两份文件:
| 项目 | 规格 |
|---|---|
| 时长 | 95 秒 |
| 画面 | 1920×1080 / 30 fps / 2850 帧 |
| 音轨 | 无 |
| 主片 | 1920×1080 · 93.2 MB |
| 网页版 | 1280×720 · 19.7 MB |
| 编码 | H.264(libx264)· yuv420p · +faststart |
重建就三步:先 --preview 抽帧确认,再 --all 渲染 2850 帧,最后按上面的两条 ffmpeg 命令编码;实机素材要从对局录像里重新抽一次。路径和字体都在这台机器上,换一台机器得重新对齐。
95 秒不长,但它把兵棋从「一堆界面」变成了「一段能给人看的东西」。程序画最大的好处不是炫技,是可复现:改一个参数,重跑,整片一起变。