Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Detect black scenes and split with ffmpeg remux
#!/usr/bin/env bash
# Splits video to separate scenes files when full black frames are found in the video
#
# Author: Werner Robitza
#
# Forked from: https://gist.github.com/davidwebca/e26186b8f4c6795b19c043fffb6f9861
# Inspired by https://gist.github.com/achesco/4dc2ebf13378a0a61fc26c7fe01f539e
# Who got inspired by https://stackoverflow.com/a/38205105
set -e
file=""
out="./"
stripaudio=""
add=0.00
trim=0.00
dur=2.00
ratio=0.98
th=0.10
usage () {
echo "Usage: $(basename $0) [[[-o folder] [-d black duration]] | [-h]] -f <input>"
echo
echo "Options:"
echo "-f, --file Input file"
echo "-o, --out Output files folder path, defaults to current directory"
echo "-d, --dur Duration for black detection in seconds. 0.05 default (practical single frame)"
echo "-r, --ratio ffmpeg pic_th : Set the threshold for considering a picture black. Default: $ratio"
echo "-th, --threshold ffmpeg pix_th : Set the threshold for considering a pixel black. Default: $th"
echo "-t, --trim Substracts to splitting timestamp in seconds. Default: 0"
echo "-a, --add Adds to splitting timestamp in seconds. Default: 0"
echo "-sa, --strip-audio Strip audio"
echo "-h, --help Display this help message"
echo
echo "Example: split.sh -d 0.5 -o /tmp/parts -f file.mp4"
echo "Splits file.mp4 file to scenes with black frames for more than 0.5 seconds"
echo "and saves output parts to /tmp/parts."
}
cleanup() {
rm -f ffout timestamps
}
trap 'cleanup' EXIT SIGINT SIGTERM
if [ "$1" = "" ]; then
usage
fi
while [ "$1" != "" ]; do
case $1 in
-f | --file )
shift
file=$1
;;
-d | --dur )
shift
dur=$1
;;
-r | --ratio )
shift
ratio=$1
;;
-th | --threshold )
shift
th=$1
;;
-o | --out )
shift
out=$1
;;
-t | --trim )
shift
trim=$1
;;
-a | --add )
shift
add=$1
;;
-sa | --strip-audio )
stripaudio="-an"
;;
-h | --help )
usage
exit
;;
* )
usage
exit 1
esac
shift
done
cut_part () {
printf -v fileout "$out/%04d_%s" "$2" "$filename"
if [[ "$3" != "" ]]; then
echo "Cutting from $1 for $3 seconds, writing to $fileout ..."
ffmpeg -nostdin -y -loglevel error -hide_banner -i "$file" -ss "$1" -t "$3" "$fileout"
else
echo "Cutting from $1 until end, writing to $fileout ..."
ffmpeg -nostdin -y -loglevel error -hide_banner -i "$file" -ss "$1" "$fileout"
fi
}
cd "$(dirname "$0")" || exit 1
filename=$(basename "$file")
mkdir -p "$out"
timefrom=0
i=1
echo "Running blackdetect filter ..."
ffmpeg -i "$file" -vf "blackdetect=d=$dur:pic_th=$ratio:pix_th=$th" -f null - 2> ffout
black_start=( $(grep blackdetect ffout | grep black_start:[0-9.]* -o | grep "[0-9]*(\.[0-9]*)?" -oE) )
black_duration=( $(grep blackdetect ffout | grep black_duration:[0-9.]* -o | grep "[0-9]*(\.[0-9]*)?" -oE) )
> timestamps
for ii in "${!black_start[@]}"; do
half=$(bc -l <<< "${black_duration[$ii]}/2")
middletime=$(bc -l <<< "${black_start[$ii]} + $half")
echo "$middletime" | LC_ALL=en_US.UTF-8 awk '{printf "%f", $0}' >> timestamps
echo "" >> timestamps
done
echo "Found the following timestamps: "
echo
cat timestamps
echo
while read -r timestamp; do
duration=$(bc -l <<< "$timestamp-$timefrom+$add-$trim" | LC_ALL=en_US.UTF-8 awk '{printf "%f", $0}')
cut_part "$timefrom" "$i" "$duration"
timefrom=$(bc -l <<< "$timestamp+$add-$trim" | LC_ALL=en_US.UTF-8 awk '{printf "%f", $0}')
i=$((i+1))
done < timestamps
if [[ "$timefrom" != 0 ]]; then
cut_part "$timefrom" "$i"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment