[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DBWQ1XGJ2NB9J947AFDP60":3,"skill-same:01M6DBWQ1XGJ2NB9J947AFDP60":69},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":49,"commitSha":50,"skillDirectory":51,"forksCount":14,"repositoryHtmlUrl":52,"repositoryHomepageUrl":6,"ownerHtmlUrl":53,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":54,"install":55,"resources":68},"01M6DBWQ1XGJ2NB9J947AFDP60","silence-trim",null,"Use when removing long silences \u002F dead air \u002F 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`\u002F`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\u002Ffiltering, not trimming); music tracks or ambient\u002FB-roll with no speech (everything reads as \"silence\" and you gut the clip — or as \"loud\" and nothing cuts); VAD-grade speech\u002Fnon-speech where the noise floor is high (silencedetect is amplitude-only, not a voice classifier); or audio-event scoring (laughter\u002Fapplause salience — that's the PANNs audio_saliency path, a different module). Source of truth: sidecar\u002Fmedia_studio\u002Ffeatures\u002Fsilencetrim.py.","Prekzursil\u002Fagent-skills-toolchain","Prekzursil","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F54636077?v=4","01M6DACZ1PCQHD99SCR1YXG28B","Andrei Mihai Visalon","01M6DACZ670WZF535V3WYYSS4Z",0,"MIT","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fsilence-trim\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000016","图像与多媒体","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"silence trim","use","as","air","module","db","drift","do","only","clip","speech","filtering","short","Audio","Media","Video","screen recording","edge","subtitles","produce","2026-11-10 10:12:30",false,"---\nname: silence-trim\ndescription: >-\n  Use when removing long silences \u002F dead air \u002F filler-pauses from a TALKING\n  video to produce a tight, jump-cut style edit (podcast, vlog, lecture, screen\n  recording, talking-head short). Detects silent spans with ffmpeg\n  `silencedetect` (noise floor in dB + a minimum silence duration), inverts them\n  to KEEP spans leaving a small pad on each edge so cuts don't sound clipped,\n  then re-cuts frame-accurately with a `trim`\u002F`atrim` + `concat` filter_complex\n  — and re-times caption cues onto the compacted timeline so subtitles don't\n  drift late after every removed gap. Do NOT use for: removing background noise\n  while keeping length (that's denoise\u002Ffiltering, not trimming); music tracks or\n  ambient\u002FB-roll with no speech (everything reads as \"silence\" and you gut the\n  clip — or as \"loud\" and nothing cuts); VAD-grade speech\u002Fnon-speech where the\n  noise floor is high (silencedetect is amplitude-only, not a voice classifier);\n  or audio-event scoring (laughter\u002Fapplause salience — that's the PANNs\n  audio_saliency path, a different module). Source of truth:\n  sidecar\u002Fmedia_studio\u002Ffeatures\u002Fsilencetrim.py.\n---\n\n# silence-trim — remove dead air with `silencedetect` + keep-span re-cut\n\nDistilled from the REAL media-studio implementation. Cite these when in doubt:\n\n- `sidecar\u002Fmedia_studio\u002Ffeatures\u002Fsilencetrim.py` — detect → invert → re-cut, the\n  `silence.trim` RPC, the no-silent-fallback notice.\n- `sidecar\u002Fmedia_studio\u002Ffeatures\u002Fboundary.py` — `build_silencedetect_argv`,\n  the `silence_start`\u002F`silence_end` regexes, the dB \u002F min-duration defaults.\n- `sidecar\u002Fmedia_studio\u002Ffeatures\u002Ffillers.py` — `build_segment_cut_argv`\n  (the `trim`\u002F`atrim`+`concat` re-cut), `remap_time` \u002F `remap_cues` (cue re-time).\n- `sidecar\u002Fmedia_studio\u002Fffmpeg.py` — `build_probe_argv` (ffprobe duration),\n  `run` (drained `-progress` runner), binary resolution by absolute path.\n- `sidecar\u002Fmedia_studio\u002Ffeatures\u002Fcues.py` — `word_cues` (the WORD-level cue\n  shape `{index,start,end,text}` that `remap_cues` re-times).\n\nThis is NOT audio_saliency. `audio_saliency.py` scores *interest* (laughter \u002F\napplause \u002F music \u002F loudness) with a PANNs CNN14 model for the short-maker's\nunified scorer. silence-trim is amplitude-only timeline surgery: it never loads a\nmodel, never tags events, and only removes\u002Fkeeps spans of time.\n\n---\n\n## The recipe (4 stages — exactly what the module does)\n\n```\nduration  -> detect (silencedetect) -> invert (keep_spans + pad) -> re-cut (trim\u002Fatrim+concat)\n                                                                  \\-> remap_cues (re-time captions)\n```\n\n1. **Probe duration** — `ffprobe` for `format=duration`. If it fails or is ≤0,\n   pass the clip through UNCHANGED with identity keeps `[(0.0, inf)]` and emit a\n   notice (see Pitfall 6). You need the total to invert silences into keeps and\n   to keep the trailing tail.\n2. **Detect silent spans** — run `silencedetect` over the whole clip, parse the\n   `silence_start` \u002F `silence_end` lines from **stderr** into `(start, end)`\n   spans. A probe\u002Fspawn failure returns `[]` (→ keep the whole clip) and emits a\n   notice; it never raises.\n3. **Invert to KEEP spans** — `keep_spans(silences, total, pad_sec)` computes the\n   talking parts between silences, leaving `pad_sec` of silence on each kept edge\n   and coalescing keeps whose gap shrank below `2*pad`.\n4. **Re-cut + remap** — if anything was removed and there's >1 keep, run a single\n   `filter_complex` of one `trim`\u002F`atrim` pair per keep then `concat`. Feed the\n   keep list to `remap_cues` so captions land on the compacted timeline.\n\nWire surface: `silence.trim({videoId|path, noiseDb?, minSilenceSec?, padSec?})\n-> {jobId} -> {path, removedSec}`. Also runnable as an EXPORT pre-step\n(`settings['silenceTrim']`) before the filler\u002Freframe\u002Fcaption stages.\n\n### Defaults (from `boundary.py` \u002F `silencetrim.py`)\n\n| Param | Default | Meaning |\n|---|---|---|\n| `noiseDb` | `-30.0` dB | anything quieter than this counts as silence (`DEFAULT_SILENCE_NOISE_DB`) |\n| `minSilenceSec` | `0.5` s | a gap must last this long to be a silence (`DEFAULT_SILENCE_MIN_SEC`) |\n| `padSec` | `0.1` s | silence left on each kept edge so cuts aren't clipped (`DEFAULT_PAD_SEC`) |\n\n---\n\n## Exact commands (real argv — no invented flags)\n\n### Stage 1 — probe duration (`ffmpeg.build_probe_argv`)\n\n```bash\nffprobe -v error -show_entries format=duration \\\n  -of default=noprint_wrappers=1:nokey=1 INPUT.mp4\n```\n\nPrints a bare number of seconds. `ffprobe_duration` returns `0.0` if it can't\nparse one — which the trim treats as \"can't trim safely, pass through\".\n\n### Stage 2 — detect silence (`boundary.build_silencedetect_argv`)\n\n```bash\nffmpeg -hide_banner -nostdin -i INPUT.mp4 \\\n  -af silencedetect=noise=-30.0dB:d=0.5 \\\n  -f null -\n```\n\n- Output goes to `null` (decode + analyze only, no file written).\n- The detector lines are on **stderr** (capture stderr, not stdout):\n  ```\n  [silencedetect @ ...] silence_start: 12.34\n  [silencedetect @ ...] silence_end: 15.80 | silence_duration: 3.46\n  ```\n- Parsed by `_SILENCE_START_RE` \u002F `_SILENCE_END_RE`\n  (`silence_start:\\s*([0-9]+(?:\\.[0-9]+)?)`, same for `end`).\n- Each paired start\u002Fend → one span. An **unpaired trailing start** (silence\n  running to EOF, no `silence_end`) is **ignored** — the re-cut keeps that tail\n  rather than guessing its length.\n\n### Stage 3 — re-cut keeps (`fillers.build_segment_cut_argv`)\n\nFor keeps `[(0.0, 12.44), (15.70, 30.00)]` (pad already applied), one\n`trim`\u002F`atrim` pair per keep, then `concat`:\n\n```bash\nffmpeg -hide_banner -nostdin -y -i INPUT.mp4 \\\n  -filter_complex \"\\\n[0:v]trim=start=0.000:end=12.440,setpts=PTS-STARTPTS[v0];\\\n[0:a]atrim=start=0.000:end=12.440,asetpts=PTS-STARTPTS[a0];\\\n[0:v]trim=start=15.700:end=30.000,setpts=PTS-STARTPTS[v1];\\\n[0:a]atrim=start=15.700:end=30.000,asetpts=PTS-STARTPTS[a1];\\\n[v0][a0][v1][a1]concat=n=2:v=1:a=1[v][a]\" \\\n  -map \"[v]\" -map \"[a]\" -c:v libx264 -c:a aac \\\n  -progress pipe:1 -nostats OUTPUT.mp4\n```\n\nKey invariants baked into the builder:\n- `setpts=PTS-STARTPTS` (video) and `asetpts=PTS-STARTPTS` (audio) on EVERY\n  segment — without these the concatenated timestamps are non-monotonic and the\n  player stutters \u002F A-V desyncs.\n- `concat=n=N:v=1:a=1` — N must equal the number of keep pairs.\n- Re-encodes (`libx264` + `aac`). This is a decode-side, frame-accurate cut, NOT\n  a stream copy — that's why edges are exact (see Pitfall 1).\n- `-progress pipe:1 -nostats` so `ffmpeg.run` can stream a 0–100% bar.\n- argv is a **list**, never `shell=True`; every path is one element so spaces are\n  safe.\n\n---\n\n## Pitfalls that actually bite\n\n**1. Stream-copy (`-c copy`) cuts are NOT frame-accurate — don't.** The module\nre-encodes via `trim`\u002F`atrim`. `-c copy` only cuts on keyframes, so every cut\nedge snaps to the nearest I-frame (often hundreds of ms off) and you'll clip\nwords or leave dead air. Accept the re-encode for accurate edges.\n\n**2. `setpts`\u002F`asetpts` reset is mandatory.** Each kept segment's PTS must be\nrebased to start at 0 before concat. The builder always emits\n`setpts=PTS-STARTPTS` \u002F `asetpts=PTS-STARTPTS`. Omit them and concat produces\njumping timestamps → stutter, freezes, A\u002FV drift. This is the single most common\nhand-rolled-concat bug.\n\n**3. Captions drift LATE without `remap_cues` — and get ERASED if you remap with\nthe wrong keep-list.** Every removed interior silence shifts all later cues\nearlier on the compacted timeline; feed the SAME keeps to `remap_cues` so cue\ntimes map through `remap_time`. Two real traps documented in the code:\n  - On a **pass-through** (nothing removed) the module returns keeps\n    `[(0.0, total)]` (a real full-length span), NOT `[]`. `remap_time` over an\n    empty keep-list returns `0.0` for *every* time → every caption collapses to\n    length 0 and is dropped. The full-length \u002F identity keep maps `t → t`.\n  - `trim_clip` returns `[(0.0, float('inf'))]` as the identity keep when it\n    can't probe\u002Ftrim, for the same reason.\n\n**4. `noiseDb` is an amplitude gate, not a voice detector.** `silencedetect`\nmeasures dB below `noise`, period. With background hum, music, HVAC, or room\ntone above `-30 dB`, NOTHING reads as silence and nothing gets cut. Conversely a\nvery quiet talker below the floor gets gutted mid-sentence. There is no VAD here.\nTune `noiseDb` to the actual room: noisy room → raise toward `-20`\u002F`-25 dB`;\nclean studio with soft speech → lower toward `-35`\u002F`-40 dB`. If you truly need\nspeech-vs-noise classification, this module is the wrong tool.\n\n**5. `minSilenceSec` too small = choppy machine-gun jump-cuts.** At `d=0.5`\nnatural breath pauses survive; drop to `0.2`–`0.3` and you cut between words and\nthe edit sounds robotic. Raise `minSilenceSec` (and keep `padSec ≈ 0.1`) before\nyou blame the codec. Padding also coalesces keeps whose removed gap is shorter\nthan `2*pad`, which further suppresses micro-cuts — that's by design.\n\n**6. Failures are SURFACED, not silent (no-silent-fallback \u002F WU-3).** A missing\nffmpeg, a `silencedetect` spawn failure, or an unprobeable duration each emit a\ntyped `silencetrim.unavailable` notice (`{type, message, reason}`) via the\n`on_notice` sink (routed to `job.progress`), then pass the clip through\nunchanged. A legitimate \"no dead air to remove\" pass-through emits NO notice and\nreturns `removedSec == 0.0` with the original path. Don't re-add a silent\n`{removedSec:0}` no-op — report the skip.\n\n**7. No needless re-encode.** When `removed \u003C= 1e-3` OR `len(keeps) \u003C= 1` the\nmodule returns the ORIGINAL `in_path` untouched (no ffmpeg pass). Mirror this:\ndon't burn an encode to produce a byte-identical clip.\n\n**8. Trailing silence is intentionally kept.** An unpaired `silence_start` with\nno matching `silence_end` (silence to EOF) is ignored by the parser, so the clip\nkeeps its tail. If you want to also trim a trailing silence, you'd need the\nduration to bound it — the module deliberately does not guess.\n\n**9. stderr vs stdout.** `silencedetect` writes to **stderr**; the re-cut\n`-progress pipe:1` writes to **stdout**. Capture the right stream for each\n(`capture_output=True` then read `.stderr` for detection).\n\n---\n\n## GPU \u002F model notes\n\n- **No model, no GPU, no Hugging Face asset.** silence-trim is pure ffmpeg +\n  numpy-free timeline math. It runs anywhere ffmpeg runs. (The PANNs CNN14\n  checkpoint and Zenodo download in `audio_saliency.py` belong to the *other*\n  module — don't conflate them.)\n- ffmpeg\u002Fffprobe are resolved by **absolute path** through\n  `ffmpeg.ffmpeg_path` \u002F `ffprobe_path` (bundled or PATH binary), never invoked\n  as a bare name through a shell.\n- The re-cut is CPU `libx264`\u002F`aac`. If you want NVENC, that's a swap of the\n  `-c:v` codec in a fork of `build_segment_cut_argv` — but be aware NVENC edges\n  are still frame-accurate here because the cut happens in the decode-side\n  `trim` filter, not the encoder.\n\n---\n\n## Worked example\n\nGoal: a 30 s talking-head clip with one long pause from 12.3 s to 15.8 s.\n\n1. Probe → `30.0`.\n2. Detect (defaults `-30 dB`, `d=0.5`):\n   ```\n   silence_start: 12.34\n   silence_end:   15.80 | silence_duration: 3.46\n   ```\n   → spans `[(12.34, 15.80)]`.\n3. Invert with `pad_sec=0.1`:\n   keep ends at `min(30, 12.34+0.1)=12.44`; next cursor `15.80-0.1=15.70`.\n   → keeps `[(0.0, 12.44), (15.70, 30.0)]`.\n   `removed_seconds = 30.0 - (12.44 + 14.30) = 3.26 s`.\n4. `removed=3.26 > 1e-3` and `len(keeps)=2 > 1` → re-cut with the Stage-3 argv\n   above → `OUTPUT.mp4`, `removedSec=3.26`.\n5. A caption cue at `start=18.0` (original) → `remap_time(18.0, keeps)`:\n   first keep contributes `12.44`; `18.0` is inside the second keep\n   `(15.70, 30.0)` → `12.44 + (18.0 - 15.70) = 14.74 s` on the compacted\n   timeline. Without the remap it would still say `18.0` and float ~3.26 s late.\n\nPass-through example: same clip, but the only pause is 0.3 s (\u003C `minSilenceSec`).\nDetection yields `[]` → keeps `[(0.0, 30.0)]` → `removed≈0` → return ORIGINAL\npath, `removedSec=0.0`, no notice, no re-encode.\n\n---\n\n## Quick reference — the pure functions to lean on\n\n- `parse_silence_spans(stderr) -> [(start,end), ...]` — paired lines only, sorted.\n- `keep_spans(silences, total, pad_sec=0.1) -> [(start,end), ...]` — invert + pad\n  + coalesce; empty silences → `[(0, total)]`.\n- `removed_seconds(keeps, total) -> float` — `total - sum(kept)`, clamped ≥0.\n- `trim_clip(in, out, ...) -> (path, removedSec, keeps)` — the whole pipeline;\n  returns the keep-list for `remap_cues`.\n- `remap_time(t, keeps)` \u002F `remap_cues(cues, keeps)` — re-time captions onto the\n  cut timeline (drops zero-length cues, renumbers 1..N).\n","# silence-trim — remove dead air with `silencedetect` + keep-span re-cut\n\nDistilled from the REAL media-studio implementation. Cite these when in doubt:\n\n- `sidecar\u002Fmedia_studio\u002Ffeatures\u002Fsilencetrim.py` — detect → invert → re-cut, the\n  `silence.trim` RPC, the no-silent-fallback notice.\n- `sidecar\u002Fmedia_studio\u002Ffeatures\u002Fboundary.py` — `build_silencedetect_argv`,\n  the `silence_start`\u002F`silence_end` regexes, the dB \u002F min-duration defaults.\n- `sidecar\u002Fmedia_studio\u002Ffeatures\u002Ffillers.py` — `build_segment_cut_argv`\n  (the `trim`\u002F`atrim`+`concat` re-cut), `remap_time` \u002F `remap_cues` (cue re-time).\n- `sidecar\u002Fmedia_studio\u002Fffmpeg.py` — `build_probe_argv` (ffprobe duration),\n  `run` (drained `-progress` runner), binary resolution by absolute path.\n- `sidecar\u002Fmedia_studio\u002Ffeatures\u002Fcues.py` — `word_cues` (the WORD-level cue\n  shape `{index,start,end,text}` that `remap_cues` re-times).\n\nThis is NOT audio_saliency. `audio_saliency.py` scores *interest* (laughter \u002F\napplause \u002F music \u002F loudness) with a PANNs CNN14 model for the short-maker's\nunified scorer. silence-trim is amplitude-only timeline surgery: it never loads a\nmodel, never tags events, and only removes\u002Fkeeps spans of time.\n\n---\n\n## The recipe (4 stages — exactly what the module does)\n\n```\nduration  -> detect (silencedetect) -> invert (keep_spans + pad) -> re-cut (trim\u002Fatrim+concat)\n                                                                  \\-> remap_cues (re-time captions)\n```\n\n1. **Probe duration** — `ffprobe` for `format=duration`. If it fails or is ≤0,\n   pass the clip through UNCHANGED with identity keeps `[(0.0, inf)]` and emit a\n   notice (see Pitfall 6). You need the total to invert silences into keeps and\n   to keep the trailing tail.\n2. **Detect silent spans** — run `silencedetect` over the whole clip, parse the\n   `silence_start` \u002F `silence_end` lines from **stderr** into `(start, end)`\n   spans. A probe\u002Fspawn failure returns `[]` (→ keep the whole clip) and emits a\n   notice; it never raises.\n3. **Invert to KEEP spans** — `keep_spans(silences, total, pad_sec)` computes the\n   talking parts between silences, leaving `pad_sec` of silence on each kept edge\n   and coalescing keeps whose gap shrank below `2*pad`.\n4. **Re-cut + remap** — if anything was removed and there's >1 keep, run a single\n   `filter_complex` of one `trim`\u002F`atrim` pair per keep then `concat`. Feed the\n   keep list to `remap_cues` so captions land on the compacted timeline.\n\nWire surface: `silence.trim({videoId|path, noiseDb?, minSilenceSec?, padSec?})\n-> {jobId} -> {path, removedSec}`. Also runnable as an EXPORT pre-step\n(`settings['silenceTrim']`) before the filler\u002Freframe\u002Fcaption stages.\n\n### Defaults (from `boundary.py` \u002F `silencetrim.py`)\n\n| Param | Default | Meaning |\n|---|---|---|\n| `noiseDb` | `-30.0` dB | anything quieter than this counts as silence (`DEFAULT_SILENCE_NOISE_DB`) |\n| `minSilenceSec` | `0.5` s | a gap must last this long to be a silence (`DEFAULT_SILENCE_MIN_SEC`) |\n| `padSec` | `0.1` s | silence left on each kept edge so cuts aren't clipped (`DEFAULT_PAD_SEC`) |\n\n---\n\n## Exact commands (real argv — no invented flags)\n\n### Stage 1 — probe duration (`ffmpeg.build_probe_argv`)\n\n```bash\nffprobe -v error -show_entries format=duration \\\n  -of default=noprint_wrappers=1:nokey=1 INPUT.mp4\n```\n\nPrints a bare number of seconds. `ffprobe_duration` returns `0.0` if it can't\nparse one — which the trim treats as \"can't trim safely, pass through\".\n\n### Stage 2 — detect silence (`boundary.build_silencedetect_argv`)\n\n```bash\nffmpeg -hide_banner -nostdin -i INPUT.mp4 \\\n  -af silencedetect=noise=-30.0dB:d=0.5 \\\n  -f null -\n```\n\n- Output goes to `null` (decode + analyze only, no file written).\n- The detector lines are on **stderr** (capture stderr, not stdout):\n  ```\n  [silencedetect @ ...] silence_start: 12.34\n  [silencedetect @ ...] silence_end: 15.80 | silence_duration: 3.46\n  ```\n- Parsed by `_SILENCE_START_RE` \u002F `_SILENCE_END_RE`\n  (`silence_start:\\s*([0-9]+(?:\\.[0-9]+)?)`, same for `end`).\n- Each paired start\u002Fend → one span. An **unpaired trailing start** (silence\n  running to EOF, no `silence_end`) is **ignored** — the re-cut keeps that tail\n  rather than guessing its length.\n\n### Stage 3 — re-cut keeps (`fillers.build_segment_cut_argv`)\n\nFor keeps `[(0.0, 12.44), (15.70, 30.00)]` (pad already applied), one\n`trim`\u002F`atrim` pair per keep, then `concat`:\n\n```bash\nffmpeg -hide_banner -nostdin -y -i INPUT.mp4 \\\n  -filter_complex \"\\\n[0:v]trim=start=0.000:end=12.440,setpts=PTS-STARTPTS[v0];\\\n[0:a]atrim=start=0.000:end=12.440,asetpts=PTS-STARTPTS[a0];\\\n[0:v]trim=start=15.700:end=30.000,setpts=PTS-STARTPTS[v1];\\\n[0:a]atrim=start=15.700:end=30.000,asetpts=PTS-STARTPTS[a1];\\\n[v0][a0][v1][a1]concat=n=2:v=1:a=1[v][a]\" \\\n  -map \"[v]\" -map \"[a]\" -c:v libx264 -c:a aac \\\n  -progress pipe:1 -nostats OUTPUT.mp4\n```\n\nKey invariants baked into the builder:\n- `setpts=PTS-STARTPTS` (video) and `asetpts=PTS-STARTPTS` (audio) on EVERY\n  segment — without these the concatenated timestamps are non-monotonic and the\n  player stutters \u002F A-V desyncs.\n- `concat=n=N:v=1:a=1` — N must equal the number of keep pairs.\n- Re-encodes (`libx264` + `aac`). This is a decode-side, frame-accurate cut, NOT\n  a stream copy — that's why edges are exact (see Pitfall 1).\n- `-progress pipe:1 -nostats` so `ffmpeg.run` can stream a 0–100% bar.\n- argv is a **list**, never `shell=True`; every path is one element so spaces are\n  safe.\n\n---\n\n## Pitfalls that actually bite\n\n**1. Stream-copy (`-c copy`) cuts are NOT frame-accurate — don't.** The module\nre-encodes via `trim`\u002F`atrim`. `-c copy` only cuts on keyframes, so every cut\nedge snaps to the nearest I-frame (often hundreds of ms off) and you'll clip\nwords or leave dead air. Accept the re-encode for accurate edges.\n\n**2. `setpts`\u002F`asetpts` reset is mandatory.** Each kept segment's PTS must be\nrebased to start at 0 before concat. The builder always emits\n`setpts=PTS-STARTPTS` \u002F `asetpts=PTS-STARTPTS`. Omit them and concat produces\njumping timestamps → stutter, freezes, A\u002FV drift. This is the single most common\nhand-rolled-concat bug.\n\n**3. Captions drift LATE without `remap_cues` — and get ERASED if you remap with\nthe wrong keep-list.** Every removed interior silence shifts all later cues\nearlier on the compacted timeline; feed the SAME keeps to `remap_cues` so cue\ntimes map through `remap_time`. Two real traps documented in the code:\n  - On a **pass-through** (nothing removed) the module returns keeps\n    `[(0.0, total)]` (a real full-length span), NOT `[]`. `remap_time` over an\n    empty keep-list returns `0.0` for *every* time → every caption collapses to\n    length 0 and is dropped. The full-length \u002F identity keep maps `t → t`.\n  - `trim_clip` returns `[(0.0, float('inf'))]` as the identity keep when it\n    can't probe\u002Ftrim, for the same reason.\n\n**4. `noiseDb` is an amplitude gate, not a voice detector.** `silencedetect`\nmeasures dB below `noise`, period. With background hum, music, HVAC, or room\ntone above `-30 dB`, NOTHING reads as silence and nothing gets cut. Conversely a\nvery quiet talker below the floor gets gutted mid-sentence. There is no VAD here.\nTune `noiseDb` to the actual room: noisy room → raise toward `-20`\u002F`-25 dB`;\nclean studio with soft speech → lower toward `-35`\u002F`-40 dB`. If you truly need\nspeech-vs-noise classification, this module is the wrong tool.\n\n**5. `minSilenceSec` too small = choppy machine-gun jump-cuts.** At `d=0.5`\nnatural breath pauses survive; drop to `0.2`–`0.3` and you cut between words and\nthe edit sounds robotic. Raise `minSilenceSec` (and keep `padSec ≈ 0.1`) before\nyou blame the codec. Padding also coalesces keeps whose removed gap is shorter\nthan `2*pad`, which further suppresses micro-cuts — that's by design.\n\n**6. Failures are SURFACED, not silent (no-silent-fallback \u002F WU-3).** A missing\nffmpeg, a `silencedetect` spawn failure, or an unprobeable duration each emit a\ntyped `silencetrim.unavailable` notice (`{type, message, reason}`) via the\n`on_notice` sink (routed to `job.progress`), then pass the clip through\nunchanged. A legitimate \"no dead air to remove\" pass-through emits NO notice and\nreturns `removedSec == 0.0` with the original path. Don't re-add a silent\n`{removedSec:0}` no-op — report the skip.\n\n**7. No needless re-encode.** When `removed \u003C= 1e-3` OR `len(keeps) \u003C= 1` the\nmodule returns the ORIGINAL `in_path` untouched (no ffmpeg pass). Mirror this:\ndon't burn an encode to produce a byte-identical clip.\n\n**8. Trailing silence is intentionally kept.** An unpaired `silence_start` with\nno matching `silence_end` (silence to EOF) is ignored by the parser, so the clip\nkeeps its tail. If you want to also trim a trailing silence, you'd need the\nduration to bound it — the module deliberately does not guess.\n\n**9. stderr vs stdout.** `silencedetect` writes to **stderr**; the re-cut\n`-progress pipe:1` writes to **stdout**. Capture the right stream for each\n(`capture_output=True` then read `.stderr` for detection).\n\n---\n\n## GPU \u002F model notes\n\n- **No model, no GPU, no Hugging Face asset.** silence-trim is pure ffmpeg +\n  numpy-free timeline math. It runs anywhere ffmpeg runs. (The PANNs CNN14\n  checkpoint and Zenodo download in `audio_saliency.py` belong to the *other*\n  module — don't conflate them.)\n- ffmpeg\u002Fffprobe are resolved by **absolute path** through\n  `ffmpeg.ffmpeg_path` \u002F `ffprobe_path` (bundled or PATH binary), never invoked\n  as a bare name through a shell.\n- The re-cut is CPU `libx264`\u002F`aac`. If you want NVENC, that's a swap of the\n  `-c:v` codec in a fork of `build_segment_cut_argv` — but be aware NVENC edges\n  are still frame-accurate here because the cut happens in the decode-side\n  `trim` filter, not the encoder.\n\n---\n\n## Worked example\n\nGoal: a 30 s talking-head clip with one long pause from 12.3 s to 15.8 s.\n\n1. Probe → `30.0`.\n2. Detect (defaults `-30 dB`, `d=0.5`):\n   ```\n   silence_start: 12.34\n   silence_end:   15.80 | silence_duration: 3.46\n   ```\n   → spans `[(12.34, 15.80)]`.\n3. Invert with `pad_sec=0.1`:\n   keep ends at `min(30, 12.34+0.1)=12.44`; next cursor `15.80-0.1=15.70`.\n   → keeps `[(0.0, 12.44), (15.70, 30.0)]`.\n   `removed_seconds = 30.0 - (12.44 + 14.30) = 3.26 s`.\n4. `removed=3.26 > 1e-3` and `len(keeps)=2 > 1` → re-cut with the Stage-3 argv\n   above → `OUTPUT.mp4`, `removedSec=3.26`.\n5. A caption cue at `start=18.0` (original) → `remap_time(18.0, keeps)`:\n   first keep contributes `12.44`; `18.0` is inside the second keep\n   `(15.70, 30.0)` → `12.44 + (18.0 - 15.70) = 14.74 s` on the compacted\n   timeline. Without the remap it would still say `18.0` and float ~3.26 s late.\n\nPass-through example: same clip, but the only pause is 0.3 s (\u003C `minSilenceSec`).\nDetection yields `[]` → keeps `[(0.0, 30.0)]` → `removed≈0` → return ORIGINAL\npath, `removedSec=0.0`, no notice, no re-encode.\n\n---\n\n## Quick reference — the pure functions to lean on\n\n- `parse_silence_spans(stderr) -> [(start,end), ...]` — paired lines only, sorted.\n- `keep_spans(silences, total, pad_sec=0.1) -> [(start,end), ...]` — invert + pad\n  + coalesce; empty silences → `[(0, total)]`.\n- `removed_seconds(keeps, total) -> float` — `total - sum(kept)`, clamped ≥0.\n- `trim_clip(in, out, ...) -> (path, removedSec, keeps)` — the whole pipeline;\n  returns the keep-list for `remap_cues`.\n- `remap_time(t, keeps)` \u002F `remap_cues(cues, keeps)` — re-time captions onto the\n  cut timeline (drops zero-length cues, renumbers 1..N).\n",{"name":5,"description":7},"main","a298c929b6cbd662e1fce54302ce520d5ceea1a5",".agents\u002Fskills\u002Fsilence-trim","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain","https:\u002F\u002Fgithub.com\u002FPrekzursil","2026-11-02 14:30:57",{"source":8,"skillName":5,"commands":56,"prompt":66,"zipAvailable":67},[57,60,63],{"tool":58,"command":59},"npx","npx skills add Prekzursil\u002Fagent-skills-toolchain --skill silence-trim",{"tool":61,"command":62},"bunx","bunx skills add Prekzursil\u002Fagent-skills-toolchain --skill silence-trim",{"tool":64,"command":65},"pnpm","pnpm dlx skills add Prekzursil\u002Fagent-skills-toolchain --skill silence-trim","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fsilence-trim\u002FSKILL.md",true,[],[70,91,119,146,160,176],{"skillNo":71,"name":72,"nameZh":6,"description":73,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":74,"categories":75,"keywords":76,"publishedAt":44},"01M6DF7D7478R55P46REYH4QAC","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.","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Fworkspace-hooks\u002Fskills\u002Fclarify-requirements\u002FSKILL.md",[],[77,78,79,80,81,82,83,84,85,25,86,87,88,89,90],"clarify requirements","clarify","requirements","risk","options","task","prompts","goal","scanning","context","repo","injection","execution","file",{"skillNo":92,"name":93,"nameZh":6,"description":94,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":95,"categories":96,"keywords":100,"publishedAt":44},"01M6DF79CA4BHJS66J8EZDBPWJ","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 \u002F 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.","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Fmcp\u002Fopenmm-md-mcp\u002Fskill\u002Fopenmm-md\u002FSKILL.md",[97],{"categoryNo":98,"categoryName":99,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[101,102,103,104,105,106,107,108,109,110,111,112,113,114,25,115,116,117,31,118],"openmm md","how","decision","pick","linux","analyze","tool","stack","open","NVIDIA","gpu","license","workflow","windows","free","molecular dynamics","local","MCP",{"skillNo":120,"name":121,"nameZh":6,"description":122,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":123,"categories":124,"keywords":131,"publishedAt":44},"01M6DF76C1MG85VEWP8VZCBNMN","visual-review","Take screenshots of web pages and UI using Playwright for visual review and iteration","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fvisual-review\u002FSKILL.md",[125,128],{"categoryNo":126,"categoryName":127,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000003","演示文稿",{"categoryNo":129,"categoryName":130,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",[132,133,134,135,136,137,138,139,140,141,102,142,143,144,145],"review","visual","visual review","pages","web","false","iteration","take","check","slides","ui","screenshot","playwright","screenshots",{"skillNo":147,"name":148,"nameZh":6,"description":149,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":150,"categories":151,"keywords":152,"publishedAt":44},"01M6DF73AQVCBZKEN0YXEX9FXP","status","Diagnostic status report — shows metaswarm installation state, project setup, and potential issues","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fstatus\u002FSKILL.md",[],[148,153,154,155,156,157,158,159],"setup","report","project","installation","project setup","issues","state",{"skillNo":161,"name":162,"nameZh":6,"description":163,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":164,"categories":165,"keywords":167,"publishedAt":44},"01M6DF6X6C2XB0P6GX76P7TTYV","start","Use when starting work on any task, when the user mentions metaswarm, or when the user wants to begin tracked development work","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fstart\u002FSKILL.md",[166],{"categoryNo":98,"categoryName":99,"taxonomyType":21,"taxonomyTypeDesc":22},[162,168,82,169,170,171,25,172,173,174,175],"start issue","issue","ready","work on issue","user","work","work on","development",{"skillNo":177,"name":153,"nameZh":6,"description":178,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":179,"categories":180,"keywords":181,"publishedAt":44},"01M6DF6QMJ9AJKFKHHKRT005PQ","Interactive project setup — detects your project, configures metaswarm, writes project-local files","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fsetup\u002FSKILL.md",[],[153,155,117,182,157,183],"files","interactive"]