关于 video-storytelling
video-storytelling 是为长篇脚本视频设计视觉层的技能,尤其适用于真人讲述形式。它通过持续存在的视觉空间、镜头移动和空间化悬念,帮助观众保持理解方向。
主要功能
- 01维持单一且持续存在的视觉空间
- 02用镜头移动引导注意力
- 03构建空间化悬念和可见的兑现
- 04统一视觉令牌与运动规则
- 05让图形聚焦于有标签的信息
适用场景
- 01为长篇真人讲述视频设计画面
- 02用持续的图示解释一个流程
- 03以视觉方式组织教程、对比或清单视频
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nateherkai/ herk-edit-kit --skill video-storytelling安装后在 Claude Code 中输入/video-storytelling确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
reference/design-system.mdreference/patterns/wall-of-slots/geom.pyREADME.mdwall.py详细介绍
视频叙事
如何构建长视频的视觉层,让观众在第九分钟时仍能理清方向。适用于任何带脚本的真人出镜视频:新闻解读、单主题深度分析、对比、教程、清单类视频。
要替代的是按场景思考的做法。每隔几秒就换一张新图,会让观众每隔几秒就得重新理解内容;但问题并不出在任何一张具体的图上,所以反馈往往含糊地说“看不懂”或“信息太多”,而不是指出具体缺陷。
如需一份中性的练习节拍表,请阅读仓库根目录下的 docs/STORYTELLING-WORKBOOK.md。可复用的设计系统位于此技能的 reference/design-system.md;完整演示的开放循环几何结构位于 reference/patterns/wall-of-slots/。无需使用私有源视频。
这不是 hyperframes-video-beats。该技能是在真人出镜画面上叠加卡片;本技能设计的是视频所讲述的内容。
1 · 四项原则
一个持续存在的世界
视频存在于一个单一空间中,且这个空间永不重置。观众已经看过的所有内容都仍留在原处,而新内容是在其上添加,而不是取而代之。一个元素一旦绘制,就绝不重绘、移动或重新标注。
本技能的其他一切都以此为基础。如果空间不断变化,镜头就无处可去;如果屏幕上没有其他内容,聚光就毫无意义;如果世界不断重置,开放循环也无法保持开放。
这个世界还必须值得观众停留,因此要在开始构建前确定空间语法,并在整个视频时长内保持一致。在参考作品中:从左到右代表走向结果,向上代表走向人,且是屏幕上唯一的暖色;向下则进入记录系统。观众若在前两分钟学会了这套规则,之后的视频就能轻松看懂。
只为表达含义而有意打破语法一次。参考作品在十分钟内唯一一次从右向左移动,是在说到“重新打开率”时,一个已解决的工单反向回到队列。这一幕无需言语便能传达意思,因为此前三分钟里,方向一直有其含义。
摄像机就是剪辑
每次切换到新的图形,观众都会失去方向感。在一个保持不变的空间中移动,则不会让他们付出任何代价。因此,推近会让人觉得是在说“仔细看看你已经了解的事物中的这一部分”,而拉远才是作品见效的地方,因为迄今构建的一切都还在那里。
在三个景别层级间工作,并在相邻层级间移动:
| 景别层级 | 展示内容 | 用途 |
|---|---|---|
| 全景 | 整个空间 | 确定方向、呈现成果、“我们现在在这里” |
| 区域 | 正在讨论的子图 | 大部分运行时间 |
| 细节 | 单个元素 | 一个数字、一次状态变化、一个决定 |
除非是刻意安排的成果呈现节拍,否则从细节直接跳到全景会让人迷失方向;若是刻意安排的成果呈现节拍,这就是最好的选择。
以下规则,每条都曾让我付出重做的代价才学会:
- 将构图命名为精确的世界窗口,而不是缩放比例。 窗口边界必须落在标签之间。将构图指定为“scale 0.6”,会让画面边缘把文字切成两半。
- 同一用途始终复用同一窗口。 如果定向镜头使用的是某个特定窗口,就始终使用这个精确窗口。近乎相同的构图会让人感觉画面在漂移。
- 根据移动距离调整时长,不要使用一个全局预设。
- 摄像机移动和全屏擦除应依次发生,绝不能重叠。 同时发生会把内容拦腰截断。先移动摄像机,再擦除。
- 遮罩层应先变为不透明,再让摄像机在其下方移动。 顺序相反会暴露部分移动过程,看起来像跳切。
- 标签距画面边缘至少 60px。 元素可以延伸出画面,让人读作“世界仍在延续”;但文字被切掉半个字形则会像程序错误。通过调整位置解决,而不是用遮罩,因为羽化边缘追不上以约 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: # never drawn: nothing to light
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后缀)。元素已点亮、但其下方的标签无法辨认,正是要修复的问题。 - 完成的道具会以不透明度 0 退场,通过
MUTED上限实现,而不是调到 0.3。调到 0.3 会让无法解释的零碎内容留在屏幕上,直到本节结束。生成器规则:在任何后续开始的lit()之前发出mute(),否则其补间完成时会覆盖该设置。 - 必须按时间线顺序发出不透明度事件,否则跟踪器会看到过时状态并计算出错误的目标。保留一个
seq()检查,以便在事件顺序错误时抛出异常。 - 每个时间窗口内,每个元素只能有一个不透明度控制源。 两个同时活跃的补间若将同一元素的不透明度驱动到不同值,在独立片段中渲染会很平滑,但在组装后的主成片中会以约 10 Hz 闪烁,因为每帧胜出的补间会交替。参考构建在每个停靠点都带有这个缺陷。当
lit()这类扫掠动画与显式增亮或静音重叠时,较晚开始的补间是唯一的控制源,扫掠动画会跳过这些 id(lit(..., skip=(...)))。对此进行把关:扫描组装后的渲染,在每个转场窗口中检查亮度是否出现单帧内突增后又回落的跳变(参考构建中的qa/v4/flicker/qa-flicker.mjs)。
留下的悬念要具象可见
永远不要说“我们之后再说这个”。在画面中留下一个明显未完成的东西,然后再回到它。观众看得见的承诺,才是他们感受到尚未兑现的承诺。
无论形状如何,机制都相同:
- 在钩子中种下它:放在空间里,放在许下承诺的那句旁边。
- 在每个转场处重新展示它。 任何章节交接都是一个转场。重新展示大约需要 2 秒,也是留存机制;省略这一步,就会让后续的兑现变得毫无意义。
- 让它 visibly 推进。 观众必须看见未完成的部分逐渐缩小。
- 以开场时的画面收尾。
根据脚本选择形状:
| 脚本形式 | 世界 | 未完成的部分 |
|---|---|---|
| N 个并列项目 | 主干上的 N 个插槽 | 空槽(参见 reference/patterns/wall-of-slots/) |
| 一个深入讲解的流程 | 完整铺开的流程线 | 断裂或缺失的连接 |
| 多个级别或层级 | 向上搭建的堆叠 | 顶层是虚影 |
| 比较 | 共用同一轴线的两列 | 一列为空,或评分卡尚未填完 |
| 新闻或公告 | 时间线或景观地图 | 右侧,也就是未来,一片空白 |
| 教程或跟做 | 用虚影呈现的完整架构 | 尚未构建的每个节点 |
将项目放入对应插槽时,要用实时镜头移动,约 1.4 秒,绝不能直接切到预先制作好的填充完成图。让观众看着刚学到的东西变成那个方块。如果视频中有数字或计数器,就在同一次移动中像里程表一样滚动更新;两个数字的起始时间、时长和缓动必须相同,否则滚动到一半时就会融合成一条色带。
计数器需要名称,不能只有数字。 角落里的数字能告诉观众他们看到序列中的第几项,却不能说明当前项目是什么;过了 9 分钟,“03”毫无意义。用 1–2 个词的标签标明当前项目(SUPPORT、VOICE 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由人工维护,因此取景调整应在那里进行。 - 共享模块必须保持共享。 定义共享几何形状的内容都必须由每个章节导入。如果由并行 Agent 构建各个章节,必须禁止它们修改共享模块;几何形状出现分歧会悄无声息地破坏每次交接。
- 组装前为 SVG 定义添加命名空间。 每个章节都定义自己的
gEdge、shadow、edgeFade。在组装后的 DOM 中,url(#id)会解析到文档顺序中的第一个匹配项,因此较早的合成卸载后,所有后续图形都会变成空白。参考构建中的代价:所有检查项都显示绿色,屏幕却空白了 35 秒。只有查看组装后的渲染结果才发现了这个问题。 - 根据词级转录文本的每个词语起始时间安排所有内容,并在锚定词出现前 0.15-0.25s 入场。绝不要把一个章节平均划分成多个节拍。
- 审查间隙。 任何超过约 1.5s 且没有内容变化的片段都是死屏;让生成器打印这些片段。
- 剪辑时留出呼吸感。 激进地压缩停顿会使句子边界处的间隙接近于零,客户端会觉得像是在“结巴”和跳切。应根据说话者自身的常态进行校准:标记比其中位数紧约 10 倍的句子边界间隙(参考说话者的中位数为 470ms;被标记的连接处为 20-40ms),并通过插入 4-8 帧留白来修复,绝不移动词语。留白应取自轨道中真实的房间底噪片段,并添加 10ms 淡变,绝不使用
anullsrc:在 -50dBFS 的底噪上插入数字静音,会听起来像音频掉失。视频保持当前帧;句子之间的讲话人头部几乎静止,保持 4-8 帧不会被察觉。仅插入内容的更改会确定性地移动转录文本,因此无需重新转录。插入片段的边缘必须遵循语音包络:若在 1ms 内将供体底噪直接接到词尾,会截断自然需要 60-120ms 才消退的尾音,客户端会把这种断崖式变化听成结巴。应以 40ms 等功率交叉淡化接入尾音自身的衰减,绝不硬接。 - 已烘焙的时间意味着必须通过脚本移位。 生成器会烘焙数百个原型时间字面值,章节偏移、接缝表和根级补间也都使用绝对时间。图形生成后的任何时间线更改(即使只是进行 2s 的留白处理),都必须通过经过审查的 AST/解析级转换来完成:每个编辑点之后的所有字面值都要移位,然后审查差异,绝不手动编辑。每次移位都必须从规范编辑列表本身推导;参考处理通过重新推导,发现自身审计中预先计算的偏移有 2 帧误差。
- 渲染时使用
--workers 1。并行工作进程会使视频图层渲染为黑色。
4 · 检查关卡
全部运行一遍,然后仍要亲自查看。
node scripts/qa-tokens.mjs # 0 off-system strokes / type sizes / radii
node scripts/qa-no-crossfade.mjs # 0 full-bleed layers animating opacity
node scripts/qa-legibility.mjs <render>
node scripts/qa-seamjump.mjs <render> plan/cover-seams.json --control <cut>
node scripts/qa-deadframe.mjs <render> --control <cut>
node scripts/qa-presence.mjs <render> --every 1 # speaker on-screen share
node scripts/integrate.mjs --check # 0 gaps, 0 stale mounts
npx hyperframes lint
检查关卡不能替代亲自查看,而从未失败过的关卡也算不上关卡。
-
运动流畅度必须通过连续帧验证,绝不能只看节拍帧。 每个节拍取一帧只能证明构图;所有运动缺陷——动作开始时的跳变、第二个动作前缓慢爬行后骤停、冻结、单帧弹跳——都存在于节拍帧之间,能通过其他所有关卡。交付前,必须为每个转场窗口(镜头移动、擦除、重新构图、停靠、章节衔接处)截取连续帧条带,并仔细检查:
ffmpeg -i render.mp4 -vf "select='between(n,A,B)',scale=480:270,tile=layout=6x7" \ -frames:v 1 tile-<window>.png需要检查的缺陷特征:相邻画面先出现大幅跳变,随后数帧几乎完全相同(缓动集中在前段/骤停);一段运动停止后,又开始另一段运动(本应是一个连贯动作,却被拆成两个动作);任何与前后两帧都不同的单帧画面(弹跳)。示例构建中的片头出现了缩放冻结故障,尽管它通过了令牌、交叉淡化、可读性、空白间隙和逐节拍帧检查,客户还是一眼就发现了。这类问题会反复出现——要把逐帧条带检查视为不可或缺的环节,而非可选项。
-
可读性无法自动化。 绝对像素占比阈值衡量的是文本量,而非可读性;文字密集的构建得分会高于文字稀疏的构建。请逐一用肉眼检查每个主要节拍缩小到 960x540 的画面。
-
每个关卡都要用已知会触发它的对照样本进行验证。 对于有缺陷的构建,如果衔接处检查关卡报告零问题,那还不如没有关卡。
-
验证最终合成的成品,绝不要只验证章节原型。本次构建中,大多数逃过关卡的 Bug 只存在于合成成品里。
-
从声学层面审查音频,而非只看转录文本。 根据编辑前素材生成的转录文本,在结构上无法发现剪辑产生的瑕疵;对它进行文本扫描会报告“干净”,但客户听到的却是卡顿。根据参考构建中的检出率排序,真正有效的方法是:扫描整条音轨中的静音区间(停顿中出现连续数字零采样,而实际底噪约为 -70dBFS,这会形成死寂空洞;应以室内底噪填充);在每个连接边缘处进行 1ms 分辨率的包络取证(基于采样导数的咔嗒声检查会漏过包络陡变,但耳朵能立刻听出);以及对交付音频重新运行 ASR,并与预期文本进行差异比对。音素重复自相关检查没有发现问题:客户所说的“卡顿”通常是截断陡变和死寂空洞,而非词语重复。
出镜占比:以图形为主的视频中,目标是让讲者约 40% 的时间出现在画面中。需要完整画面宽度的重点呈现段落可以长时间不出镜;应审查这些段落,而不是给它们设置上限。
5 · 工作顺序
- 锁定剪辑版本,并获取逐词转录文本。所有内容都以此为时间依据。
- 选择视觉世界、空间语法和贯穿全片的主体。开始构建前先将它们记录下来。
- 根据脚本结构选择开放式悬念,并先构建其共用模块。片头钩子、每个章节边界和重点呈现段落都会引用它的几何结构。
- 先完整构建一个章节并渲染。先在这个章节中验证系统,再着手其他章节。
- 静音后用肉眼审查该章节的装饰元素和可读性。修复系统,而不是章节。
- 以已验证的章节为基准构建其余部分。然后设置命名空间、合成、渲染、运行检查关卡,并检查合成后的渲染结果。
质量的关键在于第 4 和第 5 步。参考构建中的整个视觉系统,都是通过将第一个章节审查三次后形成的。