Communitygithub.com

johnwhoyou/screen-video

使用 Playwright 录制带字幕和可选语音旁白的屏幕视频,支持平滑光标、键盘和点击音效、缩放和平移,输出 2× 到 4K 画质的 .mp4 文件,并直接交付到 GitHub 工单、Linear 议题、Slack 或客户教程中。

screen-video 是什么?

**screen-video** 是一个 AI 智能体技能,用于录制高质量、带字幕、可选 AI 语音旁白的屏幕演示视频。它基于 Playwright 自动化浏览器操作,并运用自定义脚本和音效库增强录制效果:包括模拟鼠标滑过、键盘打字音效、智能缩放和平移(像真人一样“凑近”看细节)、以及对时间轴的精细控制。该技能支持多种交付渠道:将视频直接上传至 GitHub 工单或 PR(内联播放)、通过 Linear MCP 附加到 Linear 议题、通过 Slack MCP 分享到频道,或仅输出本地的 .mp4 文件。它特别适合制作 UI 变更演示、Bug 复现视频、客户上手教程和团队内部 walkthrough。技能包含多个辅助脚本(如 `tts.mjs` 生成语音、`narrate.mjs` 混合音轨、`attach-video.mjs` 上传到 GitHub),并要求在录制前询问故事内容、是否配音、交付目标和使用的数据环境,以确保不会误用生产数据。整体设计注重效率与真实感,避免常见陷阱(如浏览器全屏、自动保存干扰、链接跳转问题)。

兼容平台~Claude Code✓Codex CLI✓Cursor✓Gemini CLI
npx skills add https://github.com/johnwhoyou/screen-video/tree/HEAD/plugins/screen-video/skills/screen-video

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

预览

来自技能 README

walkthrough.mp4

文档

Screen video(屏幕视频录制)

Chrome 的 screencast 录制的是浏览器本身,重新编码为 60 fps 的 H.264 .mp4。字幕、光标、缩放和音效在录制后添加,因此无论页面在做什么,录制的画面都保持流畅。

脚本使用当前仓库 node_modules 中的 Playwright,因此请从仓库根目录运行。<skill-dir> 表示此技能所在的文件夹。

0. 先问清楚

除非请求中已明确说明,否则请一次性询问:

  1. 要讲什么故事? 哪个应用和 URL,哪些页面,观众应该看到的 3–6 个关键步骤。谁在看(队友、审查者还是客户)?
  2. 是否配音? 仅字幕,还是带旁白(AI 语音旁白)?客户视频必须带旁白。
  3. 视频交付到哪里? 仅 .mp4 文件(默认)、GitHub PR 或 Issue、Linear 议题、Slack 或其他地方。请询问具体的 PR/议题/频道;切勿代他人创建工单。
  4. 使用什么数据? 录制时要使用哪个本地数据库或账户。绝不能使用生产数据或真实客户数据。

1. 录制

  1. 在本地运行应用,并先打开每个页面一次:否则开发服务器的首次编译过程会被录进视频。

  2. 基于 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() 不会产生声音。

  3. 运行 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。

旁白(当要求配音时)

  1. 编写 script.json:格式为 [{ "id": "intro", "say": "…" }, …],每个 step 的 id 对应一句旁白。用口语化风格编写:短句,数字用文字表示(如“一千五百”)。
  2. 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 获取。
  3. 使用 { narration: '<audio-dir>/durations.json' } 参数录制。每个 step 会等待其对应的旁白播放完毕后才会继续,开始时间会记录在 <video>.mp4.marks.json 中。
  4. 运行 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 可以直接上传,无需浏览器。

  1. 使用 prepare_attachment_upload,参数包括 issue(议题 ID)、filename(文件名)、contentType(内容类型,设为 video/mp4)和 size(精确字节数:macOS 使用 stat -f%z <file>,Linux 使用 stat -c%s)。
  2. 在 60 秒内,使用 curl -X PUT --data-binary @<file> "<uploadRequest.url>" 上传文件,并逐字发送 uploadRequest.headers 中的所有头部信息。更改任何头部的字母大小写或删除任何一个都会导致 403 错误。一次只上传一个文件。
  3. 使用 create_attachment_from_upload,参数包括 issue 和 assetUrl。
  4. 要在线程中播放视频,请使用 save_comment 添加 ![walkthrough.mp4](<assetUrl>) 以及一行说明文字。打开该议题一次以确认视频渲染为播放器。
  5. 替换视频:上传新视频,编辑同一评论(使用 save_comment 及 id 参数),然后使用 delete_attachment 删除旧附件。

Slack

使用 Slack MCP,在用户确认频道之后(分享操作无法撤销):

  1. 调用 slack_get_file_upload_url,参数包括 filename(文件名)和 content_length(内容长度,精确字节数)。
  2. 执行 URL='<upload_url>' && curl -X POST "$URL" -H 'Content-Type: video/mp4' --data-binary @<file>。请保持 URL 完全不变;如果出现 401 错误,表明 URL 被篡改或已过期,请重新获取。
  3. 调用 slack_complete_file_upload,参数包括 file_id、channel_id(必须是 C… 或 D… 形式的 ID,而非频道名称;可使用 slack_search_channels 查询)、可选的 thread_ts 和 initial_comment。

如果没有 MCP,请将文件路径提供给用户,让其拖入 Slack。

其他任何地方

提供文件路径和文件大小;如果目标平台有大小限制,请提及可以使用 scale: 1 或 CRF=24 参数。

相关技能