silence-trim
Use when removing long silences / dead air / filler-pauses from a TALKING video to produce a tight, jump-cut style edit (podcast, vlog, lecture, screen recording, talking-head short). Detects silent spans with ffmpeg `silencedetect` (noise floor in dB + a minimum silence duration), inverts them to KEEP spans leaving a small pad on each edge so cuts don't sound clipped, then re-cuts frame-accurately with a `trim`/`atrim` + `concat` filter_complex — and re-times caption cues onto the compacted timeline so subtitles don't drift late after every removed gap. Do NOT use for: removing background noise while keeping length (that's denoise/filtering, not trimming); music tracks or ambient/B-roll with no speech (everything reads as "silence" and you gut the clip — or as "loud" and nothing cuts); VAD-grade speech/non-speech where the noise floor is high (silencedetect is amplitude-only, not a voice classifier); or audio-event scoring (laughter/applause salience — that's the PANNs audio_saliency path, a different module). Source of truth: sidecar/media_studio/features/silencetrim.py.
- License 许可
- MIT
- 最近更新
- 2026-11-02
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- silence-trim
- description
- Use when removing long silences / dead air / filler-pauses from a TALKING video to produce a tight, jump-cut style edit (podcast, vlog, lecture, screen recording, talking-head short). Detects silent spans with ffmpeg `silencedetect` (noise floor in dB + a minimum silence duration), inverts them to KEEP spans leaving a small pad on each edge so cuts don't sound clipped, then re-cuts frame-accurately with a `trim`/`atrim` + `concat` filter_complex — and re-times caption cues onto the compacted timeline so subtitles don't drift late after every removed gap. Do NOT use for: removing background noise while keeping length (that's denoise/filtering, not trimming); music tracks or ambient/B-roll with no speech (everything reads as "silence" and you gut the clip — or as "loud" and nothing cuts); VAD-grade speech/non-speech where the noise floor is high (silencedetect is amplitude-only, not a voice classifier); or audio-event scoring (laughter/applause salience — that's the PANNs audio_saliency path, a different module). Source of truth: sidecar/media_studio/features/silencetrim.py.
02 资源文件 3
- 资产文件solving-methods.csv7.1 KB
- 参考文档template.md2.0 KB
- 参考文档workflow.md10.5 KB
03 SKILL.md 正文
silence-trim — remove dead air with silencedetect + keep-span re-cut
Distilled from the REAL media-studio implementation. Cite these when in doubt:
sidecar/media_studio/features/silencetrim.py— detect → invert → re-cut, thesilence.trimRPC, the no-silent-fallback notice.sidecar/media_studio/features/boundary.py—build_silencedetect_argv, thesilence_start/silence_endregexes, the dB / min-duration defaults.sidecar/media_studio/features/fillers.py—build_segment_cut_argv(thetrim/atrim+concatre-cut),remap_time/remap_cues(cue re-time).sidecar/media_studio/ffmpeg.py—build_probe_argv(ffprobe duration),run(drained-progressrunner), binary resolution by absolute path.sidecar/media_studio/features/cues.py—word_cues(the WORD-level cue shape{index,start,end,text}thatremap_cuesre-times).
This is NOT audio_saliency. audio_saliency.py scores interest (laughter /
applause / music / loudness) with a PANNs CNN14 model for the short-maker's
unified scorer. silence-trim is amplitude-only timeline surgery: it never loads a
model, never tags events, and only removes/keeps spans of time.
The recipe (4 stages — exactly what the module does)
duration -> detect (silencedetect) -> invert (keep_spans + pad) -> re-cut (trim/atrim+concat)
\-> remap_cues (re-time captions)
- Probe duration —
ffprobeforformat=duration. If it fails or is ≤0, pass the clip through UNCHANGED with identity keeps[(0.0, inf)]and emit a notice (see Pitfall 6). You need the total to invert silences into keeps and to keep the trailing tail. - Detect silent spans — run
silencedetectover the whole clip, parse thesilence_start/silence_endlines from stderr into(start, end)spans. A probe/spawn failure returns[](→ keep the whole clip) and emits a notice; it never raises. - Invert to KEEP spans —
keep_spans(silences, total, pad_sec)computes the talking parts between silences, leavingpad_secof silence on each kept edge and coalescing keeps whose gap shrank below2*pad. - Re-cut + remap — if anything was removed and there's >1 keep, run a single
filter_complexof onetrim/atrimpair per keep thenconcat. Feed the keep list toremap_cuesso captions land on the compacted timeline.
Wire surface: silence.trim({videoId|path, noiseDb?, minSilenceSec?, padSec?}) -> {jobId} -> {path, removedSec}. Also runnable as an EXPORT pre-step
(settings['silenceTrim']) before the filler/reframe/caption stages.
Defaults (from boundary.py / silencetrim.py)
| Param | Default | Meaning |
|---|---|---|
noiseDb |
-30.0 dB |
anything quieter than this counts as silence (DEFAULT_SILENCE_NOISE_DB) |
minSilenceSec |
0.5 s |
a gap must last this long to be a silence (DEFAULT_SILENCE_MIN_SEC) |
padSec |
0.1 s |
silence left on each kept edge so cuts aren't clipped (DEFAULT_PAD_SEC) |
Exact commands (real argv — no invented flags)
Stage 1 — probe duration (ffmpeg.build_probe_argv)
ffprobe -v error -show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 INPUT.mp4
Prints a bare number of seconds. ffprobe_duration returns 0.0 if it can't
parse one — which the trim treats as "can't trim safely, pass through".
Stage 2 — detect silence (boundary.build_silencedetect_argv)
ffmpeg -hide_banner -nostdin -i INPUT.mp4 \
-af silencedetect=noise=-30.0dB:d=0.5 \
-f null -
- Output goes to
null(decode + analyze only, no file written). - The detector lines are on stderr (capture stderr, not stdout):
[silencedetect @ ...] silence_start: 12.34 [silencedetect @ ...] silence_end: 15.80 | silence_duration: 3.46 - Parsed by
_SILENCE_START_RE/_SILENCE_END_RE(silence_start:\s*([0-9]+(?:\.[0-9]+)?), same forend). - Each paired start/end → one span. An unpaired trailing start (silence
running to EOF, no
silence_end) is ignored — the re-cut keeps that tail rather than guessing its length.
Stage 3 — re-cut keeps (fillers.build_segment_cut_argv)
For keeps [(0.0, 12.44), (15.70, 30.00)] (pad already applied), one
trim/atrim pair per keep, then concat:
ffmpeg -hide_banner -nostdin -y -i INPUT.mp4 \
-filter_complex "\
[0:v]trim=start=0.000:end=12.440,setpts=PTS-STARTPTS[v0];\
[0:a]atrim=start=0.000:end=12.440,asetpts=PTS-STARTPTS[a0];\
[0:v]trim=start=15.700:end=30.000,setpts=PTS-STARTPTS[v1];\
[0:a]atrim=start=15.700:end=30.000,asetpts=PTS-STARTPTS[a1];\
[v0][a0][v1][a1]concat=n=2:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" -c:v libx264 -c:a aac \
-progress pipe:1 -nostats OUTPUT.mp4
Key invariants baked into the builder:
setpts=PTS-STARTPTS(video) andasetpts=PTS-STARTPTS(audio) on EVERY segment — without these the concatenated timestamps are non-monotonic and the player stutters / A-V desyncs.concat=n=N:v=1:a=1— N must equal the number of keep pairs.- Re-encodes (
libx264+aac). This is a decode-side, frame-accurate cut, NOT a stream copy — that's why edges are exact (see Pitfall 1). -progress pipe:1 -nostatssoffmpeg.runcan stream a 0–100% bar.- argv is a list, never
shell=True; every path is one element so spaces are safe.
Pitfalls that actually bite
1. Stream-copy (-c copy) cuts are NOT frame-accurate — don't. The module
re-encodes via trim/atrim. -c copy only cuts on keyframes, so every cut
edge snaps to the nearest I-frame (often hundreds of ms off) and you'll clip
words or leave dead air. Accept the re-encode for accurate edges.
2. setpts/asetpts reset is mandatory. Each kept segment's PTS must be
rebased to start at 0 before concat. The builder always emits
setpts=PTS-STARTPTS / asetpts=PTS-STARTPTS. Omit them and concat produces
jumping timestamps → stutter, freezes, A/V drift. This is the single most common
hand-rolled-concat bug.
3. Captions drift LATE without remap_cues — and get ERASED if you remap with
the wrong keep-list. Every removed interior silence shifts all later cues
earlier on the compacted timeline; feed the SAME keeps to remap_cues so cue
times map through remap_time. Two real traps documented in the code:
- On a pass-through (nothing removed) the module returns keeps
[(0.0, total)](a real full-length span), NOT[].remap_timeover an empty keep-list returns0.0for every time → every caption collapses to length 0 and is dropped. The full-length / identity keep mapst → t. trim_clipreturns[(0.0, float('inf'))]as the identity keep when it can't probe/trim, for the same reason.
4. noiseDb is an amplitude gate, not a voice detector. silencedetect
measures dB below noise, period. With background hum, music, HVAC, or room
tone above -30 dB, NOTHING reads as silence and nothing gets cut. Conversely a
very quiet talker below the floor gets gutted mid-sentence. There is no VAD here.
Tune noiseDb to the actual room: noisy room → raise toward -20/-25 dB;
clean studio with soft speech → lower toward -35/-40 dB. If you truly need
speech-vs-noise classification, this module is the wrong tool.
5. minSilenceSec too small = choppy machine-gun jump-cuts. At d=0.5
natural breath pauses survive; drop to 0.2–0.3 and you cut between words and
the edit sounds robotic. Raise minSilenceSec (and keep padSec ≈ 0.1) before
you blame the codec. Padding also coalesces keeps whose removed gap is shorter
than 2*pad, which further suppresses micro-cuts — that's by design.
6. Failures are SURFACED, not silent (no-silent-fallback / WU-3). A missing
ffmpeg, a silencedetect spawn failure, or an unprobeable duration each emit a
typed silencetrim.unavailable notice ({type, message, reason}) via the
on_notice sink (routed to job.progress), then pass the clip through
unchanged. A legitimate "no dead air to remove" pass-through emits NO notice and
returns removedSec == 0.0 with the original path. Don't re-add a silent
{removedSec:0} no-op — report the skip.
7. No needless re-encode. When removed <= 1e-3 OR len(keeps) <= 1 the
module returns the ORIGINAL in_path untouched (no ffmpeg pass). Mirror this:
don't burn an encode to produce a byte-identical clip.
8. Trailing silence is intentionally kept. An unpaired silence_start with
no matching silence_end (silence to EOF) is ignored by the parser, so the clip
keeps its tail. If you want to also trim a trailing silence, you'd need the
duration to bound it — the module deliberately does not guess.
9. stderr vs stdout. silencedetect writes to stderr; the re-cut
-progress pipe:1 writes to stdout. Capture the right stream for each
(capture_output=True then read .stderr for detection).
GPU / model notes
- No model, no GPU, no Hugging Face asset. silence-trim is pure ffmpeg +
numpy-free timeline math. It runs anywhere ffmpeg runs. (The PANNs CNN14
checkpoint and Zenodo download in
audio_saliency.pybelong to the other module — don't conflate them.) - ffmpeg/ffprobe are resolved by absolute path through
ffmpeg.ffmpeg_path/ffprobe_path(bundled or PATH binary), never invoked as a bare name through a shell. - The re-cut is CPU
libx264/aac. If you want NVENC, that's a swap of the-c:vcodec in a fork ofbuild_segment_cut_argv— but be aware NVENC edges are still frame-accurate here because the cut happens in the decode-sidetrimfilter, not the encoder.
Worked example
Goal: a 30 s talking-head clip with one long pause from 12.3 s to 15.8 s.
- Probe →
30.0. - Detect (defaults
-30 dB,d=0.5):
→ spanssilence_start: 12.34 silence_end: 15.80 | silence_duration: 3.46[(12.34, 15.80)]. - Invert with
pad_sec=0.1: keep ends atmin(30, 12.34+0.1)=12.44; next cursor15.80-0.1=15.70. → keeps[(0.0, 12.44), (15.70, 30.0)].removed_seconds = 30.0 - (12.44 + 14.30) = 3.26 s. removed=3.26 > 1e-3andlen(keeps)=2 > 1→ re-cut with the Stage-3 argv above →OUTPUT.mp4,removedSec=3.26.- A caption cue at
start=18.0(original) →remap_time(18.0, keeps): first keep contributes12.44;18.0is inside the second keep(15.70, 30.0)→12.44 + (18.0 - 15.70) = 14.74 son the compacted timeline. Without the remap it would still say18.0and float ~3.26 s late.
Pass-through example: same clip, but the only pause is 0.3 s (< minSilenceSec).
Detection yields [] → keeps [(0.0, 30.0)] → removed≈0 → return ORIGINAL
path, removedSec=0.0, no notice, no re-encode.
Quick reference — the pure functions to lean on
parse_silence_spans(stderr) -> [(start,end), ...]— paired lines only, sorted.keep_spans(silences, total, pad_sec=0.1) -> [(start,end), ...]— invert + pad- coalesce; empty silences →
[(0, total)].
- coalesce; empty silences →
removed_seconds(keeps, total) -> float—total - sum(kept), clamped ≥0.trim_clip(in, out, ...) -> (path, removedSec, keeps)— the whole pipeline; returns the keep-list forremap_cues.remap_time(t, keeps)/remap_cues(cues, keeps)— re-time captions onto the cut timeline (drops zero-length cues, renumbers 1..N).
04 同仓库更多 Skills
clarify-requirements
Resolve genuine pre-execution ambiguity by scanning repo context, generating 1-6 grounded options, and asking 1-3 focused questions via AskUserQuestion. NOT for mid-task use (injection risk); NOT when the goal is already unambiguous; use to disambiguate vague prompts before any file edits.
openmm-md
Run FREE, open-source, Windows-native GPU molecular dynamics via the openmm-md MCP (OpenMM + CUDA on the local NVIDIA GPU). Use for protein / protein-ligand MD when you want a free stack or are on Windows without a Schrodinger license. Covers the full workflow (prepare, solvate, minimize, equilibrate, production, analyze, mmgbsa) AND an honest decision guide vs Schrodinger Desmond, including how to run Desmond legitimately on Linux or a cloud GPU with a real license. This is a deliberately LESSER free alternative and does NOT replace the schrodinger MCP tools. Do NOT use it to pick a free tool when a validated FEP+, WaterMap, IFD-MD, or Prime MM-GBSA result is actually required.
visual-review
Take screenshots of web pages and UI using Playwright for visual review and iteration
status
Diagnostic status report — shows metaswarm installation state, project setup, and potential issues
start
Use when starting work on any task, when the user mentions metaswarm, or when the user wants to begin tracked development work
setup
Interactive project setup — detects your project, configures metaswarm, writes project-local files