对镜头素材处理
目标:将一段长录制或同一脚本的多个录制文件,自动生成紧凑的粗剪版本,由每一句台词的最佳拍摄片段拼接而成。语音直接衔接,每个单词完整,最终剪辑听起来像一次干净的录制,无需返工。
此技能专为 Claude Code 和 Runneth 构建。需要:
- ffmpeg 用于测量音频、抓取静态帧和剪辑视频。
- 所有语音的时间戳: 提供每个单词时间的语音转文字工具(Whisper, faster-whisper, whisper.cpp)。
- 评估画面的方法: 见下文“如何判断拍摄片段是否为最佳”。
- 检查脚本 位于
scripts/cut_check.py(依赖python3、numpy和ffmpeg)。
以日常平实的语言与剪辑师沟通,每次只提一个想法。
开始之前:工具
在Runneth中,所有工具已安装:跳过此节。
在Claude Code中,此技能需要 video-editing-setup 技能安装的免费视频工具。静默检查 ~/.video-editing-tools/bin/ffmpeg -version 是否可运行。如果可以,则在技能需要 ffmpeg、ffprobe 或 python3 的位置使用 ~/.video-editing-tools/bin 中的工具(ffmpeg、ffprobe、python),然后继续。如果不行,请先执行 video-editing-setup 技能,然后再返回此处。
步骤 0:开始前快速检查两项
-
文件确实是剪辑师想要的那个。 如果他们说这是一个补拍或新片段,且与之前某个片段的长度或场景相同,请在一行内询问后再开始。
-
按此格式提出一个问题,包含两个选项:
“您希望我先展示要保留的拍摄片段,还是直接进行剪辑?”
- 先展示给我看 → 遵循以下所有步骤,在步骤4处停下等待剪辑师确认。
- 直接剪辑 → 从头到尾执行所有步骤,不中断。最后一次性提及任何不确定的地方。
如果剪辑师已说明选择(如“直接剪”、“先展示拍摄片段”),则跳过此问题。如果无人回答,则视为“直接剪辑”。
步骤 1:获取语音信息
- 检查每个录制文件的长度、方向和帧率(使用
ffprobe)。如有多个录制文件,为每个文件指定一个简单标签(视频A、视频B、视频C),并对所有文件执行以下每个步骤。 - 从每个录制文件中提取音频:
ffmpeg -i IN.mov -vn -ac 1 -ar 16000 audio.wav。 - 标记每一段语音:
ffmpeg -i audio.wav -af silencedetect=n=-35dB:d=0.3 -f null -。静音之间的片段即为语音岛。每个单词都在某个语音岛内,且语音岛比任何转写时间戳更精确。 - 用单词时间戳进行转写。使用 faster-whisper:模型为
base,word_timestamps=True,condition_on_previous_text=False,并提供一个initial_prompt,列出视频中可能出现的人名和品牌词。更大的纯英文模型会悄悄清理重述部分,这会掩盖本技能需要看到的虚假开始。 - 长片段中,转写结果可能漂移或遗漏。每个语音岛都需要有对应的单词。当某个语音岛没有单词,或单词似乎在静音间缺失时,仅对该片段重新转写:窗口从语音岛前约1秒开始,到语音岛后约1秒结束,并关闭任何语音过滤器(
vad_filter=False)。 - 短时重新转写的窗口有时会凭空生成结尾处的单词(如“Thank you very much”、“Thanks for watching”)。只保留位于语音岛内且符合台词的单词;堆叠在同一时间戳的单词是生成的。
- 修正明显的姓名和品牌词误听。如果有脚本,将其作为台词的基准;如果没有,则以转写文本为基准。
步骤 2:分组拍摄片段
- 将素材拆分为台词(一个句子或一个想法),并将所有录制文件中每个台词的所有尝试分组。同一脚本的多个录制文件构成一个拍摄片段池:台词1同时比较来自视频A、B、C的每一次尝试,每个尝试标记其来源录制文件(“视频B,第3次”)。通常,尝试以相同的开头几个单词开始。
- 一个拍摄片段从一次尝试的开始起,到其最后一个单词的结束为止,直到下一次尝试开始。
- 将每个拍摄片段标记为 完整(说完整个台词)或 虚假开始(中途停止)。
- 台词内的每个短语算作单独片段。一个台词可以由不同拍摄片段中的最佳短语组合而成(如第一句来自第4次,第二句来自第2次),包括虚假开始中的干净片段。
步骤 3:评估每个拍摄片段
每个台词(包括只说一次的台词)的每个拍摄片段都要进行评估。每个短语的最佳版本胜出,无论它来自哪个拍摄片段或顺序。当段落之间的连接干净时(见规则7),从这些最佳片段构建台词。使用“如何判断拍摄片段是否为最佳”,并在拍摄片段文件(模板:templates/takes.md)中为每个拍摄片段写一行:台词、拍摄次数、时间、观察结果、保留与否以及简短理由。
如何判断拍摄片段是否为最佳
判断的内容在两种情况下一致,但观察方式取决于工具:
- 在Runneth中(或任何可将视频发送给可观看视频的模型如Gemini的设置),让该模型观看每个录制文件,并依据“如何判断拍摄片段是否为最佳”评估每个重复的台词,同时考虑语气、重音、笑话时机和犹豫。将您自己的选择从发送给它的内容中分离出来,以保持两个判断独立。当模型选择与您不同的拍摄片段时,采用模型的选择。始终从转写文本和语音岛获取时间,而不是从视频模型获取:其时间戳会漂移,有时会跳过台词。
- 在Claude中(无法观看视频),从它可以自行检查的三方面判断:
- 文字(转写文本):完整的台词,无磕绊、无填充词、中间无重启。
- 声音(每个片段的
astats):音量平稳,无削波,无爆音或噪音突发。 - 画面(静态帧):每个重复台词一张图,包含每个片段的堆叠,每秒2帧:
ffmpeg -nostdin -ss START -t LENGTH -i IN.mov -vf fps=2,scale=160:-2,tile=10xN take.jpg,每个片段一个,堆叠成一张图。查看这些图中与“如何判断拍摄片段是否为最佳”相关的所有内容。
- 带声音的屏幕录制: 评估措辞、声音,以及指示或点击是否与所说的内容匹配。
步骤 4:展示选择结果(仅当剪辑师要求先查看时)
- 展示剪辑清单:每个保留片段一行,包含
start-end和精确保留的文字。密切接近的选择标上“(接近选择:第2次同样干净)”,以便剪辑师知道注意哪些地方;明确选择无需注释。仅在需要解决的阻塞问题时添加一行简短说明(如无法辨识的单词)。 - 剪辑师确认、替换拍摄片段(“将台词4改用第2次”)或删除某句台词。他们的更改生效,即使后续视频模型反对。当替换时,如果未说明原因,用一个简短问题询问原因。他们的原因即是技能学习的方式(步骤7)。
步骤 5:构建剪辑
剪辑非常紧凑:语音各处直接衔接,包括保留的拍摄片段内部。短语之间的思考停顿和呼吸被剪掉。唯一的保持时间是结尾帧约0.5秒。
- 边界来自语音岛,而非转写文本。每个保留片段从其语音岛的第一个声音开始,到最后一个声音结束。第一个单词前的呼吸、唇音和处理噪音可能超过-30dB并被缝合到语音岛中,因此片段应在其第一个单词的开始处开始(电平跳升到语音水平,通常-25dB或更高),而不是在第一个细微的杂音处。片段开始或结束时的任何呼吸或噪音片段都要剪掉。结束方向相反:台词常常轻声结束(如“asleep at night”说得轻柔),因此即使最后一个单词比其他部分安静,片段也应在最后一个单词之后结束。检查剪辑的转写文本中是否包含每句台词的最后一个单词。
- 保留拍摄片段内的停顿 使用更敏感的检测
silencedetect=n=-30dB:d=0.2,并剪掉所有0.25秒或更长的停顿。拍摄片段内的重启和重复单词也需剪掉,只保留干净的最终措辞。 - 每个片段两侧各留约0.05秒的缓冲。 更多会留下停顿;更少会截断单词。然后将每个边界对齐到完整帧(起始时间向下取整到前一个帧,结束时间向上取整到后一个帧)。帧之间的边界会导致连接处重复或丢失帧,这会使每个后续剪切点移动一两帧,并在放置B-roll时让多余的帧通过。
- 短于约0.3秒的片段几乎总是残留(多余的一个音节)。除非它们包含一个完整单词,否则丢弃它们。
- 在每个连接处添加20毫秒的音频淡入淡出,并从工作副本开始一次重新编码以合并所有内容。原始文件保持不变。
- 将片段列表(
[[start, end], ...],以秒为单位)保存为clips.json;检查脚本会读取它。
步骤 6:在剪辑师看到之前检查剪辑
剪辑师看到的每个版本都已通过对该文件的所有检查。未运行的检查视为失败。
- 运行检查脚本:
python3 scripts/cut_check.py --voice audio.wav --clips clips.json --out CUT.mp4。它检查三项,并打印RESULT PASS或FAIL:- 结尾: 每个片段的最后50毫秒低于语音水平(-30dB)。如果不是,则单词被截断:将该结束时间向后移动到电平下降处。
- 开始: 每个片段开始前的30毫秒内无声音,确保片段不会在单词中间开始。
- 间隙: 剪辑中任何位置(除结尾保持外)无0.25秒或更长的停顿。
如果有多个录制文件,对每个录制文件单独运行一次:使用该录制文件的音频作为
--voice,只包含该录制文件的片段在clips.json中,每次都使用相同的剪辑作为--out。 如果声音来自与画面不同的文件(独立麦克风),则使用相同的剪辑位置同时处理摄像机的自带音频,并将其作为--camlock传入,以便脚本检查同步(每3秒偏差不超过40ms)。
- 每个单词完整出现一次: 用单词时间戳转写剪辑,并与剪辑清单逐词比较。连接处的半词意味着边界太紧。连接处的重复单词或短语意味着残留的重启。 单词之间无空音: 在该转写中,一个单词结束到下一个单词开始之间的间隙不得超过约0.3秒(除结尾保持外)。脚本中的间隙检查只发现安静停顿;充满呼吸的停顿足够响亮可以通过,因此单词间隙检查能捕捉到这一点。将片段修剪到其第一个单词处,并重新运行所有检查。
- 每个连接逐一检查: 对于每个剪切点,说出它之前的最后一个单词和它之后的第一个单词,并确认两者都被完全说出。转写每个连接点周围2秒的窗口进行检查(泛泛的“有问题吗?”会让截断的单词漏网)。在Runneth中,也让视频模型以挑剔的首次观众身份观看剪辑,并列出有问题的位置及时刻:截断或遗漏的单词、故障、冻结帧、突兀的跳跃。
- 长度: 剪辑的长度应与剪辑清单总长度一致。
修复所有失败项,重新剪辑,然后重新运行所有检查。
步骤 7:从剪辑师处学习
每次剪辑师替换拍摄片段、修改边界或说明某个拍摄片段更优的原因时,将该原因转化为通用规则,并合并到下面的“如何判断拍摄片段是否为最佳”中(如果涉及剪辑紧凑度,则合并到步骤5中)。保持列表简短:重写和合并规则,而不是堆叠新规则。只有剪辑师的原因能改变规则。
如何判断拍摄片段是否为最佳
这些规则来自一位剪辑师在她自己的对镜头视频中的真实选择。按顺序应用,然后随着剪辑师的原因逐步替换为属于您自己的规则。
- 完整清晰地说出整句台词。 磕绊或漏词的拍摄片段无论如何都不如干净的,即使画面再好。
- 脸上不能有遮挡物。 手或麦克风遮住嘴巴的拍摄片段被排除。
- 活力。 手势、动作和生动的表达优于静止、平淡的朗读。
- 最开心的表情。 在两个同样干净的拍摄片段中,真正的微笑和明亮的眼睛胜出。
- 表情与台词匹配。 在笑料上睁大双眼,在愉快的台词上展露真笑。
- 自然平稳的节奏。 不要急促,不要长时间停顿。
- 最佳片段、干净拼接。 最佳拍摄片段无论来自哪个录制文件都胜出,当每个部分都是更好的版本时,由两个拍摄片段组成的台词优于一个完整的拍摄片段。当说话者的头部和手部在两侧大致位于相同位置时(检查前后帧),或者当拼接点稍后被B-roll覆盖时,拼接是干净的。录制文件之间的切换还需要两侧具有相同的视觉外观(头发、灯光、构图、手中的产品);当外观变化时,切换应发生在台词之间,并会被B-roll或变焦变化覆盖。
- 简短有力。 当台词的其余部分重复已说过的内容或拖慢视频节奏时,只保留最强的部分(例如“直到我筋疲力尽”代替“我直到用完才知道它有多大帮助”)。
- 接近选择保持为接近选择。 当两个拍摄片段同样干净且画面无实质差异时,选择一个并标记为接近选择。静态帧无法体现语气,因此不要假装其中一个明显更好。
每次剪辑的规则
- 剪辑中的每个单词都来自音频。无法辨认的单词标记为不明,从不猜测。
- 时间来源于原始录制文件的时间线,剪辑清单中的每个片段都指明其录制文件。
- AI时间接近,但不精确。每个边界根据语音岛设置,并在音频中检查。