Skip to content

Instantly share code, notes, and snippets.

@digiguru
Last active September 12, 2023 19:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save digiguru/756f150e88087257a9a287e0d893390c to your computer and use it in GitHub Desktop.
Save digiguru/756f150e88087257a9a287e0d893390c to your computer and use it in GitHub Desktop.
A simple service that will take a .mov and search through every frame looking for differences between that frame and the previous and create a gif with 1 frame for every _changed_ image (visually)
#!/bin/bash
# Default values
FRAME_RATE=4 # Default frame rate (every nth frame)
SSIM_THRESHOLD=0.98 # Threshold below which frames are considered different
TARGET_WIDTH="" # Target width for frames in the GIF, empty means original width
TARGET_HEIGHT="" # Target height for frames in the GIF, empty means original height
input_file="" # Input filename
output_gif="" # Output filename
progress_bar() {
local total=$1
local current=$2
local width=50 # Width of the progress bar
local percent=$((current * 100 / total))
local count=$((width * current / total))
printf "\r["
for ((i=0; i<count; i++)); do
printf "#"
done
for ((i=count; i<width; i++)); do
printf " "
done
printf "] %d%%" $percent
}
# Parse command-line arguments for customization
while [[ "$#" -gt 0 ]]; do
case $1 in
-f|--frame-rate) FRAME_RATE="$2"; shift ;;
-s|--ssim) SSIM_THRESHOLD="$2"; shift ;;
-w|--width) TARGET_WIDTH="$2"; shift ;;
-h|--height) TARGET_HEIGHT="$2"; shift ;;
-i|--input) input_file="$2"; shift ;;
-o|--output) output_gif="$2"; shift ;;
*) echo "Unknown parameter passed: $1"; exit 1 ;;
esac
shift
done
# Check if input and output filenames are provided
if [[ -z "$input_file" || -z "$output_gif" ]]; then
echo "Both input and output filenames must be provided."
exit 1
fi
# Create the resize filter string based on provided dimensions
if [[ -z "$TARGET_WIDTH" && -z "$TARGET_HEIGHT" ]]; then
RESIZE_FILTER=""
elif [[ -z "$TARGET_HEIGHT" ]]; then
RESIZE_FILTER=",scale=$TARGET_WIDTH:-1"
elif [[ -z "$TARGET_WIDTH" ]]; then
RESIZE_FILTER=",scale=-1:$TARGET_HEIGHT"
else
RESIZE_FILTER=",scale=$TARGET_WIDTH:$TARGET_HEIGHT"
fi
# Create a directory to hold the frames
mkdir -p frames
# Extract every nth frame from the MOV file based on FRAME_RATE and the RESIZE_FILTER
ffmpeg -i "$input_file" -vf "select=not(mod(n\,$FRAME_RATE)),setpts=N/FRAME_RATE/TB$RESIZE_FILTER" frames/frame%04d.png
echo "Executing: ffmpeg -i $input_file -vf ""select=not(mod(n\,$FRAME_RATE)),setpts=N/FRAME_RATE/TB$RESIZE_FILTER"" frames/frame%04d.png"
# Initialize previous frame and time variables
prev_frame=""
prev_frame_number=0
prev_frame_number_ref=0
frame=""
frame_number=0
frame_number_ref=0
# Lists to hold the frames and delays for the final GIF
frames_list=()
delays_list=()
frame_count=$(ls frames | wc -l)
current_frame=0
current_delay=0
skipped_frames=0
# Loop through the frames and compare to the previous
for frame in frames/frame*.png; do
if [ ! -z "$prev_frame" ]; then
# Get the SSIM value
ssim=$(ffmpeg -i "$prev_frame" -i "$frame" -filter_complex ssim -an -f null - 2>&1 | grep 'SSIM' | awk '{print $5}' | cut -d: -f2)
# Check if SSIM indicates a significant change based on SSIM_THRESHOLD
if (( $(echo "$ssim < $SSIM_THRESHOLD" | bc -l) )); then
# Calculate the delay based on frame numbers (in hundredths of a second)
frame_number=$(echo "$frame" | grep -o '[0-9]\+' | head -1)
frame_number_ref=$((10#$frame_number))
delay=$(( $current_delay * $FRAME_RATE ))
prev_frame="$frame"
prev_frame_number=$frame_number
prev_frame_number_ref=$frame_number_ref
current_delay=0
# Skip the frame if the delay is zero
if [ "$delay" -ne 0 ]; then
frames_list+=("$frame")
delays_list+=("-delay $delay")
fi
else
skipped_frames=$((skipped_frames + 1))
fi
else
prev_frame="$frame"
fi
progress_bar $frame_count $current_frame
current_frame=$((current_frame + 1))
current_delay=$((current_delay + 1))
done
# Generate the GIF
cmd_args=()
for i in "${!frames_list[@]}"; do
cmd_args+=("${delays_list[$i]}" "${frames_list[$i]}")
done
echo "Executing: convert ""${cmd_args[@]}"" -loop 0 -layers Optimize ""$output_gif"""
convert ${cmd_args[@]} -loop 0 -layers Optimize "$output_gif"
echo "Total frames: $current_frame. Skipped frames: $skipped_frames"
# Cleanup
rm -r frames
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment