Skip to content

Instantly share code, notes, and snippets.

@achesco
Last active January 14, 2025 22:49
Show Gist options
  • Save achesco/4dc2ebf13378a0a61fc26c7fe01f539e to your computer and use it in GitHub Desktop.
Save achesco/4dc2ebf13378a0a61fc26c7fe01f539e to your computer and use it in GitHub Desktop.
Detect and split video to scenes with ffmpeg
# Splits video to separate scenes files
# Inspired by https://stackoverflow.com/a/38205105
#!/bin/bash
file=""
out="./"
diff=0.4
bitrate="512k"
trim=0
stripaudio=""
usage () {
echo "Usage: $(basename $0) [[[-o folder] [-d ratio]] | [-h]] -f file.mp4"
echo
echo "Options:"
echo "-f, --file Input file"
echo "-o, --out Outpup files folder path, default"
echo " to current folder"
echo "-d, --diff Number from 0 to 1, default to 0.4."
echo " Scene change difference factor"
echo "-b, --bitrate Bitrate to encode parts, default to 512k"
echo "-t, --trim Trim last given seconds number, 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 change more than 0.5"
echo "and saves output parts to /tmp/parts folder"
}
if [ "$1" = "" ]; then
usage
fi
while [ "$1" != "" ]; do
case $1 in
-f | --file )
shift
file=$1
;;
-d | --diff )
shift
diff=$1
;;
-o | --out )
shift
out=$1
;;
-b | --bitrate )
shift
bitrate=$1
;;
-t | --trim )
shift
trim=$1
;;
-sa | --strip-audio )
stripaudio="-an"
;;
-h | --help )
usage
exit
;;
* )
usage
exit 1
esac
shift
done
cut_part () {
duration_flag=""
if [ "$3" != "" ]; then
duration_flag="-t"
fi
ffmpeg -loglevel error -hide_banner -ss $1 $duration_flag $3 -i $file \
-vcodec libx264 -movflags faststart -b $bitrate $stripaudio \
-y $out/`printf "%04d_%s" $2 $filename` < /dev/null
}
filename=`basename $file`
mkdir -p $out
timefrom=0
i=1
while read -r timestamp; do
duration=`bc <<< "$timestamp-$timefrom-$trim" | awk '{printf "%f", $0}'`
cut_part $timefrom $i $duration
timefrom=$timestamp
i=`expr $i + 1`
done < <(
ffmpeg -i $file -filter:v "select='gt(scene,$diff)',showinfo" -f null - 2>&1 | \
grep Parsed_showinfo | grep pts_time:[0-9.]* -o | grep "[0-9]*\.[0-9]*" -o
)
if [ $timefrom != 0 ]; then
cut_part $timefrom $i
fi
@jazzyjackson
Copy link

jazzyjackson commented Dec 23, 2024

Thanks folks, I'm happily slicing up old home video tapes with this script.

Thanks to nielsbom for the hardware acceleration tip, I found the equivalent to h264_videotoolbox for my nvidia-GPU available in WSL is h264_nvenc and get about an 8x speedup.

With each tape there's usually a few scenes that I want to merge back together, so I wrote a script to run after this one; if I've identified that, say, all the videos from 0010_xyz.mp4 until 0018_xyz.mp4 should be one video, I can run the following:

./merge.sh 0010_xyz.mp4 0018_xyz.mp4

and the script will produce the intermediate list.txt of filenames to pass to ffmpeg's concat and write to file 0010-0018_xyz.mp4. it doesn't delete the input files of course, I do that manually after confirming the merge looks good.

https://gist.github.com/jazzyjackson/bf9282df0a40d7ef471e6676f282831e

And in case the scene splitting script missed something I need to split up manually, I can recommend this fun little utility that lets you seek, set markers, and split scenes without leaving your terminal: https://github.com/wong-justin/vic

Cheers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment