Skip to content

Instantly share code, notes, and snippets.

@rileyjshaw
Created March 5, 2024 02:57
Show Gist options
  • Save rileyjshaw/afe98fce6e6988bf3ce7a09b26e5e120 to your computer and use it in GitHub Desktop.
Save rileyjshaw/afe98fce6e6988bf3ce7a09b26e5e120 to your computer and use it in GitHub Desktop.
Create an ultra-wide photograph out of a slow dolly shot video. FFMPEG is amazing!
#!/bin/bash
# Default values
start_time=0
rtl_flag=false
output_file="output.png"
downscaling_factor=1
aspect_ratio="16:9"
# Parse arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
-i|--input) input_file="$2"; shift ;;
-s|--start) start_time="$2"; shift ;;
--rtl) rtl_flag=true ;;
-o|--output) output_file="$2"; shift ;;
-d|--downscale) downscaling_factor="$2"; shift ;;
-a|--aspect-ratio) aspect_ratio="$2"; shift ;;
*) echo "Unknown parameter: $1"; exit 1 ;;
esac
shift
done
if [ -z "$input_file" ]; then
echo "Input file is required."
exit 1
fi
# Parse the aspect ratio string
aspect_ratio_numerator=$(echo $aspect_ratio | cut -d: -f1)
aspect_ratio_denominator=$(echo $aspect_ratio | cut -d: -f2)
# Derive the duration and total number of frames in the video
duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$input_file")
total_frames=$(ffprobe -v error -select_streams v:0 -show_entries stream=nb_frames -of default=noprint_wrappers=1:nokey=1 "$input_file")
if ! [[ "$total_frames" =~ ^[0-9]+$ ]]; then
echo "Error: Could not determine total frames in the video."
exit 1
fi
total_frames=$(echo "$total_frames" | bc)
# Find the height of the video
height=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=p=0 "$input_file" | awk -F, '{print $1}')
scaled_height=$(echo "$height / $downscaling_factor" | bc)
# Calculate desired width based on aspect ratio and round to nearest integer
width=$(echo "scale=2; $scaled_height * $aspect_ratio_numerator / $aspect_ratio_denominator + 0.5" | bc | cut -d. -f1)
# Each frame will be 1 pixel wide, so we need $width frames
frame_count=$width
if [ "$total_frames" -lt "$frame_count" ]; then
echo "Error: Total frames in the video ($total_frames) is less than the required frame count ($frame_count) for this aspect ratio ($aspect_ratio)."
exit 1
fi
frame_interval=$(echo "$total_frames / $frame_count" | bc)
time_interval=$(echo "$duration / $frame_count" | bc -l)
# Create a temporary directory
temp_dir=$(mktemp -d)
# Extract frames as images
# Method 1: Take the center column of each frame
ffmpeg -ss "$start_time" -i "$input_file" -vf "fps=1/$time_interval,scale=$width:$scaled_height,format=yuvj444p,crop=1:ih:(iw/2):0:exact=1" -vframes "$frame_count" "$temp_dir/frame_%04d.png"
# Method 2: Move from the leftmost column (first frame) to the rightmost column (last frame)
# echo "Looping through each frame. This will take a while…"
# for ((frame_index=0; frame_index<frame_count; frame_index++)); do
# # Calculate the progress (i) from 0 to 1
# progress=$(echo "scale=8; $frame_index / ($frame_count - 1)" | bc)
#
# # Calculate dynamic x coordinate for cropping
# crop_x=$(echo "$width * $progress" | bc)
# if $rtl_flag; then
# crop_x=$(echo "$width - $crop_x" | bc)
# fi
#
# frame_time=$(echo "$frame_index * $time_interval / $frame_count" | bc -l)
# formatted_frame_time=$(printf "%08f" $frame_time)
#
# # Extract frame with dynamic crop
# ffmpeg -loglevel error -ss $formatted_frame_time -i "$input_file" -frames:v 1 -vf "scale=$width:$scaled_height,format=yuvj444p,crop=1:ih:$crop_x:0:exact=1" "$temp_dir/frame_$(printf "%04d" $frame_index).png"
# done
# Concatenate frames
ffmpeg -i "$temp_dir/frame_%04d.png" -filter_complex "tile=${width}x1" "$output_file"
# Apply RTL flag if set
if $rtl_flag; then
ffmpeg -i "$output_file" -vf "hflip" "$temp_dir/output_flipped.png"
mv "$temp_dir/output_flipped.png" "$output_file"
fi
# Cleanup and open
rm -rf "$temp_dir"
open "$output_file"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment