使用FFmpeg按静音片段分割视频的脚本问题求助
Problem
I needed to split a video made up of multiple clips, each preceded by a ~5-second silent segment with a title static frame. The goal was to cut the video so every output clip includes that 5-second opening plus the following content until the next opening.
My initial bash script used FFmpeg's silencedetect to grab silent start timestamps, but when running the actual split commands, things went wrong: output clips were broken (some wouldn’t play in QuickTime), none included the title frames, and the timestamps used for splitting got garbled (e.g., 350.107 became 50.107, 1016.07 became 016.07).
Root Cause
The issue boiled down to FFmpeg reading from standard input (stdin) by default, which interfered with the while read loop in the script. As FFmpeg ran, it consumed the remaining timestamp lines from start_timestamps.txt that the loop was supposed to process, causing the timestamp values to get truncated or mixed up entirely.
Fixed Script
Here’s the corrected version of the script—you can use either of the two fixes included:
#!/bin/bash INPUT=$1 filename=$(basename -- "$INPUT") extension="${filename##*.}" filename="${filename%.*}" SILENCE_DETECT="silence_detect_logs.txt" TIMESTAMPS="start_timestamps.txt" if [ ! -f $TIMESTAMPS ]; then echo "Probing start timestamps" # Adjust silencedetect params to match your 5-second silent segments ffmpeg -i "$INPUT" -af "silencedetect=n=-50dB:d=5" -f null - 2> "$SILENCE_DETECT" cat "$SILENCE_DETECT" | grep "silence_start: [0-9.]*" -o | grep -E '[0-9]+(?:\.[0-9]*)?' -o > "$TIMESTAMPS" fi PREV=0.0 number=0 cat "$TIMESTAMPS" | ( while read ts do printf -v fname -- "$filename-%02d.$extension" "$(( ++number ))" DURATION=$( bc <<< "$ts - $PREV") # Fix 1: Redirect FFmpeg's input to null device to avoid reading stdin ffmpeg -y -ss "$PREV" -i "$INPUT" -t "$DURATION" -c copy "$fname" < /dev/null # Alternative Fix: Use -nostdin flag (works for FFmpeg 3.1+) # ffmpeg -y -nostdin -ss "$PREV" -i "$INPUT" -t "$DURATION" -c copy "$fname" PREV=$ts done printf -v fname -- "$filename-%02d.$extension" "$(( ++number ))" # Apply the same fix to the final segment ffmpeg -y -ss "$PREV" -i "$INPUT" -c copy "$fname" < /dev/null # ffmpeg -y -nostdin -ss "$PREV" -i "$INPUT" -c copy "$fname" )
Key Fixes Explained
Block FFmpeg from reading stdin:
- Add
< /dev/nullto each FFmpeg command: This redirects FFmpeg’s input to the null device, so it won’t siphon the timestamp lines the loop relies on. - Or use the
-nostdinflag (available in FFmpeg 3.1 and later): This explicitly tells FFmpeg not to read from stdin, which is a cleaner solution if your FFmpeg version supports it.
- Add
Tune
silencedetectfor your use case:- I changed
d=3tod=5to match your 5-second silent opening segments—adjust this value if your silent prefixes are shorter or longer. - The
n=-50dBsets the volume threshold for "silence"; tweak this if your silent segments are louder or quieter than -50dB.
- I changed
Why This Works
By stopping FFmpeg from reading stdin, the while read ts loop can process every timestamp in start_timestamps.txt correctly. This ensures split timings are accurate, so each output clip includes the full silent opening + title frame and the subsequent content as intended.
内容的提问来源于stack exchange,提问作者fricadelle

