Skip to content

Instantly share code, notes, and snippets.

@wpyoga
Last active March 9, 2025 03:33
Show Gist options
  • Select an option

  • Save wpyoga/80581ffe82ed6d007f5c99e88e003d83 to your computer and use it in GitHub Desktop.

Select an option

Save wpyoga/80581ffe82ed6d007f5c99e88e003d83 to your computer and use it in GitHub Desktop.
FFmpeg split and concat videos
ffmpeg_concat() (
test "$#" -lt 1 && return
LISTFILE="`mktemp -p .`"
printf "file '%s'\n" "$@" > "$LISTFILE"
ffmpeg -f concat -safe 0 -i "$LISTFILE" -c copy concat.mp4
rm "$LISTFILE"
)
# to split video into multiple segments based on key frames
# extract key frames at start of scenes
ffmpeg -i video.mp4 -vf "select='eq(n,0)+gt(scene,0.4)*eq(pict_type,PICT_TYPE_I)',scale=960:540" -fps_mode vfr scene-%02d.jpg
# -vf "select='eq(n,0)+gt(scene,0.4)*eq(pict_type,PICT_TYPE_I)',scale=960:540"
# select: use the select filter
# eq((n,0): select the first frame (frame 0)
# + OR
# gt(scene,0.4): select frames at the start of a scene change
# * AND (takes precedence over OR)
# eq(pict_type,PICT_TYPE_I): select only key frames
# scale=960:540 scale to a lower resolution for faster frame browsing
# -fps_mode vfr
# use this for variable-bitrate (vbr) videos
# most videos will have variable bitrate
# alternative command line
ffmpeg -i video.mp4 -vf "select='eq(n,0)+gt(scene,0.2)',scale=960:540" -fps_mode vfr scene-%02d.jpg
# 0.2 delta is used here because when the video is scaled down, the delta values may sometimes decrease
# play around with smaller or larger values in order to extract scene changes more accurately
# to get frame numbers
ffmpeg -i video.mp4 -vf "select='eq(n,0)+gt(scene,0.2)',scale=960:540" -fps_mode vfr -frame_pts true frame-%03d.jpg
# in a directory full of frame-%03d.jpg files, we can make a simple list of frames to cut the original video into scenes
ls frame-*.jpg | sed -e 's,^frame-,,' -e 's,.jpg$,,' -e 's,^0*\([0-9]\),\1,' | (PREV=; while read i; do [ -n "$PREV" ] && echo $PREV $((i-1)); PREV=$i; done; echo $PREV) | while read a b; do [ -n "$b" ] && echo $a $(echo $a*0.04 | bc) $(echo $b*0.04 | bc) || echo $a $(echo $a*0.04 | bc) ; done > timestamps.txt
# to get only the frame numbers
ls frame-*.jpg | sed -e 's,^frame-,,' -e 's,.jpg$,,' -e 's,^0*\([0-9]\),\1,' | (PREV=; while read i; do [ -n "$PREV" ] && echo $PREV $((i-1)); PREV=$i; done; echo $PREV) > framenumbers.txt
# with the frame numbers, we can generate a list of timestamps for splitting the video
# the format is start_frame start_timestamp end_timestamp
cat framenumbers.txt | while read a b; do [ -n "$b" ] && echo $a $(echo $a*0.04 | bc) $(echo $b*0.04 | bc) || echo $a $(echo $a*0.04 | bc) ; done > timestamps.txt
# after we get the timestamps, we can split the original video file into scenes like this
cat timestamps.txt | (i=0; while read a b c; do i=$((i+1)); [ -n "$c" ] && ffmpeg -nostdin -i input.mp4 -ss $b -to $c -c:v copy -an -fflags +genpts scene-$i.mp4 || ffmpeg -nostdin -i input.mp4 -ss $b -c:v copy -an -fflags +genpts scene-$i.mp4; done)
# to get the timestamps
ffmpeg -i input.mp4 -vf "select='eq(n,0)+gt(scene,0.2)',scale=960:540,showinfo" -fps_mode vfr -f null /dev/null 2>&1 | grep pts_time
# to get i-frame (key frame) timestamps
# ffprobe -select_streams v -show_entries frame=pict_type,pts_time -of csv=p=0 -skip_frame nokey -i infile.mp4
# then split based on time, but minus one frame before the keyframes
# ffmpeg -i infile.mp4 -ss 123.45 -t 10s -c copy out.mp4
# make sure the -ss time is at least one frame prior to the keyframe (0.04s for 30 fps)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment