Last active
March 9, 2025 03:33
-
-
Save wpyoga/80581ffe82ed6d007f5c99e88e003d83 to your computer and use it in GitHub Desktop.
FFmpeg split and concat videos
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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