I have tried to split an mp4 file into smaller parts of equal time length like this ffmpeg -i ../data/2024-06-02_12-34-51.mp4 -c copy -map 0 -segment_time 00:00:05 -f segment v1_%03d.mp4
. However, this produced videos of highly variables size, some 25x larger than others. I assume this was due to inconsistent framerate during recording.
Next, I tried a script that would split based and limit each part to a specific size:
#!/bin/sh
# Short script to split videos by filesize using ffmpeg by LukeLR
if [ $# -ne 3 ]; then
echo 'Illegal number of parameters. Needs 3 parameters:'
echo 'Usage:'
echo './split-video.sh FILE SIZELIMIT "FFMPEG_ARGS'
echo
echo 'Parameters:'
echo ' - FILE: Name of the video file to split'
echo ' - SIZELIMIT: Maximum file size of each part (in bytes)'
echo ' - FFMPEG_ARGS: Additional arguments to pass to each ffmpeg-call'
echo ' (video format and quality options etc.)'
exit 1
fi
FILE="../data/$1"
SIZELIMIT="$2"
FFMPEG_ARGS="$3"
# Duration of the source video
DURATION=$(ffprobe -i "$FILE" -show_entries format=duration -v quiet -of default=noprint_wrappers=1:nokey=1|cut -d. -f -2)
# Duration that has been encoded so far
CURDURATION=0
# Filename of the source video (without extension)
BASENAME="${FILE%.*}"
# Extension for the video parts
#EXTENSION="${FILE##*.}"
EXTENSION="mp4"
# Number of the current video part
i=1
# Filename of the next video part
NEXTFILENAME="$BASENAME-$i.$EXTENSION"
echo "Duration of source video: $DURATION"
# Until the duration of all partial videos has reached the duration of the source video
#while [[ $CUR_DURATION -lt $DURATION ]]; do
while [[ $(bc <<< "$CURDURATION < $DURATION") -eq 1 ]]; do
# Encode next part
echo ffmpeg -i "$FILE" -ss "$CURDURATION" -fs "$SIZELIMIT" $FFMPEG_ARGS "$NEXTFILENAME"
ffmpeg -ss "$CURDURATION" -i "$FILE" -fs "$SIZELIMIT" $FFMPEG_ARGS "$NEXTFILENAME"
# Duration of the new part
NEWDURATION=$(ffprobe -i "$NEXTFILENAME" -show_entries format=duration -v quiet -of default=noprint_wrappers=1:nokey=1|cut -d. -f -2)
# Total duration encoded so far
echo $CURDURATION
CURDURATION=$(bc <<< "$CURDURATION + $NEWDURATION")
echo $CURDURATION
i=$((i + 1))
echo "Duration of $NEXTFILENAME: $NEWDURATION"
echo "Part No. $i starts at $CURDURATION"
echo "Current Duration: $CURDURATION"
NEXTFILENAME="$BASENAME-$i.$EXTENSION"
done
I call the script like this: bash split-video.sh 2024-06-02_12-34-51.mp4 10000000 "-c copy"
Unfortunately, this has an issue where some of the sub videos are extremely short and have wildly inconsistent numbers of frames in them (some with nearly 400, others with 1), despite being similar sizes. I am guessing this has something to do with inconsistent framerate and keyframes or something?
I am curious what the best way to split a video into equally sized parts, and ideally with similar numbers of frames, is using ffmpeg.
-show_frames
for getting the keyframes and packets sizes for example:ffprobe -select_streams v -of default=noprint_wrappers=1:nokey=1 -show_entries frame=key_frame,pkt_size input.mp4
. Sum the sizes to get the approximated size of each GOP (packets from keyframe to next keyframe). Do the math where to split using a Python script for example. Split by frame numbers (-segment_frames
) using segment demuxer. The accuracy is dependent on the "keyframes density".