Dependencies
- ffmpeg / ffprobe: Video clipping, silence detection, trim+concat (already installed on the system)
- whisperx venv:
scripts/.venv-whisperx/(for secondary quality check) - Transcription JSON: Requires
video-to-textskill's timestamped JSON output (for precise positioning and stutter detection) - Script set(all in
workspace/scripts/):batch-clip-v4.sh— main batch clipping script (silence removal + stutter removal + crossfade)smart-silence.py— intelligent silence processing (long-silence deletion / short-silence compression)stutter-detect.py— stutter detection (based on the original transcription JSON)clip-postcheck.py— WhisperX secondary scan + automatic repair of residual stuttersbatch-postcheck.sh— batch secondary quality-check scriptiterate-until-clean.py— repeatedly iterate a single clip (transcribe→detect→repair→re-transcribe) until zero issues remain
Complete four-phase workflow
Phase 1: 素材准备
Phase 2: 切片点定位(观点边界)
Phase 3: 批量切片(去静音 + 去口吃 + crossfade)← batch-clip-v4.sh
Phase 4: 二次质检(WhisperX 重转写 → 残留口吃检测修复)← batch-postcheck.sh
Phase 1: Material preparation
- Confirm the input file, obtain the total duration
- CJK filename handling: if it contains Chinese, create an English symlink; background nohup commands must use English paths
ln -sf "/path/to/直播回放.mp4" /tmp/livestream-input.mp4 - Confirm that the transcription JSON exists:
video-to-textskill's output (including word_segments-level timestamps)
Phase 2: Clip point positioning
The most critical step — you cannot guess timestamps by intuition; you must locate precisely based on the transcribed text.
2.1 Candidate topic sources
- Prioritize the golden quotes and timestamps in the
insight-extractoroutput - Or manually provide a topic name, then use the transcription JSON to locate it
2.2 Use the transcription JSON to calibrate boundaries
import json
with open("transcript.json") as f:
data = json.load(f)
for seg in data["segments"]:
if START <= seg["start"] <= END:
m, s = divmod(int(seg["start"]), 60)
h, m = divmod(m, 60)
ts = f"{h}:{m:02d}:{s:02d}" if h else f"{m}:{s:02d}"
print(f" [{ts}] {seg['text'].strip()}")
2.3 Principles for precise boundaries
- Complete viewpoint: from introduction/setup → conclusion/reaction, without truncation
- No redundancy before: cut out chit-chat, transitions, and irrelevant content
- No trailing after: cut as soon as the viewpoint finishes, without including the start of the next topic
- Expansion confirmation: expand 2-3 minutes before and after to confirm the boundaries are correct
2.4 Timestamp format
- < 60 minutes:
MM:SS(e.g.41:40) - ≥ 60 minutes:
H:MM:SS(e.g.1:50:08)
Phase 3: Batch clipping (main workflow)
Use scripts/batch-clip-v4.sh。
Three-layer cleanup pipeline
- Precise rough cut (
ffmpeg -ss ... -to ...): cut the original segments from the source video by time range - Silence + stutter jump cut (
smart-silence.py+stutter-detect.py):- Long silence (≥0.5s): skip entirely
- Short silence (0.25-0.5s): compress to 0.12s (preserve a sense of breathing)
- Stutter (repeated words/phrases): detected based on the original transcription JSON, skip the first occurrence
- filter_complex trim+concat + crossfade:
- Video: plain concat (avoid visual flicker)
- Audio: chained
acrossfade(20ms at each splice point, eliminating the "click" sensation)
⚠️ Key constraints
- Cannot use
select/aselect: long videos have different audio and video time bases, which causes drift - Must use
nohupRun in the background: 14 items × about 2-3 minutes each, total time ~20-30 minutes - macOS zsh has no
mapfile: usewhile IFS= read -r lineinstead - When timestamp >59:59: must use
H:MM:SSformat (109:30→1:49:30)
How to run
# 确认 symlink 存在
ln -sf "/path/to/原始视频.mp4" /tmp/livestream-input.mp4
# 后台运行
nohup bash workspace/scripts/batch-clip-v4.sh > /tmp/batch-clip-v4.log 2>&1 &
# 监控
tail -f /tmp/batch-clip-v4.log
Modify clip list
Edit batch-clip-v4.sh in the clips=() array, format is "start|end|name":
clips=(
"30:16|31:58|01-胆子够大"
"1:50:08|1:52:20|02-AI后背发凉"
...
)
Phase 4: Secondary quality check (WhisperX post-check)
Why is a secondary quality check needed?
Stutter detection is based on the original transcription JSON, but the original transcription sometimes misses certain stutters (they were not transcribed).
The edited clip may still contain residual stutters, requiring WhisperX to re-transcribe the clip itself, then perform detection again.
原始转写 JSON → stutter-detect.py → 跳切 → clip
↓
clip-postcheck.py(WhisperX 重转写)
↓
发现残留 → 自动二次修复
Run post-check
# 激活 whisperx venv
source scripts/.venv-whisperx/bin/activate
# 单条检测(不修复,只报告)
python3 scripts/clip-postcheck.py clips/02-AI后背发凉.mp4
# 单条检测 + 自动修复(-fixed.mp4 会替换原文件)
python3 scripts/clip-postcheck.py clips/02-AI后背发凉.mp4 --fix
# 批量检测 + 自动修复全部 clips/
bash scripts/batch-postcheck.sh
clip-postcheck.py detection items
- Single-character repetition(AA type): 运运, 对对对 → keep the last one
- Phrase repetition(ngram 2-4 characters): 就是就是, 当时的当时 → keep the latter one
- Low-confidence clusters: 3 or more consecutive characters with score<0.05, and overlapping with the following text → marked as jump-cut residue
Valid reduplication whitelist (no false positives): Try, take a look, thank you, just now, often, grandpa, mom, etc. 100+ words
Repeated iteration per item (strictest mode)
For clips with extremely high quality requirements, use iterate-until-clean.py:
source scripts/.venv-whisperx/bin/activate
python3 scripts/iterate-until-clean.py clips/02-AI后背发凉.mp4
Each round: WhisperX transcription → detection → repair → replacement → re-transcription verification.
At most 5 rounds, usually converges in 1-2 rounds.
Time per item
WhisperX transcription of a 1-minute clip ≈ 60-90 seconds (CPU).
Post-check for all 14 items ≈ 15-20 minutes.Be sure to run in the background。
Phase 5: Final check
for f in clips/*.mp4; do
[ -f "$f" ] || continue
sz=$(du -h "$f" | cut -f1)
dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$f" 2>/dev/null | cut -d. -f1)
min=$((dur/60)); sec=$((dur%60))
printf "%-35s %5s %d:%02d\n" "$(basename "$f")" "$sz" "$min" "$sec"
done
Output specifications
- Location:
workspace/clips/<编号>-<名称>.mp4 - Naming:
01-胆子够大.mp4、02-AI后背发凉.mp4 - Encoding:H.264 CRF23 + AAC 128kbps + faststart
- Size: 3-25MB/item (depending on duration)
- Compression rate: on average 20-30% shorter than the original clips (silence + stutter + crossfade)
Integration with other Skills
video-to-text → 转写 JSON(时间戳 + 口吃检测来源)
↓
insight-extractor → 观点摘要(切片候选来源)
↓
video-clipper → 短视频切片(本 skill)
├── Phase 3: batch-clip-v4.sh(去静音 + 去口吃 + crossfade)
└── Phase 4: batch-postcheck.sh(WhisperX 二次质检)
↓
(人工 / 剪映加字幕)→ 发布短视频平台
Pitfall log
| Problem | Cause | Solution |
|---|---|---|
| Audio-video desync | select/aselect filter audio and video time bases differ |
Switch to trim/atrim + concat |
| Timestamp not recognized | 109:30 format exceeds 59:59 |
Change to H:MM:SS(1:49:30) |
| macOS zsh error | mapfile does not exist |
Use while IFS= read -r line |
| Chinese filename garbled | nohup background + CJK path | Create English symlink |
| exec session timeout | 15-30 minute task killed by SIGTERM | All batch tasks must nohup |
| Residual stutter | Original transcription missed recognition, stutter-detect cannot catch it | Phase 4 WhisperX post-check |
| Jump-cut "click" sound | trim+concat splice point audio abrupt change | Add 20ms at each splice point acrossfade |
| Stutter misjudged as reduplication | 试试/看看/谢谢 treated as repetition | VALID_REDUP whitelist filtering |
Performance reference
| Number of slices | Total original duration | Phase 3 duration | Phase 4 duration |
|---|---|---|---|
| 6 items | ~15 min | ~8 min | ~10 min |
| 14 items | ~35 min | ~20 min | ~20 min |
| 20 items | ~50 min | ~30 min | ~28 min |