videolabs: 创建视频(终端课程风格)
所有内容都存在于一个仓库中,/opt/opus/videolabs (github.com/bigzaqui/videolabs,公开:绝不要提交凭据;在提交前运行 scripts/check-secrets.sh)。此技能的源文件位于该仓库的 skills/videolabs_create-video/ 中,通过 scripts/setup.sh 链接到 ~/.claude/skills。其配置值(ElevenLabs 和 Cloudflare 密钥、渲染主机)位于被 git 忽略的仓库根目录下的 .env 文件中,详见 .env.example。
该仓库包含:一个通用的引擎(Remotion + ElevenLabs 工具集,一个 npm ci 命令)以及 labs/ 下的每个实验室一个文件夹。用于制作“kafka - 快速入门”(26课/18分钟)、“ceph - 入门步骤”(14课/11.6分钟)以及更早的“ceph 快速入门”(41课/30分钟;其脚本和视觉效果保存在 reference/ 中,作为卡片、对比、仪表盘、树图、事件、状态和站点的示例)。当前的两个实验室都是实战性的:每个命令都首先在真实实验室上运行(参见“真实实验室”)。
/opt/opus/videolabs/
engine/ 通用工具集:engine/cli.mjs <lab> <command>,src/ (Remotion),scripts/
labs/<slug>/
lab.config.json 目录条目(标题、描述、标签)+ 外观(品牌、logo、状态栏、工具名称、示例剪辑)
script/script.md 叙述,每个场景对应一个 ### 标题
visuals.ts 每个场景显示的内容 + 提示覆盖
lab/ create-lab.sh, destroy-lab.sh, cap.sh, captures/, lab-meta.json
audio/raw/ ElevenLabs 输出(付费;提交到 git)
out/ data/ public/voiceover/ 生成的文件,被 git 忽略
site/ scripts/publish-all.sh state/ 网站及其发布(技能 videolabs_publish)
script.md ──cli scenes──▶ data/scenes.json
└─ cli tts (ElevenLabs 单词时间戳) ─▶ audio/raw ──pace──▶ public/voiceover/* + data/{durations,captions}.json
visuals.ts + engine/src/cues.ts (reveal = 单词被说出时) ─▶ Remotion ──cli render──▶ out/<slug>.mp4
cli player ─▶ out/player/{chapters.json,lab.json} ──videolabs_publish──▶ videolabs.pages.dev
所有命令:node engine/cli.mjs <lab> <scenes|tts|pace|cues|stills|typecheck|studio|sample|render|player>,以及 list 和 new <slug>(仓库的 README.md 有完整的表格)。实验室文件夹是不同课程之间唯一不同的部分;切勿将引擎代码复制到实验室中。
工作流程(与用户就大纲和脚本达成一致;之后的所有操作无需询问即可运行)
用户授予的持续授权:一旦脚本达成一致,无需为每一步请求许可,即可直接运行付费的 ElevenLabs tts、提示审核、在渲染主机上渲染、播放器/海报步骤以及实验室拆除。之后报告结果和新视频长度。仅当出现此工作流程之外的事项时才停止询问(例如,对实验室未创建的资源执行破坏性操作,或者当用户未要求时发布到网站)。
- 实战主题?先构建并运行一个真实的实验室(参见下面的“真实实验室”)。切勿凭记忆编写命令输出:ceph-first-steps 脚本的初稿在真实集群上运行时,有十处错误。然后根据真实发生的情况编写脚本。
- 先脚本,后视频。 提出一个大纲(模块 → 课程,并规划每节课的图表),获得同意,然后编写
labs/<slug>/script/script.md。更改文本远比重新生成音频便宜。以速度 1.0 并带有停顿 ≈ 160 wpm 标记:1,500 个单词 ≈ 9.5 分钟(含模块卡片);在 1.08 倍速下 ≈ 168 wpm。在tts之后立即检查Paced … total X min行以了解真实长度,并在渲染前进行调整。对于特定版本的命令,请先检查当前版本(文档和项目的下载/镜像标签)。格式(由engine/scripts/build-scenes.mjs解析):
在模块最后一课结束时加上“模块要点:…”——单词 takeaway 会触发要点窗口。## 模块 1 — 核心架构 ### S02 · RADOS **可视化:** 给设计师的备注 **命令:** `ceph -s` `ceph osd tree` (可选,仅屏幕显示) **旁白:** 一个段落,用于口头讲述。按口语拼写命令(“ceph dash s”)。 node engine/cli.mjs new <slug>会搭建labs/<slug>/目录(配置文件、一个包含三个场景的脚本、一个 visuals.ts);填写lab.config.json(目录的标题、描述、标签;外观的brand、user、logo、statusbar、statusCmd、networkLabels、session、tools、sample)并运行node engine/cli.mjs <slug> scenes。- 可视化 在
labs/<slug>/visuals.ts中(类型在engine/src/types.ts中,colors来自@engine/theme),每个场景 ID 对应一个条目。图表种类(engine/src/components/Diagrams.tsx):topology:在一个或两个网络线上的主机(名称、IP、守护进程芯片、磁盘/副本)、一个可选的状态行(statusCmd标签,默认为配置中的值)以及flows(在主机之间移动的块,从cue开始直到until)。任何主机/守护进程/磁盘/状态都可以有一个cue(当被说出时出现)和changes: [{cue, state, label}](磁盘empty|osd|leader|idle|down|destroyed|new,主机ok|spare|down|maint)。没有提示的元素从一开始就可见。使用辅助函数(host(n)、built(n))构建实验室状态,以便图表逐课展开。session:一个真实的 shell;每个cmd在被说出时逐字键入,然后打印其out行;保留最新的行。host设置整行的提示符(默认为配置中的session.prompt)。上方有一个可选的紧凑型topology。对于会话场景,底部命令栏是隐藏的。 其他种类:title(fastfetch 卡片;user、额外的rows)、layers、cards、flow、tree、compare、gauge、terminal(带标注的命令输出)、incident(症状/检查/行动)、checklist、states、sites;每个场景可选takeaway和commandCues。对于每个out和symptom,使用lab/captures/中的真实输出。
- 在支付音频费用前审查视觉效果:
node engine/cli.mjs <slug> stills(每个场景一帧,缩放至 60%;S04,S11 --at=12.5用于特定时刻)并使用 ffmpegxstack拼成 2×2 的联系表进行查看。检查溢出、换行和空布局。 - 语音:仓库根目录下的
.env文件包含ELEVENLABS_API_KEY/ELEVENLABS_VOICE_ID,然后运行node engine/cli.mjs <slug> tts(直接运行,无需批准;跳过已存在的;--only=S03,S04 --force仅重新生成叙述更改的场景,这也能节省点数)。它会自动运行pace。当脚本变长时,告知用户新的长度。提交labs/<slug>/audio/(付费输出)。 - 同步审核:
node engine/cli.mjs <slug> cues(在对任何命令进行更改后,也要更改其commandCues条目:一个用于dmesg的芯片仍然提示在 "smartctl" 上,这是一个无声的不匹配)—— 列出揭示每个元素和每个命令行的单词(MISS= 在相邻元素之间分散)。使用提示覆盖修复错误的匹配(如下)。在任何叙述或语音更改后重新运行。 - 预览:
node engine/cli.mjs <slug> sample(在渲染服务器上约 1 分钟),使用ffmpeg -ss <t> -frames:v 1抽查帧,使用volumedetect检查音量(无削波:最大值 < -1 dB)。 - 渲染 完整课程(远程,如下),然后
node engine/cli.mjs <slug> player,制作海报labs/<slug>/out/spot.jpg,销毁实验室虚拟机,并使用 videolabs_publish 技能发布整个站点。
用户已签字同意的设计规则
- 外观:Omarchy/Hyprland 桌面,Tokyo Night 调色板(
src/theme.ts;像rados、mon、cephfs这样的语义名称只是调色板槽位)。Waybar 条,模块作为工作区 0–7,一个状态(在故障排除模块上显示HEALTH_WARN),课程计数器07/41。一个平铺窗口,2px 方形边框,无圆角/发光/渐变。每节课以键入的 robbyrussell 提示符➜ module-1-core-architecture git:(s03) cat the-daemons.md开始,然后是# Heading。备注渲染为// comments,命令渲染为❯ cmd行,要点渲染为浮动窗口cat sre-takeaway.md,模块分隔符渲染为cd module-N-…+ 大标题,开场白渲染为 fastfetch。全部使用 JetBrains Mono;Inter 仅用于字幕。它取代了用户不喜欢的“通用”深色渐变外观。 - 揭示同步:绝不按时间交错。每个元素在叙述者第一次说出其名称时出现(
engine/src/cues.ts:最长有意义的令牌,前缀词干化,从前一个提示向前搜索)。在实验室的visuals.ts中的覆盖:cue: "=managers"精确的口头单词(忽略连字符/标点:如"=write-ahead"),cue: null= 无单词,插值。pointCues(对比侧),noteCue(树图),linkCue(站点),cues(标题徽章),commandCues(命令行,每个场景)。cueLabels()中的标签顺序必须与engine/src/components/Visuals.tsx中渲染器使用的索引顺序匹配(topology/session 使用topologyKeys/sessionKeys中的键遍历)。- 一个正向游标在一个场景的所有标签上运行,因此一个重复使用常见单词(如“three”、“recovery”、“add”)的后续标签可能会落在后面的出现位置。首选在那一刻只出现一次的单词(如“=repeat”、“=begins”),并且始终阅读
cues审核报告。
- 字幕:纯短语,在句子/从句结尾断开,单行 ≤ 64 个字符,无逐词卡拉 OK 高亮显示(“令人精疲力竭”)。
- 呼吸/停顿:
engine/scripts/pace.mjs在原始 ElevenLabs 音频中,在. ? !后插入 250 毫秒,在: ;后插入 120 毫秒,并移动单词时间戳。裁剪点来自 ffmpegsilencedetect(严格的 -38 dB/40 ms,然后是 -30 dB/20 ms),否则为最安静 5 ms 窗口,带有 8 ms 淡入淡出——绝不单凭 ElevenLabs 单词边界(这些边界会落在单词内部)。调整SENTENCE_PAUSE/CLAUSE_PAUSE,重新运行node engine/cli.mjs <slug> pace(免费;原始音频保留在audio/raw/中)。停顿处理取决于 ffmpeg 版本(对于相同的 Kafka 音频,7.1.5 和 8.0.1 版本分别给出了 17.9 和 17.8 分钟):在渲染的位置进行停顿处理,或者将data/和public/voiceover/复制到渲染主机。 - 过渡:通过
@remotion/transitionsTransitionSeries实现 15 帧交叉淡入淡出;它们仅重叠静音引入(0.4 秒)/ 尾部(0.8 秒)。engine/src/timeline.ts减去重叠部分;engine/scripts/player.mjs和stills.mjs镜像这些常量——保持它们同步。 - 用户选择的语音:ElevenLabs "Mark"
v3p1kjzUvro6S76qmYmH(语音库;适用于 TTS,尽管GET /v1/voices/{id}返回 404),eleven_multilingual_v2,速度 1.08,with-timestamps端点,previous_text/next_text用于连续性。在进行完整运行前,始终使用一个句子的 TTS 调用验证语音 ID(之前.env中的一个 ID 被截断了)。
每门课程的设置(labs//lab.config.json)
Waybar 中的工作区以及 fastfetch 模块/课程数量会自动跟随场景。其他课程特定的所有内容都是配置文件中的数据,而非代码:brand(Waybar 名称)、user(标题卡片上的提示符)、logo(五行块状字母)、statusbar(ok/warn 文本、right 文本、warnModule = 显示警告的模块)、statusCmd 和 networkLabels(拓扑场景的默认值)、session.prompt/session.title(终端会话)、tools(自动提示命令行时跳过的命令词)、sample(示例剪辑的场景 ID),以及用于目录的 title、description、tags。具有不同外观的新实验室无需更改引擎;新的图表种类或状态则需要更改(engine/src/types.ts、组件、cues.ts),然后会使每个实验室受益。
应用的 Remotion 实践(来自 remotion-* 技能)
通过 @remotion/google-fonts 使用字体(绝不依赖系统字体);使用来自 @remotion/media 的 <Audio> 并带有 from 属性;TransitionSeries;calculateMetadata 根据音频持续时间调整合成大小;使用 interpolate/Easing.bezier 和 translate 进行动画处理(无 CSS 过渡)。有意未采用的实践:Interactive.create/连接的合成(仅对在 Studio 中手动编辑有用)、大型社交媒体视频文字最小值、模因音效。
渲染
VIDEOLABS_RENDER_HOST(渲染和实验室服务器的 SSH 目标,密钥已设置)来自仓库根目录下的.env:在执行以下命令之前,运行set -a; . /opt/opus/videolabs/.env; set +a,并在提交的任何内容中写入该变量,而不是字面地址。同一台主机运行实验室虚拟机。TMPDIR=/var/tmp由 CLI 设置:/tmp在这些主机上是一个小的 RAM tmpfs,长时间的音频混合会在最后失败并显示“设备上没有剩余空间”。- 本地机器(4 vCPU)大约需要 45 分钟来处理 30 分钟的视频。远程渲染服务器 $VIDEOLABS_RENDER_HOST(Ubuntu 26.04,24 线程,31 GB RAM,RTX 3090 Ti,KVM;Node 22,ffmpeg,rsync 以及来自 apt 的 Chromium 库)以 12–16 的并发度在 3 分钟内渲染 11.6 分钟的 ceph 课程,在 5 分钟内渲染 18 分钟的 kafka 课程。仓库位于两台机器的
/opt/opus/videolabs。流程:(为了保持与你审查的时序一致,rsyncrsync -az --delete --exclude node_modules --exclude out --exclude .env --exclude .git --exclude site --exclude state ./ $VIDEOLABS_RENDER_HOST:/opt/opus/videolabs/ # 从 /opt/opus/videolabs ssh $VIDEOLABS_RENDER_HOST 'cd /opt/opus/videolabs/engine && npm ci && cd .. && node engine/cli.mjs <slug> scenes && node engine/cli.mjs <slug> pace && node engine/cli.mjs <slug> render --concurrency=12 --log=error' rsync -a $VIDEOLABS_RENDER_HOST:/opt/opus/videolabs/labs/<slug>/out/<slug>.mp4 labs/<slug>/out/ && node engine/cli.mjs <slug> playerlabs/<slug>/data和public/voiceover,而不是在服务器上运行scenes/pace。)切勿将.env复制过去。在后台运行长时间渲染(使用ssh -f -n启动的nohup bash script &),并使用time命令real行的监视器等待;不要 grep 日志中的“error”(命令行中已有--log=error)。如果 Waybar/图表代码发生更改,请检查两个实验室(stills),因为它们共享这些代码。 - 验证引擎更改:使用新旧代码渲染示例剪辑,并比较
ffmpeg -i a.mp4 -i b.mp4 -lavfi ssim -f null -;重新生成的scenes/durations/captions.json和cues审核必须……