解说视频
将“解释这个功能”转化为一段简短的MP4:真实的应用画面、讲解的语音、字幕、像真人一样移动的光标,以及围绕当前讨论控件的环形高亮。用户负责简要说明和审核,你完成其余所有工作。
整个流程中有一个比任何其他环节都更重要的审核关卡:在录制任何内容之前,用户必须批准叙事脚本。 修改措辞在脚本上只需数秒,在视频上则需要数分钟;而一个错误的声明在纸面上发现远比在视频中纠正成本低得多。
所需条件
- 应用的源代码,以及一个可以打开并登录的实例(本地或开发/预发环境)。切勿对着生产环境录制:每一次点击都会产生实际影响。
- 应用项目中的Playwright(
node_modules/playwright或@playwright/test)。大多数包含E2E测试的项目已经安装了它。如果没有:npm i -D playwright && npx playwright install chromium。 - 系统PATH中可用的
ffmpeg和python3。 - 用于最终语音的文本转语音密钥(Murf 或 ElevenLabs)。如果没有,
--provider say会使用macOS语音,这适用于草稿录制。参见references/voices.md。
首先检查这些条件,并明确指出缺少什么。不要要求用户将API密钥粘贴到聊天中:请他们将其放入环境变量或git忽略的文件中,并告诉你位置。
工作流程
1. 接收简要说明
你需要四件事。仅询问请求中未明确提及的内容。
- 目标和受众。“让CTO了解新的权限页面”制作出的视频与“教新成员如何发布应用”完全不同。受众决定了视频长度、词汇以及需要解释的程度。
- **操作流程。**要展示的内容及顺序。如果用户给出了流程,请进行改进并说明更改内容和原因:更好的顺序、遗漏的步骤、或多余的步骤。用户要求的是视频,而不是他们列表的转述。
- **时长。**默认长度为60-120秒,聚焦一个主题。如果流程需要更长时间,建议制作两个视频。
- **语音。**如果用户有偏好,指定提供商和音色。否则使用默认设置。
2. 在动笔之前,先在真实应用中走通流程
运行应用,使用演示账号登录,然后使用Playwright或浏览器亲自执行流程中的每一步。你需要验证叙述中将要声明的每一个事实是否属实。
这是该技能提供大部分价值的地方。功能通常是根据记忆或规格说明来描述的,而实际应用可能略有不同。如果应用的行为与用户所说的不符,请停下来并告知用户你所观察到的实际情况。不要编写掩盖问题的叙述,也不要悄悄跳过该步骤。
同时记录下你看到的按钮和菜单上的确切标签。叙述中应使用屏幕上的实际文字。
3. 植入演示数据
在空账号或状态不完整的账号上录制解说视频,看起来就像一份bug报告。创建故事所需的数据:一个组织、几个不同角色的用户、具有可信名称的项目。仅使用虚构的名称(Acme、Northwind、example.com):这些视频可能会被转发。
记录你创建或修改的所有内容。在第9步中你需要恢复它们。
4. 撰写叙事脚本,然后暂停等待审核
将脚本写成表格形式,并保存在视频将要存放的位置旁边,文件名为 <nn>-<slug>.script.md:
| # | 屏幕上显示的内容 | 旁白 | ~秒 |
|---|---|---|---|
| 1 | 任务列表静止状态。环高亮列表。 | 这是团队任务列表。/团队中的每个人都能看到相同的任务。 | 5 |
| 2 | 光标移至 **添加任务**,表单打开。输入名称,点击 **保存任务**。 | 要添加一个任务,选择添加任务,/给它一个名称,/然后保存。 | 7 |
/ 符号用于将某个场景的旁白分割成字幕行。在撰写之前,请阅读 references/narrative.md:其中涵盖了节奏、每行长度、前后对比卡片以及容易让观众难以理解的常见错误。
向用户展示表格并等待。说明预期的总时长。如果你更改了他们的流程,请在此处说明。只有在用户批准后才能继续;如果他们要求修改,请再次发送修改后的表格。
5. 生成语音
将已批准的叙述写入 scenes.json,每个表格行对应一个条目,在 / 标记处分割行:
[{ "id": "02", "lines": ["要添加一个任务,选择添加任务,", "给它一个名称,", "然后保存。"] }]
python3 scripts/tts.py scenes.json --audio-dir work/audio --provider murf
这会为每个场景写入一个音频片段和 durations.json。语音生成特意放在录制之前:每个场景需要在屏幕上停留与音频片段相同的时间,因此必须首先确定片段长度。重新运行仅会重新生成文本发生变化的场景。
6. 编写录制脚本并进行空跑测试
复制 example/record.cjs 并根据当前流程重写其场景。scripts/walkthrough.cjs 提供了以下辅助功能:
| 辅助函数 | 用途 |
|---|---|
scene(id, async () => {...}) | 一个带有旁白的场景。显示字幕并保持画面直到语音结束。 |
clickAt(locator) | 将光标滑动到该元素,显示按下动作,然后点击。请使用此函数,不要用 locator.click(),否则操作会在没有任何可见原因的情况下发生。 |
moveTo(locator) | 指向某个元素但不点击。 |
typeInto(locator, text) | 点击输入框并以可读的速度输入文本。 |
ring(locator) / ring(null) | 高亮显示语音正在讨论的内容;当语音切换到下一部分时移除高亮。 |
step('第2步,共4步 · 保存') | 左下角的标签,告诉观众他们当前所处的位置。 |
card({ title, subtitle }) | 全屏标题卡片,用于结束场景。 |
card({ before, after }) | 前后对比卡片。card(null) 隐藏它。 |
login: async (page) => {...} | record() 的可选参数。在镜头外完成登录,使视频从故事开始的地方开始。 |
优先使用角色和标签定位器(getByRole、getByLabel)而不是CSS选择器:它们能适应样式变更,并且这些名称就是旁白中使用的名称。在运行时从屏幕上读取名称,因为不同环境中的数据可能不同。
首先使用 dry 模式运行:
node record.cjs <app-repo> work/rec work/audio/durations.json dry
空跑测试不进行任何录制,也不等待语音,因此错误的选择器只会浪费几秒钟,而不是一次完整的录制。修复问题直到每个场景都能正常运行。
7. 录制并合成
node record.cjs <app-repo> work/rec work/audio/durations.json
python3 scripts/mux.py --rec work/rec --audio-dir work/audio --output walkthrough-videos/<nn>-<slug>.mp4
录制器会打印每个场景的片段长度,以及该场景操作所花费的时间。如果某个场景在语音结束后还有数秒的静默,说明该场景的操作过多,而语音内容过少:要么精简操作,要么让语音多说一些内容。
mux.py 会输出MP4文件、对应的 .srt 字幕文件以及 <name>.sheet.jpg 联系表。
8. 先自己检查,再交给用户
打开联系表:每个场景一帧。检查环形高亮是否指向了错误的元素或缺失、字幕是否覆盖了正在讨论的内容、场景是否出现在错误的屏幕上、是否出现了开发者工具徽标或错误提示框、以及是否暴露了真实的个人数据。如果联系表分辨率不够,使用ffmpeg提取单帧。
然后将视频交给用户,并附带说明:视频长度、与已批准叙述相比是否有任何更改(如果有),以及你无法验证的任何内容。
当用户要求修改时,首先回到叙事脚本。 修改表格,展示给用户,获得批准,然后仅重新生成发生变化的部分。大多数反馈(“太快了”、“我搞不清楚哪里变了”、“还要提一下滤镜”)其实是对脚本的反馈,仅仅在录制中修正会产生一段与脚本不再匹配的视频。
9. 恢复环境
撤销录制过程中所做的更改,使用第3步中的清单以及录制本身所点击的内容:切换过的设置、创建或删除的项目、演示账号的状态。然后告知用户哪些内容已恢复,以及你故意保留了什么(例如,可能用于下一个视频的种子数据)。如果某些内容无法恢复,请说明。
文件
交付物应集中存放在用户容易找到的同一个地方,默认在项目的 walkthrough-videos/ 目录下:
walkthrough-videos/
03-add-a-task.mp4
03-add-a-task.srt
03-add-a-task.script.md
将工作文件(scenes.json、音频、原始录制文件、录制脚本)保存在代码库之外或git忽略的文件夹中。它们体积较大,且录制脚本中包含特定时刻应用的选择器。脚本和 .srt 文件值得保留:它们能使后续的修改变得非常容易。
可能出现的问题
- 原生控件无法录制。 原生的
<select>下拉框、文件选择器或浏览器对话框是由浏览器在页面外部绘制的。使用moveTo指向该控件,使用selectOption/setInputFiles设置值,并在旁白中说明所选的选项。 - 导航后覆盖层消失。 覆盖层会在每次页面加载时重新创建,当前的步骤和字幕会被恢复。如果某个页面完全替换了
document.body,请在页面稳定后再次调用step()。 - 页面尚未就绪时场景就开始。 在调用
scene()之前(而不是内部)等待场景所需的元素就绪(await locator.waitFor()),以免加载时间也被配上旁白。 - 开头几秒是空白页面。
mux.py会裁剪掉第一个场景之前的所有内容。请将导航和等待操作放在scene('01')之前完成。 - 在镜头前进行登录。 请使用
login选项。没有人需要观看输入密码的过程,而且凭证绝不能在共享视频中可见。