With `ffmpeg`, how to avoid in a video black segments by keeping last frame?
Yesterday 6:19pm

There's no single built-in "replace black with previous frame" filter, but you can do it in two passes: detect the black intervals with blackdetect, then cut the video around them, freeze the last non-black frame with tpad, and concat everything back. Since total duration is unchanged, audio stays in sync and can be stream-copied.

1. Detect the black segments

bash
ffmpeg -i input.mp4 -vf blackdetect=d=0.1:pix_th=0.10 -an -f null - 2>&1 | grep blackdetect

You'll get lines like black_start:4.20 black_end:6.90 black_duration:2.70. Tune d (minimum black duration) and pix_th (pixel darkness threshold) to your content.

2. Automate the replacement

bash
#!/usr/bin/env bash
in="input.mp4"; out="output.mp4"

readarray -t blacks < <(ffmpeg -hide_banner -i "$in" \
  -vf blackdetect=d=0.1:pix_th=0.10 -an -f null - 2>&1 \
  | sed -n 's/.*black_start:\([0-9.]*\) black_end:\([0-9.]*\).*/\1 \2/p')

n=${#blacks[@]}; echo "Found $n black segment(s)"

graph="[0:v]split=$((n+1))"
for i in $(seq 0 "$n"); do graph+="[s$i]"; done
graph+=";"

prev=0; i=0; ins=""
for seg in "${blacks[@]}"; do
  b=${seg% *}; e=${seg#* }
  dur=$(awk -v a="$b" -v b="$e" 'BEGIN{printf "%.3f", b-a}')
  # non-black part before the gap, then freeze its last frame for the gap length
  graph+="[s$i]trim=$prev:$b,setpts=PTS-STARTPTS,tpad=stop_mode=clone:stop_duration=$dur[p$i];"
  ins+="[p$i]"; prev=$e; ((i++))
done
graph+="[s$n]trim=$prev,setpts=PTS-STARTPTS[p$n];"
ins+="[p$n]"
graph+="${ins}concat=n=$((n+1)):v=1:a=0[v]"

ffmpeg -i "$in" -filter_complex "$graph" -map "[v]" -map "0:a?" -c:a copy "$out"

How it works:

  • Each black interval (b, e) becomes: keep trim(prev → b) normally, then tpad=stop_mode=clone:stop_duration=(e−b) repeats the last frame for exactly the black duration.
  • setpts=PTS-STARTPTS resets each piece's timestamps so concat joins them seamlessly.
  • The final trim=$prev keeps everything after the last black segment.

Caveats

  • If the video starts with black, trim=0:0 would be empty — for that first piece use [s0]trim=0:$e,tpad=start_mode=clone:start_duration=$e instead (clones the first non-black frame backwards).
  • Video must be re-encoded (default libx264; add -crf 18 or similar if quality matters). Audio is copied untouched since timing is preserved.
  • If your black segments are actually scene transitions or intentional fades, raise d so short dark moments aren't frozen.

If instead you'd rather remove the black segments entirely (shortening the video), drop the tpad from the graph — but then you'd need to cut the audio the same way to keep sync.

Yesterday 6:19pm

This is a shared conversation and may include unverified content that doesn't reflect Mistral AI's views. Login to resume chatting.

Vibe can make mistakes. Check answers. Learn more