影像 复盘

95 秒宣传片:每一帧都是程序画的

《最后的狼踪》的 95 秒宣传片,不是把截图拖进剪辑软件再配字幕:画面由 Python 用 Pillow 逐帧合成,2850 帧渲染完再交给 ffmpeg 编码。这篇写它怎么搭起来,也写踩过的坑。

成片网页版:1280×720 / 19.7 MB / 无音轨;完整版为 1920×1080。

一、为什么不录屏,改成画

片子要在 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 段里,前面交代它是什么,后面演示怎么玩。

宣传片对局实录段落的截帧:左右两栏夹着回放棋盘,右侧有三条战况标注
对局实录段落截帧:8 秒实机画面压在左右两栏之间,左侧是 REPLAY 与种子号,右侧是三条战况标注。

全片唯一一段实拍素材是 36.6–44.8 秒的对局实录:从对局录像里抽 8 秒,裁掉浏览器和任务栏,只留棋盘,再按暗色调一遍。这一段的任务是证明「它真的能下」,其余画面交给程序画。

三、Pillow 逐帧合成:一帧就是一张画布

渲染脚本是 film.py。它的循环很朴素:对每一帧,先按帧号算出时间,查时间表得到当前段落和段落内进度,然后在 1920×1080 的画布上从底色、点阵、截图、文字、光效一层层往上叠,最后存成 JPG。每一次修改都不是重录一段,而是改一行绘制参数再重渲染。

字体有明确分工:simhei 给标题,msyhbd 给小标题,msyh 给正文(包括中文终端),consola 只给纯英文小字。这个分工后来救了一次命——Consolas 一遇到中文就是方块。

渲染前先跑 --preview 抽几张帧看构图:字幕有没有压到边、截图和底子脱不脱节;确认没问题再 --all 全量出 2850 帧。先预览省掉的返工,比多看几遍代码管用。

宣传片主标题画面:黑底上写着「最后的狼踪」,下方是 A DARK TACTICAL WARGAME
主标题段落:六边形点阵压在黑底上,标题砸入后接冲击闪白与光条展开。

四、截图调色:按明暗自适应

片里不少画面来自兵棋已有的界面截图,亮度不统一:有的偏亮,有的深色面板几乎全黑。统一加亮或压暗都不行,只能按明暗自适应——亮图压暗、降饱和,暗图提亮。否则暗色界面会被压死,糊成一块。

实机片段单独处理:先用 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
宣传片战场段落的截帧:六边形棋盘界面配右侧信息面板,画面下方写着「六边形战场」
战场段落:界面截图先压暗降饱和,再被 HUD 角标和暗角收进画面。

五、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 秒不长,但它把兵棋从「一堆界面」变成了「一段能给人看的东西」。程序画最大的好处不是炫技,是可复现:改一个参数,重跑,整片一起变。

Pillow ffmpeg 程序化影像