视频叙事技巧
如何构建长视频的视觉层,使得观众在第九分钟依然保持方向感。适用于任何有脚本的说话人头像视频:新闻观点、单一主题深度解析、对比、教程、列表清单。
本技能旨在取代的失败模式是“场景思维”。每隔几秒出现新图形会迫使观众每隔几秒重新开始理解,而没有任何单个图形是罪魁祸首——这就是为什么反馈会以模糊的“混乱”或“东西太多”的形式出现,而不是具体的缺陷。
如需一份中立的练习节奏表,请阅读仓库根目录下的 docs/STORYTELLING-WORKBOOK.md。可复用的设计系统在本技能的 reference/design-system.md 中;已实现的开放循环几何结构在 reference/patterns/wall-of-slots/ 中。不需要私有的源视频。
这与 hyperframes-video-beats 不同。那个技能是在说话人头顶叠加卡片。本技能设计视频所关于的内容。
1 · 四大原则
一个持续性世界
视频存在于一个单一空间内,且永不重置。观众已经看到的所有内容仍留在他们离开的位置,新素材被添加进去,而非替换。一个元素一旦被绘制,就永不重绘、移动或重新标记。
本技能中的一切都依赖于此。如果空间不断变化,摄像机无处可去;如果屏幕上没有其他内容,聚光灯毫无意义;在一个会重置的世界里,开放循环无法保持开放。
这个世界还必须值得观众停留,因此在构建任何东西之前决定空间语法,并在整个运行时间内保持它。在参考构建中:从左到右是朝向结果,向上是朝向人类并且是屏幕上唯一温暖的东西,向下是进入一个记录系统。在前两分钟学会这一点的观众,可以在视频剩余部分免费解读。
刻意且仅一次打破语法,以求含义。参考构建中十分钟内唯一的从右到左移动,是在“再次开放率”这个词上,一个已解决的工单反转回队列。它落地时无需言语,因为方向在过去三分钟里一直意味着某种东西。
摄像机即剪辑
每次切换到新图形都会让观众失去方向感。在一个保持不变的空间中移动则不会损失任何东西。推近镜头于是被解读为“更仔细地观察你已经理解的那部分”,而拉远镜头则是工作得到回报的地方——因为之前构建的一切仍然在那里。
在三个高度上工作,并在相邻高度之间移动:
| 高度 | 展示内容 | 用途 |
|---|---|---|
| 世界 | 整个空间 | 定位、回报、“我们所在之处” |
| 区域 | 正在讨论的子图 | 大部分运行时间 |
| 细节 | 一个元素 | 数字、状态变化、决策 |
从细节跳到世界会让人迷失方向,除非这是一个刻意的回报节拍,在这种情况下,这是最好的可用动作。
需要花费一次重建才能学会的规则:
- 将取景精确命名为世界窗口,而不是缩放比例。 窗口边界必须落在标签之间。将取景指定为“缩放 0.6”会在框架边缘将单词切成两半。
- 为相同目的重复使用相同窗口。 如果定位镜头是某个特定窗口,始终使用该确切窗口。几乎相同的取景会被解读为漂移。
- 持续时间随移动距离变化,而不是使用一个全局预设。
- 摄像机擦拭和全帧擦拭必须顺序执行,永不重叠。 同时进行会斩断内容。摄像机先动,然后擦拭。
- 覆盖层在摄像机在其下方移动之前变为不透明。 反过来,部分移动会被暴露并解读为跳切。
- 没有标签距离框架边缘 60px 以内。 元素可以出血并被视为“世界在继续”;被切断一半的字形会被解读为 bug。通过放置解决,而不是通过遮罩,因为羽化无法捕捉到以约 1800 px/s 速度移动的刀刃。
- 在相同主体上的两次连续移动会被解读为故障。 一个先完成再开始擦拭的重新取景是缩放、死停和第二次运动——客户称之为“故障和暂停”。将它们合并为一个手势:共享对称缓动(power2.inOut),在同一帧上着陆,以便面板看起来像是将素材推到一边。相关:在可见素材移动上使用前置负载缓动(power3/4.out)会将约 90% 的移动前置,并缓慢爬行停止;将它们保留给元素入场,在那里爬行被视为稳定。(在示例介绍的已投入制作版本中导致了一次客户标记的修订。)
注意力是聚光灯
在任何时刻,恰好一个元素处于全亮度。所有其他已绘制的元素保持安静但可读。 不可协商。这是让一个世界累积到十几个元素而从不感到拥挤的规则,也是让拉远镜头可读而非压倒性的原因。
层次来自活动与安静之间的比例,而不是让所有东西变暗。参考构建的第一个原型最亮像素为 101/255,在 960x540 分辨率下无法阅读;修复方法是更亮的墨水,而不是更多的暗色。
| 状态 | 目标亮度 (Y, 16-235) | 参考中的不透明度 |
|---|---|---|
| 活动描边 + 标签 | 200-235 | 1.0 |
| 上下文,已绘制且仍然相关 | 120-150 | 0.56 |
| 已耗尽,说完了 | 85-105 | 0.33 |
| 任何标签,任何状态 | 不低于 90 |
实现是一个函数,在每个旁白节拍被调用,并传入正在讨论的 ID(build_s01.py,lit()):
def lit(t, active, warm=(), dur=0.5, floor=FLOOR):
active = list(active) + [g + "-l" for g in active if g + "-l" in order]
for gid in order:
if gid not in opac: # 从未绘制:无需照亮
continue
tgt = 1.0 if gid in active else (WARM if gid in warm else floor)
if gid not in active:
tgt = min(tgt, MUTED.get(gid, 1.0))
...
其中的三个关键细节:
- 标签始终继承其元素的状态(通过
-l后缀折叠)。一个被照亮的元素下方有不可读的标签正是要修复的缺陷。 - 一个已经完成的道具通过
MUTED上限退休到不透明度 0,而不是 0.3。静音到 0.3 会在屏幕留下无法解释的碎屑。生成器规则:在任何稍后开始的lit()之前发出mute(),否则当补间完成时,静音会被覆盖。 - 不透明度事件必须按时间线顺序发出,否则追踪器会看到过时状态并计算错误目标。使用一个在乱序发出时报错的
seq()守卫。 - 每个窗口每个元素只有一个不透明度权威。 两个同时活动的补间驱动同一元素的不透明度到不同值,在独立章节中能平滑渲染,但在组装后的主视频中会以约 10Hz 的频率闪烁,因为获胜的补间每帧交替。参考构建在每个停靠点都存在此缺陷。当
lit()这样的扫描与显式的变亮或静音重叠时,较晚开始的补间成为唯一驱动者,并且扫描跳过这些 ID(lit(..., skip=(...)))。通过检查来把关:在每一个过渡窗口中扫描组装后的渲染,寻找一帧的亮度跳变(参考构建中的qa/v4/flicker/qa-flicker.mjs)。
开放循环是空间性的
绝不说“我们稍后再回到这一点”。在世界中留下一些视觉上不完整的东西,然后再回来处理它。一个观众能看到的承诺是一个他们能感到悬而未决的承诺。
无论形状如何,机制都是相同的:
- 在钩子中植入它,在空间中,在做出承诺的那句台词上。
- 在每个边界处重新展示它。 任何章节交接都是边界。重新展示大约花费两秒钟,是保持观看的机制;跳过它就是回报最终得不到任何回报的原因。
- 使其可视地推进。 观众必须看到不完整部分在缩小。
- 在你开始时的图像上关闭。
根据脚本选择形状:
| 脚本形状 | 世界 | 不完整的部分 |
|---|---|---|
| N 个并列项目 | 脊柱上的 N 个插槽 | 空插槽(参见 reference/patterns/wall-of-slots/) |
| 一个过程,深度 | 完整的管线,已布置 | 一个断裂或缺失的链接 |
| 层级或等级 | 向上构建的堆栈 | 堆栈顶部是一个幻影 |
| 对比 | 共享轴上的两列 | 一列为空,或未填写的记分卡 |
| 新闻或公告 | 时间线或景观地图 | 右侧,未来,是空白的 |
| 教程或跟着做 | 完整的架构,幻影显示 | 每个尚未构建的节点 |
将项目放入其插槽是一个实时的摄像机运动,约 1.4 秒,绝不剪切到预先制作的已填充状态图片。观众看着他们刚刚学到的内容变成卡片。如果视频有数字或计数器,在同一运动中滚动它,作为里程表,数字使用相同的开始、持续时间和缓动,否则它们会在滚动过程中合并成一个带。
计数器需要一个名称,而不仅仅是一个数字。 角落的数字告诉观众他们在序列中的位置,但未告诉他们当前项目是什么;九分钟时,“03”不传达任何信息。将数字与一个 1-2 个词的标签配对,命名当前项目(支持,语音 AI),使用相同的卡片化学性质,作为一个刚性固定件与数字一起移动,在同一个里程表滚动中交换。参考构建的客户审查正是要求这一点:视觉效果很好,但观众仍然失去对屏幕上是用例的追踪。
2 · 学科
如果没有一个词,就不要画出来
每个形状都是一个带标签的元素、两个带标签元素之间的连接器、移动的主体或环境背景。没有其他。
这源于一次静音测试审查,该审查评分理解度为 7/10,并指出了原因:图表是干净的,装饰则不是。十几个未标记的形状以与有意义的形状相同的粗细和调色板绘制,以至于首次观看者无法区分信号和纹理,并花费真正的注意力去解码从未打算被解码的东西。这就是“东西太多”从文字转移到了形状。
预算:每个部分约 20 个屏幕上的词。 没有句子。标签是 1-2 个词,位于其元素下方。参考中的第 1 部分在 84 秒内有 21 个词。
推论:反例视觉上绝不覆盖推荐。 先将世界变为接近黑色,或者将反例放在好图表不在的地方。
给观众一个需要跟随的东西
通过一个状态可见变化的单一移动主体来承载故事:一个带有数据行和分数条的芯片、一个文档、一个请求。行填满,条移动,边框改变颜色。观众必须能够通过只观看那个对象来跟随整个部分,并且它绝不能瞬移;它沿着已绘制的连接器以恒定速度移动。
一个全局令牌集
完整细节在 reference/design-system.md 中。
| 令牌 | 值 |
|---|---|
| 描边 | 1.5 / 2.5 / 4 / 7,没有其他 |
| 字号 | 22 / 30 / 42 / 60 / 84 |
| 圆角 | 10 / 22 / 40,始终为 superellipse(2) |
五种颜色,五个固定含义,零装饰。暖色意味着涉及人类,其他任何东西都不是暖色。避免纯白用于字体;它在暗色上会产生光晕。
这防止的失败,来自第一个构建的工艺审查:“每个决定都是在本地正确做出的;没有一个决定是全局做出的。” 18 种描边粗细和 9 种字号,其中大多数相差 0.2,单独看不可见,合起来看致命。眼睛得到的是噪音而不是层次。用一个令牌检查器来把关,该检查器会阻止构建。
运动
入场使用 cubic-bezier(0.16,1,0.3,1),退场比入场快,持续时间随质量变化,因此一个 290px 的节点和一个 40px 的标签不会以相同的减速停止,并且 back.out 每节恰好一次,在值得的节拍上。
绝无交叉淡入淡出。 全出血层通过在已不透明背景上的裁剪进入和退出。在任何瞬间,一个像素要么是图形,要么是视频素材。
3 · 构建合同
- 每节一个生成器。
scripts/build_<id>.py只写入compositions/<id>.html。编辑生成器,绝不编辑生成的 HTML。章节根文件index-*.html是手动维护的,因此取景更改在那里进行。 - 共享模块保持共享。 任何定义共享几何的内容都被每个章节导入。如果章节由并行代理构建,禁止它们触及共享模块;分叉的几何会静默地破坏每一个交接。
- 在组装前为 SVG 定义添加命名空间。 每个章节定义自己的
gEdge、shadow、edgeFade。在一个已组装的 DOM 中,url(#id)解析为文档顺序中的第一个匹配,因此一旦较早的章节卸载,之后的每个图形都绘制空内容。在参考构建中的代价:35 秒的空屏,但每一项检查都是绿色。只有查看组装后的渲染才发现了它。 - 根据单词级转录本,将所有时间对齐到单词开始,在锚定词之前 0.15-0.25 秒 进入。绝不将章节均匀划分成节拍。
- 审计间隙。 任何超过约 1.5 秒且无变化发生的延伸都是死屏;让生成器打印它们。
- 在剪辑中留下气口。 激进的暂停修剪会在句子边界产生接近零的间隙,客户听起来像“口吃”和跳切。根据说话者自己的标准进行校准:标记出句子边界间隙比其中位数紧约 10 倍的连接点,并通过插入 4-8 帧气口来修复,绝不触及单词。气口是取自轨道真实房间音调的捐赠切片,带有 10ms 淡入淡出,绝不使用
anullsrc:在 -50dBFS 底噪之上的数字静音是可闻的中断。视频保持静止;在句子之间,说话人的头几乎不动,保持 4-8 帧是不可见的。仅插入更改会确定性地移动转录本,因此无需重新转录。插入的边缘必须尊重语音包络:以 1ms 将捐赠音调邻接单词尾部……
……
[已截断以用于翻译]