Wjs Editing Multicam — Wjs 多摄像头编辑
v0.1.0当用户拥有同一事件的2+个录制(每个都来自wjs-syncing-multicam的`.sync.json`副文件),并希望将它们合并为单个MP4文件 —— 根据音频能量每秒自动在摄像头之间切换,具有可选的画中画插入。触发器 —— "auto-edit multicam"、"做个剪辑"、"切几个机位"、"把这几个视频合成一个"、"combine these angles"、"PiP overlay"。
运行时依赖
安装命令
点击复制技能文档
wjs-editing-multicam 将 N 个同步的摄像机角度合并成一个渲染的 MP4 文件。决定是基于音频能量驱动的 —— 每秒钟音量最大的摄像机获胜。输出是硬切换(或硬切换加角落 PiP)。
这个技能是什么 —— 和不是什么 是
- 音频能量驱动的摄像机切换
- 面部 / 框架检测(无 face_recognition,无 MediaPipe)
- 单源音频(一个摄像机的麦克风)
- 多麦克风混音 / 每个扬声器门控
- 硬切换,带有可选的 PiP 插入
- 跨渐变 / 不透明度转换 / 滑动动画
- ffmpeg concat + overlay 滤镜渲染
- HyperFrames 组合 /
- 覆盖感知(不会选择在其 sidecar 窗口外的摄像机)
- 帧准确的节拍对齐 / VAD 边缘切换
如果需要面部跟踪、渐变转换、字幕或 HyperFrames 组合,请在此技能的 MP4 输出上使用 hyperframes 技能。
所需输入 原始摄像机文件(未修改)及其旁边的 .sync.json sidecar 文件。如果源尚未同步,请先运行 wjs-syncing-multicam 以写入 sidecar 文件。缺少 sidecar 文件 = 摄像机假设在 delta=0,全部覆盖。
autoedit.py 读取每个 sidecar 文件以获取 delta_seconds + overlap_in_reference,提取每个摄像机的单声道 PCM @ 16 kHz,从原始文件中。对数 RMS 包络在 1 Hz 帧率(每秒)。将每个包络提升到参考时间轴中,通过索引 t_ref - delta_k;未覆盖的秒数变为 -inf,因此它们永远不会被选中。
音频源 = 包络跨度最大的摄像机(90th - 10th 百分位数超过其覆盖的秒数),具有小的覆盖分数奖励。每秒得分:cam[k] - mean(其他覆盖的摄像机)。最高得分 = 最好的主动扬声器候选者。
编辑器决定 EDL —— 两个模式:
- 旋转(默认):随机停留在 [min_dwell=8, max_dwell=15] s,选择每个切换的最佳评分的覆盖摄像机(≠ 当前)。
- 贪婪:滞后 —— 保持当前,除非另一个摄像机的预测窗口得分超过它 --switch-threshold。最小停留时间 min_dwell=4,最大停留时间 max_dwell=18。两种模式都强制切换,如果主动摄像机在中途退出其覆盖窗口。
发出 EDL JSON。EDL 模式(edl.json) { "_about": "EDL 由 wjs-editing-multicam/autoedit.py 生成。时间在参考时间轴中。渲染脚本应用 ffmpeg -itsoffset deltas[k] 每个输入。", "_help": { "inputs": "原始媒体路径,以摄像机索引顺序(摄像机 0,摄像机 1,...)。", "deltas": "每个摄像机的 delta_seconds,从每个 sidecar 中。渲染使用 ffmpeg -itsoffset deltas[k]。", "duration_sec": "输出持续时间,在参考时间轴中。", "audio_source": "成为主音频的摄像机索引。单源 —— 不是混合。", "coverage": "[start, end] 每个摄像机在参考时间轴中。", "edl": "列表 {cam, start, end} 段。时间是参考时间轴秒。" }, "inputs": ["cam_a.MOV", "cam_b.MOV"], "deltas": [0.0, 12.345], "duration_sec": 4512, "audio_source": 0, "coverage": [[0.0, 4512.0], [12.345, 4499.835]], "edl": [{"cam": 0, "start": 0, "end": 13}, {"cam": 1, "start": 13, "end": 28}, ...] }
autoedit.py 将 _about + _help 直接写入文件,因此在任何编辑器中打开 JSON 都可以自我解释。
渲染脚本 scripts/render_cuts.py 硬切换。concat 滤镜图,按段修剪 + 缩放 + 填充。音频 = audio_source 摄像机,修剪到第一个 EDL 行的开始。 scripts/render_pip.py 硬切换 + 角落 PiP 覆盖。主摄像机 = EDL 行的摄像机;PiP 摄像机按轮流选择(或通过每行 pip 字段)。PiP 缩放到 --pip-width(默认 480 px),放置在可配置的角落,带有可选的白色边框。无渐变 / 无不透明度 —— 实心块开/关。两者都应用 -itsoffset deltas[k] 每个输入。
在运行前集思广益 三个真正的旋钮需要确认用户:
- 节奏 —— --mode rotation(变化的停留时间,更容易听)vs --mode greedy(能量跟随,更快)。
- PiP —— 是 / 否。如果是,哪个角落 + 宽度?
- 最小切换长度 —— --min-dwell 地板。8 s 默认值对于旋转是保守的;对话头可以去到 4。
- audio_source 是自动选择的;使用 --audio-source 覆盖,如果自动选择听起来不正确在 30 s 听取。
文件布局 working_dir/ cam_a.MOV # ORIGINAL,未修改 cam_a.MOV.sync.json # 从 wjs-syncing-multicam cam_b.MOV # ORIGINAL,未修改 cam_b.MOV.sync.json edl.json # 从 autoedit.py multicam_render.mp4 # 从渲染脚本