an eink camera running on an rpi zero 2 w
at main 6.6 kB view raw
1#!/usr/bin/env -S nix shell nixpkgs#parallel nixpkgs#ffmpeg nixpkgs#imagemagick --command bash 2 3# Define temporary directory variables globally so trap can access them 4FRAMES_IN="" 5FRAMES_OUT="" 6 7# Function to clean up and exit 8cleanup_and_exit() { 9 echo -e "\nInterrupted. Cleaning up temporary files..." 10 # Only remove if directories were actually created 11 [ -n "$FRAMES_IN" ] && [ -d "$FRAMES_IN" ] && rm -rf "$FRAMES_IN" 12 [ -n "$FRAMES_OUT" ] && [ -d "$FRAMES_OUT" ] && rm -rf "$FRAMES_OUT" 13 exit 1 14} 15 16# Set up trap for Ctrl+C (SIGINT) 17trap cleanup_and_exit INT 18 19# Check if input and output parameters are provided 20if [ $# -lt 2 ]; then 21 echo "Usage: $0 input_video output_video [num_threads]" 22 exit 1 23fi 24 25INPUT_VIDEO=$1 26OUTPUT_VIDEO=$2 27# Default to number of CPU cores if threads not specified 28NUM_THREADS=${3:-$(nproc)} 29 30# Determine and create temporary directories, preferring RAM disk 31TEMP_BASE="" 32if [ -d "/dev/shm" ] && [ -w "/dev/shm" ]; then 33 TEMP_BASE="/dev/shm" 34 echo "Using RAM disk (/dev/shm) for temporary files." 35elif [ -d "/tmp" ] && [ -w "/tmp" ] && mount | grep -q 'on /tmp type tmpfs'; then 36 TEMP_BASE="/tmp" 37 echo "Using RAM disk (/tmp) for temporary files." 38else 39 TEMP_BASE="." 40 echo "RAM disk not available or not writable, using current directory for temporary files." 41fi 42 43# Create unique temporary directory names using process ID 44# Add a random suffix for extra safety in case PID wraps or script runs concurrently 45RAND_SUFFIX=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8) 46FRAMES_IN="${TEMP_BASE}/inkify_frames_in_${$}_${RAND_SUFFIX}" 47FRAMES_OUT="${TEMP_BASE}/inkify_frames_out_${$}_${RAND_SUFFIX}" 48 49# Check if magick command exists 50if ! command -v magick &>/dev/null; then 51 echo "Error: ImageMagick (magick command) not found. Please install it." 52 # Attempt cleanup before exiting 53 [ -d "$FRAMES_IN" ] && rm -rf "$FRAMES_IN" 54 [ -d "$FRAMES_OUT" ] && rm -rf "$FRAMES_OUT" 55 exit 1 56fi 57 58# Check if eink-2color.png exists 59if [ ! -f "eink-2color.png" ]; then 60 echo "Error: Colormap file 'eink-2color.png' not found in the current directory." 61 # Attempt cleanup before exiting 62 [ -d "$FRAMES_IN" ] && rm -rf "$FRAMES_IN" 63 [ -d "$FRAMES_OUT" ] && rm -rf "$FRAMES_OUT" 64 exit 1 65fi 66 67# Create temporary directories only after checks pass 68mkdir -p "$FRAMES_IN" 69if [ $? -ne 0 ]; then echo "Error: Could not create temporary directory $FRAMES_IN"; exit 1; fi 70mkdir -p "$FRAMES_OUT" 71if [ $? -ne 0 ]; then echo "Error: Could not create temporary directory $FRAMES_OUT"; rm -rf "$FRAMES_IN"; exit 1; fi 72 73 74# Extract frames from video and detect framerate 75FRAMERATE=$(ffprobe -v error -select_streams v -of default=noprint_wrappers=1:nokey=1 -show_entries stream=r_frame_rate "$INPUT_VIDEO" 2>/dev/null | bc -l | awk '{printf "%.2f", $0}') 76if [ -z "$FRAMERATE" ] || [ "$FRAMERATE" = "0.00" ]; then 77 echo "Error: Could not detect framerate for $INPUT_VIDEO." 78 cleanup_and_exit # Use cleanup function 79fi 80echo "Detected framerate: $FRAMERATE fps" 81 82# Extract frames from video (use -threads for faster extraction) 83echo "Extracting frames to $FRAMES_IN..." 84if ! ffmpeg -threads "$NUM_THREADS" -i "$INPUT_VIDEO" "$FRAMES_IN/frame_%04d.png" >/dev/null 2>&1; then 85 echo "Error: ffmpeg failed to extract frames from $INPUT_VIDEO." 86 cleanup_and_exit # Use cleanup function 87fi 88 89# Check if frames were extracted 90if ! ls "$FRAMES_IN"/frame_*.png > /dev/null 2>&1; then 91 echo "Error: No frames were extracted to $FRAMES_IN. Check input video and permissions." 92 cleanup_and_exit 93fi 94 95 96# Process frames in parallel using GNU Parallel 97if command -v parallel &>/dev/null; then 98 echo "Processing frames in parallel with $NUM_THREADS threads..." 99 # Need to export FRAMES_OUT for parallel to see it in subshells 100 export FRAMES_OUT 101 # Use parallel's built-in path manipulation {/} for basename 102 if ! find "$FRAMES_IN" -name "frame_*.png" | parallel --progress -j "$NUM_THREADS" \ 103 "magick {} -dither FloydSteinberg -define dither:diffusion-amount=100% -remap eink-2color.png \"$FRAMES_OUT/{/}\""; then 104 echo "Error: Parallel processing with magick failed." 105 cleanup_and_exit 106 fi 107else 108 echo "GNU Parallel not found. Processing frames sequentially..." 109 # Process each frame with the dithering effect 110 processed_count=0 111 for frame in "$FRAMES_IN"/frame_*.png; do 112 # Check if the file exists before processing 113 if [ -f "$frame" ]; then 114 # Construct output path correctly using basename 115 output_frame="$FRAMES_OUT/$(basename "$frame")" 116 if ! magick "$frame" -dither FloydSteinberg -define dither:diffusion-amount=100% -remap eink-2color.png "$output_frame"; then 117 echo "Error: magick failed to process $frame." 118 # Decide if you want to stop on first error or continue 119 cleanup_and_exit # Stop on first error 120 # continue # Or uncomment to continue processing other frames 121 fi 122 # echo "Processed: $frame" # Can be too verbose 123 processed_count=$((processed_count + 1)) 124 fi 125 done 126 echo "Processed $processed_count frames sequentially." 127 if [ $processed_count -eq 0 ]; then 128 echo "Error: No frames found to process sequentially in $FRAMES_IN" 129 cleanup_and_exit 130 fi 131fi 132 133# Check if output frames exist before attempting recombination 134if ! ls "$FRAMES_OUT"/frame_*.png > /dev/null 2>&1; then 135 echo "Error: No processed frames found in $FRAMES_OUT. Image processing likely failed." 136 cleanup_and_exit 137fi 138 139 140# Recombine frames into video with detected framerate (use -threads for faster encoding) 141echo "Recombining frames from $FRAMES_OUT..." 142# Allow ffmpeg to prompt for overwrite by removing output redirection 143if ! ffmpeg -framerate "$FRAMERATE" -threads "$NUM_THREADS" -i "$FRAMES_OUT/frame_%04d.png" -c:v libx264 -preset faster -pix_fmt yuv420p "$OUTPUT_VIDEO"; then 144 echo "Error: ffmpeg failed to recombine frames into $OUTPUT_VIDEO (or user cancelled overwrite)." 145 cleanup_and_exit # Use cleanup function 146fi 147 148# Use the cleanup function for consistency 149cleanup_and_exit() { 150 echo -e "\nCleaning up temporary files..." 151 # Only remove if directories were actually created 152 [ -n "$FRAMES_IN" ] && [ -d "$FRAMES_IN" ] && rm -rf "$FRAMES_IN" 153 [ -n "$FRAMES_OUT" ] && [ -d "$FRAMES_OUT" ] && rm -rf "$FRAMES_OUT" 154} 155# Call cleanup explicitly at the end (trap handles interruptions) 156cleanup_and_exit 157# Reset exit code to 0 for successful completion 158exit 0