cut-rough
机械式剪辑:死寂、填充词、口误、拼接卫生。这里的所有操作都是可逆的,每个决策都带有数据来源。
参数 $project 是项目短名称;完整请求在 $ARGUMENTS 中。空的 $ 占位符表示该技能是自动触发的,而非通过斜杠命令调用:从会话上下文的 PIPELINE_STATE 中获取项目,并说明选择了哪一个。动词名称遵循构建合约;若有冲突,以 python engine/ve.py --help 为准。
前置条件及为何严格
先运行 cut-story。如果 story.blocks 缺失,请说明情况并提供 cut-story 选项,而非继续执行——对即将被整体丢弃的素材进行机械式剪辑,是在浪费口舌检测和填充词检测的时间。
如果用户明确希望对所有内容进行机械式处理(例如“ einfach nur die Ähms raus”),这是合理的。执行该操作,并说明未进行任何内容选择。
帧规则
剪切点必须对齐到整帧。这并非追求整洁——偏离帧网格的剪切点会使 concat 过滤器在每个剪切点累积 8.33 毫秒的 A/V 漂移,150 次剪切后漂移可达 1250 毫秒。对齐后,同样的 150 次剪切产生的漂移正好为 0 个采样点。
in_frame = floor(t_in * fps + 0.5) # 向上舍入(半向上)——平局时取较晚帧
out_frame = ceil (t_out * fps - 0.5) # 向下舍入(半向下)——平局时取较早帧
if out_frame <= in_frame: out_frame = in_frame + 1
out 是排他性的——ffmpeg 实现 trim=end 时为严格的 <,半开区间 [in, out) 是精确拼接的关键。如果 OUT 舍入错误,每个片段都会变成一帧长;200 次剪切将产生 6.7 秒的漂移,导致可听到的同步问题。
永远不要手动进行此算术运算。vedit/timebase.py 负责此操作:snap_in / snap_out。
步骤 1——执行遍数
python engine/ve.py cut $project
对展平后的单词列表执行四次遍数,然后进行对齐:
- 静音分层。 低于
min_gap的间隙永远不剪切。高于该值的保留停顿随前一个单词的标点符号缩放:.?!x1.5(下限 0.40 s),,;:x1.0,分句内部 x0.7(下限 0.20 s),话题切换 x2.0(下限 0.60 s)。统一的停顿长度是机器编辑最明显的标志之一——人耳期望在句号后有更长的呼吸时间。 - 填充词分层。 见下文。
- 口误检测。 重复的 n-gram,其中较晚的取代表较早的。
- 拼接卫生。 填充、交叉淡入淡出、过零对齐、剪辑密度限制。
步骤 2——填充词层级,以及一个绝不能自动化的层级
- A 级——非词汇(
äh ähm hm mh uh um er):自动移除。没人想要这些。 - B 级——词汇填充词(
weißt du, sozusagen, quasi, im Prinzip, you know, I mean):自动移除,但带有保护条件——不在句子开头,且移除后不留小于min_clip的片段。 - C 级——语气词:
also halt eben ja mal doch schon eigentlich genau,以及英语的like just well right so basically actually。仅标记。永不自动移除。
C 级是自动化编辑毁掉德语口语的关键。doch、eben、halt 和 ja 蕴含态度和话语结构;移除它们不会让说话者听起来更紧凑,反而会让他们听起来像机器翻译。如果用户仍然要求移除它们,可以这样做——但仅说一次其代价,之后不再提及。
检测信号顺序:词汇匹配,然后 duration/char >= 0.12 s,然后相邻间隙 >= 0.10 s,然后能量在周围语音的 6 dB 范围内。
切勿将 WhisperX 的 score 用作填充词信号。 它测量的是对齐度,而非犹豫度。低分数往往集中在稀有词和专有名词上——与填充词相反。仅将其用作时间戳可靠性标志:score < 0.35 会将填充宽度增加 60 毫秒,并禁止在该词内部进行剪切。
步骤 3——默认值及其状态
| 参数 | 默认值 | 说明 |
|---|---|---|
pad_in | 0.08 s | /p t k b d g/ 前的爆破音闭合 |
pad_out | 0.10 s | /t k s/ 的释放音超出对齐器的 end |
keep_pause | 下限 0.18 s | 在死寂位置保留的呼吸槽 |
min_gap | 0.55 s | 低于此值,永不剪切 |
min_removal | 0.20 s | 更短的移除不值得一次拼接 |
max_cut_density | 每 1.5 秒 1 次 | 跳切疲劳 |
min_clip | 0.35 s | 更短的保留片段合并到相邻片段 |
| 交叉淡入淡出 | 20 毫秒(最小 10,最大 40) | 同一段落使用 curve=tri,跨段落使用 qsin |
| 过零搜索 | +/-5 毫秒(240 个采样点) | 仅音频,从不移动视频 |
这些阈值是研究得出的,而非经过听觉测试。 当 project.json 中 "calibrated": false 时,在您为某个剪切辩护时,请务必说明这一点。不要将未校准的阈值呈现为调校过的阈值。会话钩子每次会话都会显示此标志。
过零对齐值得理解:在非过零点进行硬剪切会产生比信号自然最大值大 29.5 倍的采样步进——可听到的咔嗒声。过零对齐和 20 毫秒交叉淡入淡出都能将其恢复至基线水平。同时使用两者。对齐仅移动音频,从不移动视频;子帧视频偏移是无法表示的。
步骤 4——J/L 剪辑
4-12 帧是有效范围;默认 6 帧。
拒绝任何两侧共享同一个 source_id 且均未覆盖 B 卷或全帧叠加的 J/L 剪辑。 如果可见的嘴部画面不是 J 剪辑,那它就是唇音不同步错误,会被视为技术故障而非编辑手法。
步骤 5——写入片段
片段写入 segments[],以 src_in_frames(包含)和 src_out_frames(排他)作为唯一权威依据。每个片段还需要:
enabled——被拒绝的素材设置为false并给出原因,永不删除。 每个自动决策都必须可以通过翻转一个布尔值来逆转。这是使激进默认值变得安全的属性。order——显式输出顺序,在启用的片段上保持密集连续。provenance——包括stage(故事级删除为content,静音、填充词、口误各有对应)、rule、confidence、applied_by,以及触发它的测量值(original_gap_s)。数据来源让用户可以说“撤销所有 B 级移除”。word_range和反规范化的text。文本仅供阅读,绝不具有权威性——如果它与word_range冲突,则以单词范围为准。
时间线位置是推导出来的,永不存储。它们是加载时对已启用片段进行运行总和后写入 _computed 的结果。切勿手动编写 _computed。
将 stage 设置为 "rough_cut"。
步骤 6——像编辑而非脚本一样报告
报告:源时长、剪切后时长、移除百分比、剪切次数、剪辑密度(每分钟剪切次数),以及每个数据来源规则的次数。然后标记任何看起来过度剪辑的情况:每 1.5 秒内超过一次剪切的区域、任何接近 min_clip 的保留片段,以及被标记但未被处理的 C 级填充词数量。
提供三个下一步操作:预览某个段落、调整某个阈值、或进入图形/字幕阶段。
故障模式
| 症状 | 原因 | 修复 |
|---|---|---|
| 每个片段只有 1 帧 | OUT 向上舍入而非向下舍入 | 使用 snap_out;切勿内联计算 |
| 拼接处有可听到的咔嗒声 | 跳过了过零对齐 | 对齐加上 20 毫秒交叉淡入淡出 |
| 语音听起来断断续续且缺乏呼吸感 | 保留的停顿长度统一 | 应用基于标点符号的缩放表格 |
| 说话者听起来像机器人且不地道 | C 级被移除 | C 级仅标记;通过数据来源恢复 |
| 剪切落在单词中间 | 转录本为第 3 层(BPE 令牌) | 重新转录;令牌时间戳不能用于剪切 |
| 视频中的 A/V 漂移增大 | 剪切点未对齐帧 | 对齐每个剪切点,然后重新运行 QC 检查 (a) |