Screen video(屏幕视频录制)
Chrome 的 screencast 录制的是浏览器本身,重新编码为 60 fps 的 H.264 .mp4。字幕、光标、缩放和音效在录制后添加,因此无论页面在做什么,录制的画面都保持流畅。
脚本使用当前仓库 node_modules 中的 Playwright,因此请从仓库根目录运行。<skill-dir> 表示此技能所在的文件夹。
0. 先问清楚
除非请求中已明确说明,否则请一次性询问:
- 要讲什么故事? 哪个应用和 URL,哪些页面,观众应该看到的 3–6 个关键步骤。谁在看(队友、审查者还是客户)?
- 是否配音? 仅字幕,还是带旁白(AI 语音旁白)?客户视频必须带旁白。
- 视频交付到哪里? 仅
.mp4文件(默认)、GitHub PR 或 Issue、Linear 议题、Slack 或其他地方。请询问具体的 PR/议题/频道;切勿代他人创建工单。 - 使用什么数据? 录制时要使用哪个本地数据库或账户。绝不能使用生产数据或真实客户数据。
1. 录制
-
在本地运行应用,并先打开每个页面一次:否则开发服务器的首次编译过程会被录进视频。
-
基于
scripts/record-lib.mjs编写一个流程脚本(复制examples/playwright-site/flow.mjs,这是一个针对公共网站的可运行录制示例)。 使用step(id, caption, action)(或仅用caption())在每一步显示一行字幕:底部居中,高于播放器的进度条,半透明(使用captionStyle: 'top'可显示为横幅)。视频长度保持约 1 分钟。 使用流程中的click(locator)/hover(locator)进行点击,而非locator.click():前者会让绘制的光标先滑向目标。直接使用locator.click()仍然有效,但光标会跳跃。zoom(locator, { scale })会平滑放大到目标,unzoom()恢复全屏,click(locator, { zoom: true })会在点击前放大。缩放会保持状态直到移动;在缩放视图外部点击会先平移画面。缩放预算:它应该让人感觉是真人凑近看,而不是条件反射。
- 只有在小字体就是重点时才缩放:比如差异对比、变化的值、对话框中的消息。绝不在导航、打开菜单或任何人都能看到的按钮操作时缩放。
- 大约每 15–20 秒一次缩放。连续两次缩放时,应在目标之间平移(对下一个目标调用
zoom),而不是缩回再放大。 - 在缩放状态下继续执行同一区域内的后续操作;当故事转移到其他地方时再调用
unzoom()。 - 缩放比例 1.3–1.5。仅在针对非常小的元素(如单个图标)时使用 2。保持至少 2 秒。
- 优先使用带旁白的暂停而非缩放:如果文字在全尺寸下也能清晰阅读,就不要缩放。
使用
type(locator, text, { clear })来输入:它会模拟人类的打字速度。click和type会记录每次点击和按键到<video>.sfx.json文件中,narrate.mjs会将这些录制的声音与语音混合:使用sounds/中的鼠标点击和机械键盘采样。SFX_VOLUME控制音量(默认 1),SFX=0表示不使用音效。page.keyboard.type和直接使用locator.click()不会产生声音。 -
运行
node flow.mjs会输出.mp4文件路径。需要安装ffmpeg(可通过$FFMPEG环境变量或 PATH 路径指定;如果未安装,运行npm i --prefix <scratch> ffmpeg-static)。
画质
record(flow, { scale }) 将页面布局为 size 尺寸(默认 1280×800 CSS 像素,因此 UI 显示正常),并以 scale 倍的像素密度渲染:2(默认)为 2560×1600,3 为 3840×2400。文字更清晰,而不是更小。在 2 倍率下,每分钟视频大约 15–40 MB;在 3 倍率下约为 2–3 倍的体积。对于有大小限制的目标平台(GitHub 免费计划 10 MB,Slack 预览),请使用 scale: 1 或 CRF=24。
旁白(当要求配音时)
- 编写
script.json:格式为[{ "id": "intro", "say": "…" }, …],每个step的id对应一句旁白。用口语化风格编写:短句,数字用文字表示(如“一千五百”)。 GOOGLE_API_KEY=… node <skill-dir>/scripts/tts.mjs script.json <audio-dir>会为每句旁白生成一个 WAV 文件以及durations.json。Gemini 3.8 Flash TTS(可通过TTS_MODEL、TTS_VOICE环境变量覆盖)听起来比 OpenAI 的gpt-4o-mini-tts更自然。API 密钥可从 aistudio.google.com 获取。- 使用
{ narration: '<audio-dir>/durations.json' }参数录制。每个step会等待其对应的旁白播放完毕后才会继续,开始时间会记录在<video>.mp4.marks.json中。 - 运行
node <skill-dir>/scripts/narrate.mjs <video.mp4> <audio-dir> <out.mp4>将音频片段和音效混合到视频中(以CRF质量重新编码,默认 18)。输出为<out.mp4>。
TTS 注意点:
- 只发送旁白文本本身。如果加上“请用温暖语气说:……”这样的前缀,模型会读出来;而使用
systemInstruction参数则返回 400 错误。 - Gemini 返回一个完整的 WAV 文件外加一个来源块;若直接封装该 WAV 会导致每句旁白首尾都有静音。
tts.mjs会据此提取数据块,请保留此逻辑。 - 要检查同步,可以使用 OpenAI 的
whisper-1模型(response_format=verbose_json)转录音频并加上时间戳,再与标记对比。
可能导致重录的陷阱
- 清空预填的输入框时,请先使用
.fill(''),然后再使用pressSequentially,因为后者只会追加内容。 - 每次录制都会改变数据(发布、接受、保存)。每次录制之间请重置数据。
- 等待自动保存和后台 AI 填充完成后再进行下一步,否则 UI 会跳动。
- 滚轮滚动可能会折叠折叠页头,从而隐藏下一个按钮。建议使用
el.scrollIntoView()并通过locator.evaluate执行。 - 绝不能让页面进入浏览器全屏模式:否则帧大小会变化,视频会被裁剪为左上角部分。在全屏按钮生效前,执行
document.documentElement.requestFullscreen = undefined。
演示数据
- 向本地数据库中精确填充故事所需的状态,使用真实可信的用户名(
example.com邮箱和555电话号码)。绝不能使用真实客户数据,也不能使用预览或生产数据库。 - 历史记录、审计和事件表是仅追加的:每次重录都会增加行,使下一次录制看起来不真实。每次录制请使用全新的记录,或者选择不会留下痕迹的操作来构成故事。
- 先进行试运行:在流程中添加
DRY=1分支,对每个破坏性确认点击取消。在几个步骤标记处截取画面,修复定位器和框架,然后执行一次真正的录制。 - 录制完成后,检查数据库的最终状态是否与字幕或旁白所述一致。
- 更改数据意味着需要为描述数据的旁白重新运行
tts.mjs。 - zsh 不会对
$VAR进行单词分割。请将帧时间直接传递给frames.mjs,或者在bash下使用循环。
客户教程视频(入门、操作指南)
- 必须带旁白。字幕保持简短;细节由语音传达。
- 电子邮件步骤使用 Gmail 场景:从
scripts/gmail-scene.mjs中使用mountGmail(page, { emailHtml, subject, sender, me })。提供应用的真实渲染邮件。该函数会将target="_blank"重写为_top,因为弹出窗口会导致录制在错误的页面上等待。回复功能已内置:使用#reply-btn、#reply-text、#send-btn。若需要使用多个收件箱,则传入mailbox: 'sam'(在/sam和/sam/message下提供服务)。回复邮件不会到达应用,因此请将其效果直接写入本地数据库。 - 等待元素出现,而非使用
waitForURL:认证重定向链会中止导航(ERR_ABORTED),并且某些页面永远不会触发load事件。给导航点击添加{ noWaitAfter: true }参数。 - 在(重新)启动开发服务器后,预加载录制中会访问的每个页面和 API 路由。
- 每次录制前重新播种数据。魔法链接令牌只能使用一次,因此失败的重试不会有效。
2. 检查
运行 node <skill-dir>/scripts/frames.mjs <video.mp4> 10 30 50 会在视频旁边生成 frame-<t>.jpg 文件。交付前请查看这些截图。对于带旁白的视频,请让用户听一遍:截图无法捕捉音频缺陷。检查是否包含真实客户数据(姓名、邮箱、电话):一旦上传,所有能看到该目标的人都能看到这些数据。
3. 交付
仅文件(默认)
打印最终 .mp4 文件的绝对路径。完成。
GitHub PR、Issue 或评论(内联播放)
GitHub 只允许通过 https://github.com/user-attachments/assets/<uuid> 这样的 URL 在其独占行上内联播放视频,而该 URL 只能通过 GitHub 的网页上传获得:gh 命令行工具、REST API 和提交的文件都只会提供下载链接。上传的文件对仓库成员保持私有,且永远不会被提交到仓库。
node <skill-dir>/scripts/attach-video.mjs <GitHub PR 或 Issue URL> <video.mp4>
- 会打开一个可见的 Chromium 窗口。如果保存的登录信息已过期,将显示 GitHub 的登录页面:用户需亲自登录。切勿询问密码,也切勿读取浏览器的 Cookie。
- 通过页面的评论框上传;评论不会被发布。输出
ATTACHMENT_URL https://github.com/user-attachments/assets/<uuid>。 - 登录信息保存在
~/.cache/screen-video/gh-profile中。设置FORGET_SESSION=1可在上传后删除该配置文件。并发上传会等待该配置文件(WAITING_FOR_PROFILE)。
将 URL 单独放在一行中,添加到正文或评论中(使用 gh pr edit <n> --body-file <file>,gh issue comment <n> --body-file <file>)。要确认是否生效,可以检查 PR 的 body_html 中是否有 <video src=…private-user-images…> 标签;使用 curl -sI 请求资源 URL 可能会返回 404,但视频仍可正常播放。大小限制:免费计划 10 MB,付费计划 100 MB。
Linear 议题
Linear MCP 可以直接上传,无需浏览器。
- 使用
prepare_attachment_upload,参数包括issue(议题 ID)、filename(文件名)、contentType(内容类型,设为video/mp4)和size(精确字节数:macOS 使用stat -f%z <file>,Linux 使用stat -c%s)。 - 在 60 秒内,使用
curl -X PUT --data-binary @<file> "<uploadRequest.url>"上传文件,并逐字发送uploadRequest.headers中的所有头部信息。更改任何头部的字母大小写或删除任何一个都会导致 403 错误。一次只上传一个文件。 - 使用
create_attachment_from_upload,参数包括issue和assetUrl。 - 要在线程中播放视频,请使用
save_comment添加以及一行说明文字。打开该议题一次以确认视频渲染为播放器。 - 替换视频:上传新视频,编辑同一评论(使用
save_comment及id参数),然后使用delete_attachment删除旧附件。
Slack
使用 Slack MCP,在用户确认频道之后(分享操作无法撤销):
- 调用
slack_get_file_upload_url,参数包括filename(文件名)和content_length(内容长度,精确字节数)。 - 执行
URL='<upload_url>' && curl -X POST "$URL" -H 'Content-Type: video/mp4' --data-binary @<file>。请保持 URL 完全不变;如果出现 401 错误,表明 URL 被篡改或已过期,请重新获取。 - 调用
slack_complete_file_upload,参数包括file_id、channel_id(必须是C…或D…形式的 ID,而非频道名称;可使用slack_search_channels查询)、可选的thread_ts和initial_comment。
如果没有 MCP,请将文件路径提供给用户,让其拖入 Slack。
其他任何地方
提供文件路径和文件大小;如果目标平台有大小限制,请提及可以使用 scale: 1 或 CRF=24 参数。