领夹麦克风同步
输入:摄像机片段 + 独立录音机音轨。输出:每个片段的独立领夹音轨 + 同步的 DaVinci Resolve 时间线。首次测试于一场现场演讲:5 个摄像机片段对应一段 59 分钟的录音机音轨。每个片段的对齐精度在 20 毫秒以内,远低于一个帧的时长。
为何重要: 在活动现场,摄像机麦克风通常摆放在房间的另一端,其录制的音频无法使用,而领夹麦克风是唯一清晰的声音来源。使用本技能后,<工作目录>/<片段名>.wav 和 <片段名>.json 将指向纯净的领夹音轨,因此后续的纸面剪辑(paper-edit)和视频成片(video-reels)技能可以直接读取到干净的音频和转录文本,无需任何额外调整。
设置:填写以下内容
| 占位符 | 说明 | 示例 |
|---|---|---|
<FOOTAGE_DRIVE> | 原始素材所在驱动器,每个视频一个文件夹,录音机文件在 audio/ 子文件夹内 | /Volumes/MyWorkDrive |
<SYNC_OUT> | 全质量领夹音轨切片的输出目录 | ~/Movies/<视频名称>-sync |
<LUT_PATH> | 可选的 LUT 文件路径,相对于 Resolve 的 LUT 文件夹 | MyLuts/SLog3-to-709.cube |
WHISPER_MODEL (环境变量) | whisper.cpp 模型文件 | 默认 ~/.cache/whisper-models/ggml-small.en.bin |
需求:ffmpeg、ffprobe、whisper-cli(whisper.cpp)、Python 3(numpy 和 scipy 会自动安装到 ~/.cache/lapel-sync-venv)、DaVinci Resolve(需运行 Resolve MCP 服务器以暴露 execute_resolve_code),以及 paper-edit 技能(用于获取 ensure_resolve.sh)。
运行
工作目录:会话暂存区,例如 <暂存目录>/<视频名称>。以下技能目录指 <暂存目录>/.claude/skills/lapel-sync。全质量切片输出至 <SYNC_OUT>。
zsh 陷阱: 在内联 shell 中,$ss:end_sample 会解析 :e 作为修饰符并静默吞掉文本。务必写成 ${ss}。
1. 查找素材和音频
<FOOTAGE_DRIVE> 上每个视频一个独立文件夹。录音机文件位于 audio/ 子文件夹:
find "<FOOTAGE_DRIVE>" -maxdepth 4 -iname "*<关键词>*" -not -path "*/.*"
ls "<文件夹>" "<文件夹>/audio"
例如,一台 Tascam DR-10L 会生成 000_YYMMDD.wav、001_... 等文件,每 15 分钟分段,并伴有 _D 后缀的双轨安全音轨(安全音轨音量约低 20 dB)。使用主音轨文件;保留 _D 文件用于修补削波位置。
2. 合并录音机分段文件
bash <技能目录>/scripts/stitch.sh "<文件夹>/audio" "<SYNC_OUT>/lav-master.wav"
它会读取每个文件的 BWF time_reference(自午夜起的样本数),并检查每个文件是否恰好从前一个文件的末尾开始。如果样本连续,则进行无损数据流复制。如果存在间隙,则会插入等量的静音,使主音轨保持墙上时钟时间,并提示间隙大小。
3. 转写两边音频
bash <技能目录>/scripts/transcribe_sources.sh "<文件夹>" "<SYNC_OUT>/lav-master.wav" "<工作目录>"
所有结果存放在 <工作目录>/sync/ 中,避免干扰 <工作目录> 本身(防止 paper-edit 技能的 sentences.py 误读)。摄像机音频首先会被强力增益(highpass=f=120,volume=40dB,alimiter,16k 单声道),因为其音量通常低至 -65 dB。已完成的转写任务会被跳过,因此可以安全地重复运行。
领夹音轨的转写速度约为每 8 分钟处理 1 小时音频。可在后台运行。Whisper 会将词级时间戳流式输出到 <工作目录>/sync/lav.log,当 lav.json 尚未写入时,align.py 会读取该日志,因此对齐过程可以在领夹音轨转写完成前开始。对于超出已转写范围之外的片段,仅会提示“仅匹配到 N 个单词”;待转写完成后重新运行即可。
4. 对齐
python3 <技能目录>/scripts/align.py "<工作目录>"
输出 <工作目录>/sync/sync.json。首次运行会创建 ~/.cache/lapel-sync-venv 并安装 numpy 和 scipy(系统自带的 python3 通常缺少这些库);之后会在该虚拟环境中运行自身。
工作原理(了解后可以信任或质疑其结果):
- 单词匹配优先。 寻找两个转录文本中共同出现的独特四单词组合。偏移量 = 领夹音轨时间减去摄像机时间。每个片段有数百个匹配点;在 0.5 秒内找最密集的簇,取中位数。精度可达约 0.1 秒。
- 波形精调。 针对每个 20 秒的摄像机音频块(短于 40 秒的片段使用 6 秒块),在 300-3000 Hz 范围内进行带通滤波(8 kHz 采样率),然后与领夹音轨在 ±0.6 秒的单词偏移范围内进行互相关计算。峰值超过中位数的 6 倍才被视为有效。如果多个块给出的偏移量在 20 毫秒内一致,则采用;对于超过 5 分钟的片段,会通过线性拟合得出偏移量和漂移量。
阅读其打印的每一行输出:偏移量、达成一致的块数、离散程度、漂移量。任何标记为 请通过听感检查 的片段(少于 3 个达成一致的块)可能存在高达 0.1 秒的误差;请在 Resolve 中手动检查其同步情况。如果漂移量在一个片段内超过一帧,也会被标记。
5. 为每个片段裁剪领夹音轨
python3 <技能目录>/scripts/slice.py "<工作目录>" "<SYNC_OUT>/lav-master.wav" "<SYNC_OUT>"
为每个片段输出:
<SYNC_OUT>/<片段名>_lav.wav:24-bit,源采样率,与片段时间精确到样本级别,因此领夹音轨时长等于片段时间<工作目录>/<片段名>.wav(16k 单声道)和<工作目录>/<片段名>.json(该切片的 Whisper 转写):供paper-edit和video-reels读取<工作目录>/clips.ndjson,其中"audio"字段指向切片文件
如果录音机在某个片段开始时启动较晚或提前停止,相应部分会填充为静音并给出警告。切片的 Whisper 转写需要几分钟;之后不要再运行 paper-edit 的 transcribe.sh(它也会因为 JSON 文件已存在而跳过)。
6. 检查削波
slice.py 会计算每个切片中采样值达到或超过 0.99 满量程的样本数,并打印最长连续运行长度。数百个短运行时(几十个样本)通常是录音机的限制器捕捉到的峰值,而非失真。只有当列出需要一听的片段(运行时达到 1 毫秒或更长),或用户反应声音听起来刺耳时,才从 _D 安全音轨中修补这些时刻(时间相同,音量提升约 20 dB)。
7. 在 Resolve 中构建时间线
bash .claude/skills/paper-edit/scripts/ensure_resolve.sh
写入 <工作目录>/sync/params.json:
{"project":"<视频名称> - YYYY-MM-DD","work":"<工作目录>","name":"Synced - Full Talk",
"lut":"<LUT_PATH>"}
lut 是可选的;不使用则省略。然后调用一次 execute_resolve_code:
PARAMS = "<工作目录>/sync/params.json"
exec(open("<技能目录>/scripts/resolve_sync_timeline.py").read())
它会创建项目(如果名称已存在则打开),导入摄像机片段和领夹音轨切片,并构建名为 Synced - Full Talk 的时间线:每个片段在 V1 轨上仅视频(mediaType: 1),其对应的领夹音轨切片在 A1 轨上仅音频(mediaType: 2),入点和出点相同。recordFrame = 时间线起点 + round((偏移量 - 最早偏移量) * 24000/1001),从而保留片段间的实际间隙。LUT 应用于节点 1。摄像机的原始音频被保留。时间线分辨率:如果片段是竖屏则为 1080x1920,否则为 1920x1080(可通过 width/height 覆盖)。如果时间线名称已存在,则添加 v2 后缀,而不会修改现有时间线。
请让用户在几个片段中检查有清晰辅音(如“p”或“t”)的位置。这是最可靠的同步检查方式。
8. 交接
告知用户时间线已创建、第 4 步中每个片段的同步数据(每行一个片段)、任何被标记的片段以及削波检查结果。然后继续:paper-edit(跳过其转写步骤,因为 <工作目录> 已准备就绪)或 video-reels。
值得记住的事实
- 仅靠波形相关性失败了。 在摄像机麦克风平均音量约为 -65 dB 的混响房间内,对整个片段和 20 秒块进行宽窗口波形相关性计算得到的结果不一致且杂乱。先词对齐,再窄窗口波形搜索,是有效的方法。
- 摄像机时钟通常不准。 测试中,摄像机比录音机慢了约 149 秒。部分相机(如索尼)写入的
creation_time是 UTC(以Z结尾),而录音机的 BWFtime_reference是本地时间。creation_time最多只能作为粗略参考。单词匹配不需要它。 - 漂移可忽略不计:在测试的设备上,约为 0.5 毫秒/分钟,一个 21 分钟的片段累计漂移 11 毫秒。无需进行时间拉伸。较短片段报告为 0,因为几秒内的拟合仅包含噪声。
- 重新运行脚本生成的手动切片差异在 6 毫秒以内。
- 竖屏素材: 例如,一台索尼相机拍摄的竖屏视频会以 23.976 fps 记录为 4K,并带有
rotation=90的显示矩阵。Resolve 会将其识别为 2160x3840,无需额外旋转修正。 - 示例录音机,Tascam DR-10L: 48k 24-bit 单声道 BWF 格式,每 15 分钟生成一个新文件,
_D安全音轨音量低约 20 dB。 paper-edit的resolve_build.py仍会从摄像机文件中获取音频。 在领夹麦克风场景下,它构建的时间线会包含摄像机的原始音频,直到它学会读取"audio"字段。在此之前,告知用户,或将 A1 轨重新链接到_lav.wav切片;由于切片时间等于片段时间,所以剪辑点是一致的。- Whisper 运行时使用
-oj -ml 1 -sow参数以启用词级时间戳。
相关技能
paper-edit:粗剪,在本技能之后运行video-reels:读取clips.ndjson;"audio"字段指示其从切片中获取音频(如果已安装)