mcpField
P
pr-walkthrough

pr-walkthrough

github.com/tldraw创意与媒体51.2k220 次安装今天更新
代码仓库

关于 pr-walkthrough

将 GitHub 拉取请求整理为带旁白的内部讲解视频的技能。它指导贡献者分析变更、编写分段旁白、生成音频与定时字幕,并渲染聚焦代码的 MP4。

主要功能

  1. 01检查 PR 提交与差异
  2. 02编写分段旁白
  3. 03生成逐段语音音频
  4. 04渲染带字幕的讲解 MP4

适用场景

  1. 01向团队成员讲解 PR
  2. 02制作聚焦代码的 PR 概览
  3. 03分享内部旁白评审材料

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/tldraw/tldraw --skill pr-walkthrough

安装后在 Claude Code 中输入/pr-walkthrough确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
manifest.md
scripts/
generate-audio.sh
make-video.sh
video/
.gitignore
build.mjs
hyperframes.json
meta.json
render.sh

详细介绍

PR 演示视频

为 pull request 创建带旁白的演示视频。这被设计为一种内部产物,其作用与 pull request 作者录制的 loom 视频相同——讲解代码变更,说明做了什么以及为什么这样做,让任何观看的人都能快速理解该 PR。

输入: 一个 GitHub pull request URL(例如 https://github.com/tldraw/tldraw/pull/7924)。如果只给出 PR 编号或其他描述,则假定该 PR 位于 tldraw/tldraw 仓库。

输出: 一个 1280×720(30 fps)的 MP4 视频,包含音频旁白、whisper 定时的黑底黄字字幕以及标准化的片头 / 片尾幻灯片,保存到 out/pr-<number>-walkthrough.mp4。

所有中间文件(音频、manifest、脚本)都放在相对于此 skill 目录的 tmp/pr-<number>/ 中。该目录已被 gitignore。只有最终的 .mp4 位于 out/。

请从此 skill 目录运行引用 ./scripts 或 ./video 的命令。

理念

这是一次从作者视角出发的演示。 其目标与 PR 作者坐下来向某人讲解变更时相同——展示具体代码,按有助于建立理解的顺序说明改了什么以及为什么改。观看者看完后应既能理解 代码做了什么,也能理解 如何思考这些变更。

这意味着:

  • 旁白驱动一切。 先撰写演示旁白,作为对 PR 的连续讲解。然后确定每一时刻屏幕上应显示什么,以支撑所讲内容。
  • 展示代码。 默认视觉内容是代码 diff 或源文件。文字幻灯片是例外(片头、简短过渡、片尾),而非常态。当旁白谈到某个函数时,观看者应该正看着那个函数。
  • 按逻辑顺序讲解变更,不一定是文件顺序或提交顺序——但始终锚定于具体代码,而非抽象描述。
  • 解释"为什么",而不仅仅是"是什么"。 屏幕上的代码展示了改了什么。旁白补充其中的理由——为什么采用这种方法、它解决了什么问题、它处理了哪些边界情况。

工作流

第 1 步:理解 PR

阅读 PR 的提交、diff 和描述。理解叙事脉络:

  • 它解决了什么问题?
  • 采用什么方法?
  • 关键机制是什么?
gh pr view <number> --json title,body,commits
git log main..HEAD --oneline
git diff main..HEAD --stat

跳过生成的文件。 阅读 diff 时,忽略自动生成而非手动编辑的文件。它们只会增加噪音,对演示没有帮助。此仓库中常见的例子:

  • **/api-report.md、**/api-report.api.md — 生成的 API 表面报告
  • **/*.api.json、**/temp/*.api.json — API extractor 输出
  • apps/docs/content/reference/** — 生成的参考文档
  • pnpm-lock.yaml、package-lock.json — lockfile
  • **/CHANGELOG.md — 自动生成的 changelog
  • 任何由仓库工具链重新生成的文件(快照、schema 转储、打包后的资源)

如果不确定某个文件是否为生成文件,请检查是否有类似 "DO NOT EDIT" 的头部注释,或检查仓库是否有生成它的生成器命令。在选择要在 diff/code 幻灯片中展示哪些文件时,请过滤掉这些文件。

第 2 步:编写旁白

将旁白写成连续文本,拆分为逻辑段落。每个段落是演练的一个节拍——一个概念、一处变更或一组相关变更。将其保存为 tmp/pr-<number>/SCRIPT.md。

旁白的读感应像作者向同事解释这个 PR:“所以我们要做的是……核心问题是 X……我采用的方法是 Y……你看这里的这个函数……”

结构:引言 → 背景/问题 → 代码讲解 → 总结。见下文 脚本结构。

如果提交简单且组织良好(通常位于名称中带 -clean 的分支上),你可以参照其提交信息和描述来引导旁白。否则,请审视代码并自行构建叙事。按照层层递进的顺序引入概念。

避免冗余,尤其是引言与第一个内容段落之间。

第 3 步:生成音频和时间戳

为每个段落生成音频片段,每段一次 TTS 调用。这样完全避免了分块、对齐和拆分——每个段落都足够短,可以可靠地通过单次 TTS 调用处理。

编写一个 narration.json 文件,然后运行 generate-audio.sh 命令行工具:

./scripts/generate-audio.sh narration.json tmp/pr-<number>/

API key: 自动从仓库的 .env 文件读取(GEMINI_API_KEY)。

旁白 JSON 格式
{
	"style": "Read the following walkthrough narration in a calm, steady, professional tone. Speak at a measured pace as if the author of a pull request were walking a colleague through the code changes.",
	"voice": "Iapetus",
	"slides": [
		"This pull request adds group-aware binding resolution to the arrow tool...",
		"The core problem was that arrow bindings broke when the target shape...",
		"If you look at the getBindingTarget method in ArrowBindingUtil.ts..."
	]
}
  • style —— 声音人设和节奏说明。保持简短具体。
  • voice —— Gemini 语音名称(默认:Iapetus)。
  • slides —— 旁白文本数组,每个段落一个条目。
工作原理
  1. 对于每个段落,脚本会构建一个提示词:风格前言 + 段落文本。
  2. 每个段落调用一次 gemini-3.1-flash-tts-preview API,直接生成一个 WAV 片段。
  3. 每个片段都会经过验证(时长合理性检查,对照字数),如果输出不佳会自动重试。
  4. 每个片段的首尾静音都会被裁掉。

输出: 各段落的音频片段(audio-00.wav 等)以及一个 durations.json 文件,将每个音频文件名映射到其以秒为单位的时长。

依赖: ffmpeg / ffprobe。除标准库外无需任何 Python 包。

请勿使用 旁白文本中的 [pause long] 或 [pause medium] 标记标签——模型可能会把它们照字面读出来。

第 4 步:编写清单

从第 3 步的 durations.json 中读取每个片段的时长,然后在音频文件旁编写 manifest.json。它按顺序为每张幻灯片列出一个条目,每个条目包含其 type、audio 和 durationInSeconds。

幻灯片类型及其字段见 references/manifest.md。

第 5 步:渲染视频

运行 render.sh 脚本:

./video/render.sh \
  tmp/pr-<number>/manifest.json \
  out/pr-<number>-walkthrough.mp4

该脚本会:

  1. 将引用的音频/图片文件复制到 video/assets/。
  2. 对每个音频文件运行 whisper 转写 → video/transcripts/audio-NN.json(幂等——仅当音频比现有转写更新时才重新转写)。
  3. 运行 build.mjs <manifest> 生成 video/index.html——一个 hyperframes 合成,包含每张幻灯片的时间轴片段、用于转场和代码聚焦平移的 GSAP 时间轴,以及带开始/结束时间(由 whisper 转写得出)的黄字黑底字幕片段。
  4. 对合成进行 lint,并通过 npx hyperframes render 渲染 1920×1080 帧。
  5. 下采样到 1280×720 / 30fps,并用 ffmpeg 重新压缩(CRF 26 + AAC 96k),生成最终小而清晰的 MP4。

依赖: Node.js 22+、ffmpeg、Python 3(由 render.sh 用于解析清单)。hyperframes 通过 npx --yes 调用,因此无需安装步骤。Whisper 在本地运行(small.en 模型,首次下载约 150MB)。

通过 whisper 同步字幕

字幕以黄色文字显示在纯黑色胶囊背景上,锚定在画面底部。其开始/结束时间来自词级 whisper 转录,按 5–7 个词一组进行分组,在自然停顿处提前断开(>450ms 的间隔 = 句子边界)。一个影响:whisper 会按发音转录品牌/代码名称——"tldraw" → "TL Draw","OverlayUtil" → "overlay util"。这对字幕来说可以接受,但之后可以通过 build.mjs 中的替换表进行规范化。

文件大小调节项

默认渲染下,一段 8 分钟视频的目标大小约为 30–60 MB。要调整:

  • render.sh 中 ffmpeg 降采样步骤里的 --crf <n>——22 接近无损,26 是默认值,30+ 会小得多。对于文档级质量的输出,CRF 28–30 是比较合适的范围。
  • 1080p hyperframes 渲染使用 -q draft --crf 30 来保持中间文件较小(无论如何,最终大小主要由降采样决定)。

文件组织

最终输出位于此 skill 目录中。所有中间文件都放在 tmp/(已被 gitignore):

pr-walkthrough/
├── SKILL.md                    # This file
├── scripts/                    # CLI tools (checked in)
│   └── generate-audio.sh       # narration.json → per-slide WAVs + durations.json
├── video/                      # Hyperframes project (checked in)
│   ├── hyperframes.json        # hyperframes config
│   ├── meta.json               # project meta
│   ├── build.mjs               # manifest.json → index.html composition
│   ├── render.sh               # manifest.json → 720p MP4 (full pipeline)
│   ├── assets/                 # Auto-populated at render time (gitignored)
│   ├── transcripts/            # Whisper word-level JSON (gitignored, cached)
│   └── renders/                # Intermediate 1080p renders (gitignored)
├── out/                        # Final outputs (gitignored)
│   └── pr-XXXX-walkthrough.mp4
└── tmp/                        # Intermediate files (gitignored)
    └── pr-XXXX/
        ├── SCRIPT.md           # Narration script
        ├── narration.json      # Input to generate-audio.sh
        ├── durations.json      # Audio filename → duration in seconds
        ├── manifest.json       # Input to render.sh
        └── audio-XX.wav        # Per-segment audio clips

API 配置

  • Gemini API 密钥: 存储在项目根目录 .env 文件中的 GEMINI_API_KEY。用于 TTS 和音频对齐。
  • TTS 模型: gemini-3.1-flash-tts-preview
  • TTS 音色: Iapetus(始终)

脚本结构

演示遵循一致的叙事弧线。并非每个部分都需要单独的分段——根据 PR 的复杂程度合并或跳过部分。目标是总共 8-12 个分段,其中绝大多数展示代码。

引言(1 个分段)

引言卡片:tldraw logo + PR 标题 + 日期。旁白应该是单句,从高层次概括这个 PR 做了什么。暂时不要深入细节。

Manifest 幻灯片类型:intro。

背景(0-1 个分段)

在深入代码前的简要定位。这个 PR 之前的情况是什么?是什么问题或需求推动了这项工作?保持简短——只要足够铺垫,让代码演示能说得通即可。

  • 要具体:"当目标形状位于组内时,箭头绑定会出错",而不是"绑定有一些问题"
  • 指明受影响的代码库区域

如果可以在展示第一段相关代码时一并解释背景,就跳过独立的背景分段,将其并入第一个代码分段。

Manifest 幻灯片类型:text 或 diff(如果展示有问题的代码)。

代码演示(6-10 个分段)

视频的主体部分。演示实际的代码变更,展示具体的 diff 和文件,同时解释做了什么以及为什么。

每个分段都应该展示代码。 变更用 diff 幻灯片,未变更的参考代码用 code 幻灯片。

指南:

  • 点明文件和函数。 每个有旁白的段落都应至少引用一个具体的文件或函数。
  • 展示 diff。 每个分段的画面应该是正在讨论的实际 diff。使用 git diff main..HEAD -- path/to/file 获取 diff,然后提取相关的 hunk。
  • 按理解顺序排列,而不是按文件。 按照有助于建立理解的顺序呈现变更。如果新类型在一个文件中定义、在另一个文件中使用,先展示定义。
  • 解释"为什么",而不只是"是什么"。 diff 展示_什么_变了——旁白补充原因、处理的边界情况、考虑过的替代方案。
  • 跳过样板代码,但要提及。 不要为每个 import 变更或类型导出都单独做一个分段,但可以顺便提一句:"index.ts 中还有一些新增的类型导出——这些只是我们接下来会看到的新类型的重新导出。"
  • 将相关的小改动分组。 如果三个文件都做了相同的单行修复,一个分段可以覆盖全部三个。逐一按名称提及每个文件。
总结(1 个分段)

简要回顾这个 PR 完成了什么。这是一个简短的收尾——用一两句话总结整体变更,如果相关的话,提及任何已知限制或后续工作。

Manifest 幻灯片类型:text。

结尾(1 个片段,静音)

tldraw 标志,3 秒静音。始终将其作为最后一张幻灯片。

Manifest 幻灯片类型:outro,带有 durationInSeconds: 3。

旁白撰写技巧

  • 具体说明代码。 说“在 BindingUtil.ts 中,onAfterChange 处理程序现在会检查组祖先”——而不是“绑定系统已更新”。指明文件和函数,让观众能将旁白与屏幕上的内容对应起来。
  • 每个片段 = 一个变更或密切相关的一组变更。 如果你无法为该片段指出具体的 diff,那它可能过于抽象。
  • 以作者的身份撰写。 语气应是解释性的、自然的——就像带着别人了解你的工作。“所以这里的主要问题是……”或“棘手的部分是……”都可以。
  • 避免冗余,在引言和第一个内容片段之间。
  • 提及未展示的文件。 如果一个 PR 涉及 15 个文件,但只有 6 个有趣,请简要说明其他文件:“其余变更是类型导出和测试夹具。”
  • 总旁白目标为 5-7 分钟。

检查清单

  • 阅读所有 PR 提交并理解完整 diff
  • 在 SCRIPT.md 中撰写旁白(8-12 个片段)
  • 生成逐片段音频(Iapetus 语音)
  • 阅读 durations.json 以获取逐片段时长
  • 编写带有幻灯片类型、diff/代码和音频引用的 manifest.json
  • 使用 render.sh 渲染视频
  • 验证最终输出:1280×720 / 30 fps,音频同步,字幕可读,结尾存在

评论

支持 Markdown