an eink camera running on an rpi zero 2 w

feat: add dither video script

dunkirk.sh 9b7daf6e f920e50e

verified
Changed files
+158
+158
inkify-video.sh
···
+
#!/usr/bin/env -S nix shell nixpkgs#parallel nixpkgs#ffmpeg nixpkgs#imagemagick --command bash
+
+
# Define temporary directory variables globally so trap can access them
+
FRAMES_IN=""
+
FRAMES_OUT=""
+
+
# Function to clean up and exit
+
cleanup_and_exit() {
+
echo -e "\nInterrupted. Cleaning up temporary files..."
+
# Only remove if directories were actually created
+
[ -n "$FRAMES_IN" ] && [ -d "$FRAMES_IN" ] && rm -rf "$FRAMES_IN"
+
[ -n "$FRAMES_OUT" ] && [ -d "$FRAMES_OUT" ] && rm -rf "$FRAMES_OUT"
+
exit 1
+
}
+
+
# Set up trap for Ctrl+C (SIGINT)
+
trap cleanup_and_exit INT
+
+
# Check if input and output parameters are provided
+
if [ $# -lt 2 ]; then
+
echo "Usage: $0 input_video output_video [num_threads]"
+
exit 1
+
fi
+
+
INPUT_VIDEO=$1
+
OUTPUT_VIDEO=$2
+
# Default to number of CPU cores if threads not specified
+
NUM_THREADS=${3:-$(nproc)}
+
+
# Determine and create temporary directories, preferring RAM disk
+
TEMP_BASE=""
+
if [ -d "/dev/shm" ] && [ -w "/dev/shm" ]; then
+
TEMP_BASE="/dev/shm"
+
echo "Using RAM disk (/dev/shm) for temporary files."
+
elif [ -d "/tmp" ] && [ -w "/tmp" ] && mount | grep -q 'on /tmp type tmpfs'; then
+
TEMP_BASE="/tmp"
+
echo "Using RAM disk (/tmp) for temporary files."
+
else
+
TEMP_BASE="."
+
echo "RAM disk not available or not writable, using current directory for temporary files."
+
fi
+
+
# Create unique temporary directory names using process ID
+
# Add a random suffix for extra safety in case PID wraps or script runs concurrently
+
RAND_SUFFIX=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8)
+
FRAMES_IN="${TEMP_BASE}/inkify_frames_in_${$}_${RAND_SUFFIX}"
+
FRAMES_OUT="${TEMP_BASE}/inkify_frames_out_${$}_${RAND_SUFFIX}"
+
+
# Check if magick command exists
+
if ! command -v magick &>/dev/null; then
+
echo "Error: ImageMagick (magick command) not found. Please install it."
+
# Attempt cleanup before exiting
+
[ -d "$FRAMES_IN" ] && rm -rf "$FRAMES_IN"
+
[ -d "$FRAMES_OUT" ] && rm -rf "$FRAMES_OUT"
+
exit 1
+
fi
+
+
# Check if eink-2color.png exists
+
if [ ! -f "eink-2color.png" ]; then
+
echo "Error: Colormap file 'eink-2color.png' not found in the current directory."
+
# Attempt cleanup before exiting
+
[ -d "$FRAMES_IN" ] && rm -rf "$FRAMES_IN"
+
[ -d "$FRAMES_OUT" ] && rm -rf "$FRAMES_OUT"
+
exit 1
+
fi
+
+
# Create temporary directories only after checks pass
+
mkdir -p "$FRAMES_IN"
+
if [ $? -ne 0 ]; then echo "Error: Could not create temporary directory $FRAMES_IN"; exit 1; fi
+
mkdir -p "$FRAMES_OUT"
+
if [ $? -ne 0 ]; then echo "Error: Could not create temporary directory $FRAMES_OUT"; rm -rf "$FRAMES_IN"; exit 1; fi
+
+
+
# Extract frames from video and detect framerate
+
FRAMERATE=$(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}')
+
if [ -z "$FRAMERATE" ] || [ "$FRAMERATE" = "0.00" ]; then
+
echo "Error: Could not detect framerate for $INPUT_VIDEO."
+
cleanup_and_exit # Use cleanup function
+
fi
+
echo "Detected framerate: $FRAMERATE fps"
+
+
# Extract frames from video (use -threads for faster extraction)
+
echo "Extracting frames to $FRAMES_IN..."
+
if ! ffmpeg -threads "$NUM_THREADS" -i "$INPUT_VIDEO" "$FRAMES_IN/frame_%04d.png" >/dev/null 2>&1; then
+
echo "Error: ffmpeg failed to extract frames from $INPUT_VIDEO."
+
cleanup_and_exit # Use cleanup function
+
fi
+
+
# Check if frames were extracted
+
if ! ls "$FRAMES_IN"/frame_*.png > /dev/null 2>&1; then
+
echo "Error: No frames were extracted to $FRAMES_IN. Check input video and permissions."
+
cleanup_and_exit
+
fi
+
+
+
# Process frames in parallel using GNU Parallel
+
if command -v parallel &>/dev/null; then
+
echo "Processing frames in parallel with $NUM_THREADS threads..."
+
# Need to export FRAMES_OUT for parallel to see it in subshells
+
export FRAMES_OUT
+
# Use parallel's built-in path manipulation {/} for basename
+
if ! find "$FRAMES_IN" -name "frame_*.png" | parallel --progress -j "$NUM_THREADS" \
+
"magick {} -dither FloydSteinberg -define dither:diffusion-amount=100% -remap eink-2color.png \"$FRAMES_OUT/{/}\""; then
+
echo "Error: Parallel processing with magick failed."
+
cleanup_and_exit
+
fi
+
else
+
echo "GNU Parallel not found. Processing frames sequentially..."
+
# Process each frame with the dithering effect
+
processed_count=0
+
for frame in "$FRAMES_IN"/frame_*.png; do
+
# Check if the file exists before processing
+
if [ -f "$frame" ]; then
+
# Construct output path correctly using basename
+
output_frame="$FRAMES_OUT/$(basename "$frame")"
+
if ! magick "$frame" -dither FloydSteinberg -define dither:diffusion-amount=100% -remap eink-2color.png "$output_frame"; then
+
echo "Error: magick failed to process $frame."
+
# Decide if you want to stop on first error or continue
+
cleanup_and_exit # Stop on first error
+
# continue # Or uncomment to continue processing other frames
+
fi
+
# echo "Processed: $frame" # Can be too verbose
+
processed_count=$((processed_count + 1))
+
fi
+
done
+
echo "Processed $processed_count frames sequentially."
+
if [ $processed_count -eq 0 ]; then
+
echo "Error: No frames found to process sequentially in $FRAMES_IN"
+
cleanup_and_exit
+
fi
+
fi
+
+
# Check if output frames exist before attempting recombination
+
if ! ls "$FRAMES_OUT"/frame_*.png > /dev/null 2>&1; then
+
echo "Error: No processed frames found in $FRAMES_OUT. Image processing likely failed."
+
cleanup_and_exit
+
fi
+
+
+
# Recombine frames into video with detected framerate (use -threads for faster encoding)
+
echo "Recombining frames from $FRAMES_OUT..."
+
# Allow ffmpeg to prompt for overwrite by removing output redirection
+
if ! ffmpeg -framerate "$FRAMERATE" -threads "$NUM_THREADS" -i "$FRAMES_OUT/frame_%04d.png" -c:v libx264 -preset faster -pix_fmt yuv420p "$OUTPUT_VIDEO"; then
+
echo "Error: ffmpeg failed to recombine frames into $OUTPUT_VIDEO (or user cancelled overwrite)."
+
cleanup_and_exit # Use cleanup function
+
fi
+
+
# Use the cleanup function for consistency
+
cleanup_and_exit() {
+
echo -e "\nCleaning up temporary files..."
+
# Only remove if directories were actually created
+
[ -n "$FRAMES_IN" ] && [ -d "$FRAMES_IN" ] && rm -rf "$FRAMES_IN"
+
[ -n "$FRAMES_OUT" ] && [ -d "$FRAMES_OUT" ] && rm -rf "$FRAMES_OUT"
+
}
+
# Call cleanup explicitly at the end (trap handles interruptions)
+
cleanup_and_exit
+
# Reset exit code to 0 for successful completion
+
exit 0