OBS / 演讲录音) 是做什么的?
脚本工具包:此技能中的 scripts/(prep.sh、transcript_txt.py、ocr_scan.py、
cam_flags.py、df_enhance.py、build_edl.py、render.py、verify_output.py、
chapters.py、overview.py、job.example.json、selftest.sh)。在 <workdir>/scripts/ 的副本中运行它们,以便作业永远不会依赖于技能目录。scripts/selftest.sh 可在几分钟内端到端验证安装。
输入
<src>:录制文件,通常是~/Desktop/*.mov中最新的(OBS 命名YYYY-MM-DD HH-MM-SS.mov)。如果有多个最近的录制文件,请在启动耗时长流程前确认是哪一个——剪辑错误的视频会浪费一个小时。<target_minutes>:如果未给出则询问;典型的 45-90 分钟源文件对应 20-30 分钟目标时长。<workdir>:新的临时目录,例如/tmp/<session>/edit。- 工具链:
ffmpeg6+、python3、mlx_whisper、DeepFilterNet3、Pillow/numpy/soundfile、Apple Vision(pyobjc-framework-Vision/-Quartz)。
步骤
- 侦察,绝不触碰源文件。
ffprobe <src>(分辨率、帧率、音频)。仅从<workdir>中的副本/提取文件工作。 - 准备。
prep.sh "<src>" $W:orig48.wav、mono16.wav、frames/(每 2 秒 1 帧)、camall/(网络摄像头裁剪,1 fps,几何形状自动从分辨率推导)、sheet_overview.jpg、silences_raw.txt(仅用于诊断)和transcript.json(mlx_whisper large-v3-turbo,带单词时间戳,约 4 分钟)。Whisper 在 prep 返回后继续在后台运行——立即启动df_enhance.py orig48.wav df_enhanced.wav(DeepFilterNet3,CPU 上约 6 分钟),它是独立的。 - 阅读整个转录(通过
transcript_txt.py ... --from/--to分成 3 块)。将保留范围(源文件秒数)写入job.json -> keep。静音修剪很小(47 分钟中约 2 分钟);真正的时长节省来自内容决策。Whisper 自我指示:一个短语重复 10 次以上或空片段序列 = 死区,剪切它。 - 视觉静态检查。 "没有任何事情发生"通常视觉上是静态的,而不是无声的。在 0.5 fps 帧上进行帧差分:在 ~10 秒窗口内 <1% 像素发生变化 = 非活跃;合并由 <=1 秒毛刺分隔的连续片段;需要 >=4 秒才能剪切;0.5 秒手柄。闪烁的光标/转轮模拟活动——遮盖或提高阈值;检查中间帧是否出现变化并恢复的突发。打字是噪音,而不是静音:门控使用增强轨道的 RMS(
pause.loud_db),绝不使用原始silencedetect。 - 开场。 在第一个真正内容之前剪掉所有内容。如果开头缓慢/无聊,则从最有趣的演示开始;可选择使用 1-2 个精彩片段然后加速开场。绝不以墙纸/黑色画面开场——前 30 秒的卡顿看起来像垃圾内容。
- 泄露扫描(在您阅读时运行)。 在
frames/上run之后运行ocr_scan.py scan:IPv4/v6、*.ts.net、电子邮件、API 密钥、ssh 材料。在每个终端/配置密集范围上进行密集的 2 fps 扫描(ocr_scan.py dense src.mov dense 600:60 ...)。静态文本 ->boxes(实心填充);滚动的终端输出 -> 对整个窗格进行模糊处理(blur窗口 +blur_regions,使用源像素)——逐行框会遗漏样本之间的文本。127.0.0.1/localhost没问题。在job.json中记录每个窗口。 - 隐私剪切(网络摄像头)。
cam_flags.py(通过 Vision 检测手靠近嘴)+cam_flags.py --sheet S:DUR对每个标记的聚类进行 2 fps 缩放查看。剪切用户不会发布的时刻:暂停时手在脸部、离开座位、其他人入镜、全屏脸部片段。普通的思考姿态和小手势没问题。绝对不要在句子中间为手势而剪切——捕捉到最近的句子边界并记录丢失的内容。 - 用户时间戳剪切是字面的。 "剪切 0000 - 01:12" -> 将其放入
job.cuts(或keep),精确应用,然后重新检查接缝处的节奏。 - 音频母带。 合理性:增强版与原始版的相关系数 > 0.95(
df_enhance.py会打印它)。默认audio_chain是高通 75,在 220 Hz / 3.2 kHz / 9 kHz 处进行剪切/提升,去嘶声器,3:1 压缩器;通过添加equalizer项来切除任何持久的风扇音调;render.py执行两次通过的 loudnorm 到 -16 LUFS / -1.5 dBTP。未处理的麦克风保留为音频轨道 2。 - EDL + 渲染。
build_edl.py->segments.json(keep 内部 >0.7 秒的暂停缩短为 0.4 秒;捕捉到探测到的帧率;<0.3 秒删除)。render.py:分组并行h264_videotoolbox编码,-c copy拼接,混流两个轨道。可按组重启——如果一个组失败,修复后重新运行,未更改的组将被跳过。除非另有要求,否则交付 1080p(4K 渲染时间翻倍,但无明显收益)。 - 交付前验证。
verify_output.py退出非零,除非帧数匹配 EDL,两个音频轨道与视频时长匹配,并且输出在每个脱敏窗口以 4 fps 进行的 OCR 发现零泄露。还要在每个隐私剪切处以及 0:00-1:00 提取输出帧并查看它们。在材料敏感时使用--full进行全文件泄露扫描。 - 重新转录新剪辑。 转录、字幕和章节必须匹配输出时间线,而不是源文件——在写入 SRT/章节前,对剪辑重新运行 Whisper(或使用
chapters.py,它通过 EDL 重新映射)。绝对不要使用源自源文件转录的 SRT 时间码。 - 元数据+交付。 无需二次询问:章节时间戳(
chapters.py,>=3,第一个在 0:00),2-3 个标题选项,约 30 个标签(500 字符以内,按需提供 +2 标签/描述变体),描述,每种语言的 SRT(约 42 字符/行;多语言按要求),全部为纯文本。通过 youtube-pack 生成缩略图:读取用户最后 10 个缩略图的样式;默认白色/奶油色背景 + 黑色文本,除非要求否则不包含人脸,标志/文本大且清晰。然后将<output>.mp4(以及 SRTs/元数据)移动到~/Downloads并使用默认应用程序打开文件/打开文件夹。
输出
<Topic> - YYYY-MM-DD - edit.mp4(1080p,2 个音频轨道)位于~/Downloads,在默认应用程序中打开。<workdir>:job.json、segments.json、group_bounds.json、vf_<n>.txt、脱敏/隐私日志。~/Downloads:SRTs、metadata.txt(标题、描述、标签、章节)。- 按需可选:从同一源文件拆分的主题子视频,每个都有自己的章节。
与以下组合
whisper/DFN3(工具包内) -> video-edit(经过验证的 mp4 在 ~/Downloads) -> youtube-pack(thumbnail.png) -> 浏览器上传到 YouTube Studio,配置如用户其他视频。
规则
- 绝不修改源录制文件。
- 目标时长是一个目标;如果内容在不破坏演讲的情况下无法缩短,则稍微超出并命名后续两个剪切点。
- 在修剪更多暂停之前,先剪切整个跑题内容;保留与硬件相关的讨论、现场演示、诚实的注意事项、总结回顾。
- 始终以:移动到 ~/Downloads + 打开结束。
- 如实报告:您无法听到结果——请说明并指向轨道 2。
- 单独录制的音频(Discord/Craig)通常不存在;检查 Desktop/Downloads/iCloud/Voice Memos,如果不存在则使用 OBS 麦克风并说明。
- 当要求释放空间时:删除旧的未接受草稿/变体,保留源文件和最终文件。
- 多人播客:找到正确的音频轨道(通常是多个中的 2 个),将音频映射到正确的说话者,切换视频以对应说话中的人,清理过渡。
- 绝不要因为渲染完成就声称编辑完成;只有
verify_output.py+ 人工查看帧才能确定。
验证
verify_output.py打印 PASS(帧数、两个音频轨道、窗口中无泄露)并退出 0。- 在 0:00-1:00 以及每个隐私剪切处人工查看帧(
verify_output.py --sheet 0:60)。 - 对剪辑进行 SRT 重新转录的抽检。
- 使用
ffprobe检查交付文件:时长、分辨率、帧率、2 个音频流。
已知失败
- 剪辑了错误的视频 -> 在流程启动前确认最新/主题匹配。
- 字幕/章节基于源文件时间线 -> 在剪切后重新转录(步骤 12)。
- 输出中可见主机名 -> OCR 样本之间的滚动文本;模糊整个窗格,加宽窗口,然后进行输出端 OCR。
- Silencedetect 剪切打字/视频播放 -> 使用增强音频 RMS 门控(
pause.loud_db),而非仅凭静音。 - 卡顿的黑色开场 -> 验证输出 0:00-1:00 帧,在真正内容上冷开场。
cp -i覆盖提示 / 沙盒 TCC 阻止~/Downloads-> 使用禁用了沙盒的/bin/cp。- ffmpeg 滤镜在超过 ~100 个
between()项时死亡(“无法分配内存”) -> 减少render_groups。 - 闪烁的光标在帧差分中被计为活动 -> 遮盖/调整阈值,检查中间帧。
render.py帧不匹配 -> 它拒绝拼接;读取render_<n>.log(-ss在非关键帧之前,解码器损坏)。- 读取
transcript.json时 Whisper 仍在运行 ->transcript_txt.py报告空片段;等待whisper.log完成。
示例
- 2026-09-02 演讲,47 分钟 -> 26 分钟:32 个保留范围,2 个隐私剪切(9:52-9:58,26:08-26:14),2 个模糊窗口 + 1 个用于
*.ts.net主机名的框,渲染约 2 小时实际时间,仅由输出端 OCR 捕获的泄露。 - 2026-09-24 教学 OBS 会议:用户拒绝了第一个剪辑(“背景噪音,打字,开头非常无聊”) -> 移除打字噪音,选择更好的开场,重新生成转录,然后字面头部剪切
0:00-1:12,泄露扫描,白色/奶油色缩略图搭配更大的标志,2 个元数据变体,移动到 Downloads。 - 播客(2026-09-18):1 小时 40 分钟视频 + 5 个候选音频文件 -> 识别出正确的 2 个每说话者轨道,将语音映射到视频,1080p,修剪开头,SRT + 西班牙语/阿拉伯语/印地语翻译,章节时间戳,包含两位说话者的缩略图,文字在上方,无黑色背景。