1实现原理 · 为什么它能做到
整条链路是确定性算法而非模型判断:只处理 head/tail 死气与超过 --gap 的词间停顿,一句话都不改。
Pure pause/silence trimmer. Takes any word-level transcript (ElevenLabs Scribe … shape, or { words: [{ text, start, end }] }) and removes silences only:
头尾死气按 --head-pad / --tail-pad 直接砍掉,作为两个独立删除区间进入合并。
// head + tail dead air const headEnd = words[0].start - opts.headPad; if (headEnd > 0) deleteRanges.push({ start: 0, end: headEnd, reason: "head trim before first word" });
词间停顿不是砍到 0,而是按上下文保留一口“自然呼吸”:长停顿 0.24s、句末 0.20s、其余 0.14s。
if (gap < opts.gap) continue; … const sentenceBreak = /[.!?]$/.test(prev.text ?? ""); const keep = gap >= 2 ? 0.24 : sentenceBreak ? 0.2 : 0.14;
保留的呼吸被有意偏向后句首:前句末端多留 55%、后句开头少留 45%。
// Bias the kept breath slightly toward the end of the previous phrase. const delStart = prev.end + keep * 0.55; const delEnd = next.start - keep * 0.45;
删除区间先合并再做补集得到 keep ranges,并丢掉小于 40ms 的碎片区间。
if (cursor < duration) keepRanges.push({ start: cursor, end: duration }); const usableKeepRanges = keepRanges.filter((r) => r.end - r.start >= 0.04);
转录按同一套删除表重定时到剪辑轴,并保留 source_start/source_end 供回查原片。
const editedWords = []; for (const word of words) { if (inDeleted(word)) continue; … start: Number((word.start - removedBefore(word.start)).toFixed(3)),
渲染走 ffmpeg trim/atrim + concat 的 filtergraph,先落盘成 *.silence-filter.txt 再用 -/filter_complex 传入;默认只算不渲。
lines.push(`[0:v]trim=start=${r.start.toFixed(3)}:end=${r.end.toFixed(3)},setpts=PTS-STARTPTS[v${i}];`); … const ffArgs = [ "-y", "-i", videoPath, "-/filter_complex", scriptPath, "-map", "[v]", "-map", "[a]", "-c:v", "libx264", "-preset", "medium", "-crf", "18", "-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart", outputPath, ];
2核心能力
3外部依赖
| 类型 | 依赖 |
|---|---|
| cli | Node.js(跑 cut-silences.mjs) |
| cli | ffmpeg(trim/atrim + concat 重编码,仅 --apply 时执行) |
| cli | ffprobe(无 audio_duration_secs 时探源片时长) |
| api | ElevenLabs Scribe(**前置步骤**:由仓库级 scripts/transcribe-elevenlabs.mjs 上传音频取词级时间戳;本次侦察未在本 skill 目录内发现任何调用代码) |
| cli | 本地 Whisper / whisper.cpp(文档列出的无 key 替代转录路线) |
4风险提醒 风险提醒:蓝色 · 知晓即可
- 前置转录步的素材出网与计费(不在本 skill 目录,但由 SKILL.md 指引) — Prerequisites 指向仓库级 scripts/transcribe-elevenlabs.mjs,该 helper 读 ELEVENLABS_API_KEY 并把音频 POST 到 api.elevenlabs.io;愿意的话可换 OpenAI Whisper(另一 key)或本地 Whisper/whisper.cpp(无 key,需本地算力)。本 skill 自身脚本不参与这一步。
- 切太快会变成“喘不上气”的播客式节奏 — --gap 调小会让停顿被大量削掉;SKILL.md 给的经验是 0.55 约削 15-20% 纯沉默,明显偏离这个区间就该抽查 decisions.md 里的 top15 停顿。
- 转录与源片不一致时整条链会系统性错位 — 所有区间都建立在转录时间戳上;若转录来自另一个版本的素材(或时长字段与实际不符),切点会整体偏移而不报错——脚本只在 duration 缺失时才 ffprobe 兜底。
- 硬编码阈值对非英语/快语速素材未必合适 — 0.55s 阈值与 0.20s 句末呼吸是按英语 talking-head 调的;中文/快节奏口播可能需要重调参数,且脚本没有自动标点感知之外的语义判断。
5第二遍独立确认
- [ok] 目录文件清单与 Outputs 表对照 — 目录仅 SKILL.md + scripts/cut-silences.mjs;SKILL.md 列出的 5 类产物中 3 类无条件写、filter.txt 与 mp4 受 --video/--apply 门控,与源码 writeFileSync/spawnSync 分支一致。
- [ok] 网络面反例搜索 — 目录内 0 处 fetch/URL/网络模块导入;SKILL.md 出现的 `node scripts/transcribe-elevenlabs.mjs ...` 是仓库级前置命令,文件名不含 http,且未被本脚本引用。
- [ok] 凭证面反例搜索 — 0 处 process.env/API_KEY/TOKEN/.env;脚本对 process 的导入仅 argv/exit(`import { argv, exit } from "node:process";`)。
- [ok] 「只删沉默」是否被代码强制 — 删除区间只由 head/tail pad 与 gap>opts.gap 的停顿生成三处 push 产生,源码无重复词/相似度/语义判断,与文档声明一致,无夸大。
- [ok] 重定时映射正确性 — start = word.start - removedBefore(word.start),removedBefore 只累加严格位于其前的删除量,因此不会出现负值或跳变;edited_duration = duration - removedTotal 与被删总量自洽。
- [ok] 文档参数 vs 脚本 argv — --video/--out-dir/--output/-o/--gap/--head-pad/--tail-pad/--apply 全部在 argv 解析中定位到,未知 --flag 会 die,与 SKILL.md 表格一致。
6结论
484001b000655549…0d30152a82