真实 UI 录制 + Remotion 合成。成片:video/out/sharednet-tutorial-zh.mp4、video/out/sharednet-tutorial-en.mp4(1080p30,约 2:40,云扬/Emma 配音 + swing 三重奏配乐)。
- 浏览器部分是真录的:本机
~/Desktop/SharedNet(main)跑在 :3001,Playwright 以拟人方式操作(弯曲缓动的鼠标轨迹、不均匀的打字节奏),CDP screencast 逐帧存 JPEG(DPR 2,3072×1728)。 - Agent 是真 CLI:
sn.sh在.state/里模拟两台机器(alex-mbp 上的 Claude Code + Codex,sam-laptop 上的 Cursor),用 Dashboard 复制出来的那条命令真实入座、发言、发起审批。SHAREDNET_WAKE=off,不会去拉起真的 Claude/Codex 会话。 - 终端画面是重绘的(
video/src/terminal.tsx):Claude Code / Codex 的界面是仿的,但命令、room/seat/message id、CLI 回显都取自本次录制的真实输出。 - 只做了两处外观替换:页面上的
127.0.0.1:3001显示成https://www.sharednet.ai(剪贴板保留真命令);隐藏 Next.js dev 角标。另外光标是按录到的坐标在合成层重画的(30fps 平滑)。
- 本机跑着 SharedNet(main,:3001,本地 Postgres
sharednet_dev)。 - 录制依赖:根目录
npm install(playwright-core,用~/Library/Caches/ms-playwright/chromium-1223里的 Chrome for Testing)。 - 合成依赖:
cd video && npm install remotion@4.0.504 @remotion/cli@4.0.504 @remotion/bundler@4.0.504 @remotion/renderer@4.0.504 react react-dom。 .state/不进仓库:第一次需要建两个本地测试账号(alex/sam@sharednet.test)、给 Sam 签一个 dev API key,并写好.state/agents/<agent>/{env,machine,workspace},格式见sn.sh顶部注释。
./prep.sh # 新建一个 "Alex Rivera" 账号 + 清空模拟机器 + Sam 的新 Room
node record.mjs # 约 3 分钟,产出 rec/frames + rec/log.json
node build-clips.mjs # -> video/public/clips/*.mp4 + video/src/take.json
cd video && node stills.mjs 20 60 120 # 抽帧检查
npx remotion render TutorialZH out/sharednet-tutorial-zh.mp4 --crf=18剪辑(放大、加速、字幕)全在 video/src/Root.tsx 的 SHOTS 里,时间用录制时打的 cue(如 'compose'、'c2+0.4' = 第 3 次点击后 0.4s)表达,重录后不用改数字。
- 旁白 = 字幕原文。
video/src/Root.tsx的cueSheet()按和字幕同一条时间轴导出每句的开始时间和可用时长。 video/narrate.py逐句生成配音:中文用云扬(zh-CN-YunyangNeural,+22%),英文用 Emma(en-US-EmmaNeural,+12%)。如果一句话会超出它的字幕时长,就自动再提速。- 现在走的是 edge-tts(Edge 浏览器朗读用的免费通道)。正式对外发布,建议换成 Azure Speech 官方接口,声音一样。
- 运行方式:
EDGE_TTS=<edge-tts 路径> TTS_PROXY=http://127.0.0.1:10080 python3 narrate.py
music/里的配乐全部由代码合成(numpy),没有用任何采样,没有授权问题。synth.py:calm / lofi / upbeatstyles2.py:piano / synthwave / marimba / garage / guitarjazz.py:swing / bossa / latenight- 成片用的是
jazz.py swing,按全片长度渲染。
- Remotion 只渲染画面(
--muted)。video/mix.py用 ffmpeg 混音:每句旁白按 cue 时间放入,音乐在说话时侧链压低,最后 loudnorm 到 -16 LUFS。
cd video
python3 narrate.py
python3 ../music/jazz.py swing <全片秒数> public/audio/music-swing.wav
npx remotion render TutorialZH out/picture-zh.mp4 --muted --crf=18
python3 mix.py zh out/picture-zh.mp4 out/sharednet-tutorial-zh.mp4- 空账号的 /chat 有内联「Schedule a Room」表单,和 + 弹窗里的表单同名,选择器要限定
section form。 - Invite 弹窗打开会自动滚到底;Copy command 在弹窗自己的滚动区里。「是否在屏」要按裁剪它的滚动容器判断,别用 elementFromPoint:首页的粒子层会让它误判成被遮挡,录出来就是开头上下来回滚的抖动。
- 跨账号 @ 默认被扣住,变成对方的「Allow an agent to respond?」Decision;Sam 那边由
samAllowAll()在镜头外放行。 - 同一 Room 里有两个同名 seat 时
@builder解析为空 → 每次重录都要给 Sam 新建 Room(prep.sh 已做)。 - 本地没配 Decision 优先级分类器,卡片上会显示「Classifying… · Provisional P2」,生产环境会是模型给的级别。
.state/存本地测试账号的密码、Sam 的 dev API key 和 cookie(只对本机 sharednet_dev 有效),别提交到任何仓库。