Skip to content

Instantly share code, notes, and snippets.

@guyjin
Created July 14, 2025 15:36
Show Gist options
  • Select an option

  • Save guyjin/a0137aeb47f2ce0cb7341160222b72a3 to your computer and use it in GitHub Desktop.

Select an option

Save guyjin/a0137aeb47f2ce0cb7341160222b72a3 to your computer and use it in GitHub Desktop.
Batch create thumbnails with ImageMagick in zsh
#!/bin/zsh
# ^ Changed shebang to /bin/zsh
# Output directory for thumbnails
OUTPUT_DIR=".thumbs"
FORCE=0
DRY_RUN=0
THUMBNAIL_DIMENSIONS="200x150" # Default size for landscape/portrait images
# ANSI colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[1;36m'
RED='\033[0;31m'
NC='\033[0m' # No color
# --- Argument Parsing ---
while [[ $# -gt 0 ]]; do
case "$1" in
--force)
FORCE=1
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
*)
echo -e "${RED}Unknown option: $1${NC}" >&2
echo "Usage: $(basename "$0") [--force] [--dry-run]" >&2
exit 1
;;
esac
done
# --- Helper Function for Image Processing (THIS FUNCTION WILL BE RE-DEFINED FOR XARGS) ---
# We keep this for clarity/testing in a non-xargs context, but it won't be directly called by xargs.
# The version used by xargs is injected into the 'bash -c' string.
# (Leaving this 'local' definition commented out as it's not strictly necessary for the xargs path)
# process_image() { ... }
# Export variables needed by process_image
export OUTPUT_DIR FORCE DRY_RUN THUMBNAIL_DIMENSIONS
export GREEN YELLOW CYAN RED NC
# --- Main Logic ---
# Collect image list first using Zsh's array assignment with null splitting
echo -e "${CYAN}Searching for images...${NC}"
local -a image_list # Declare as array
image_list=( "${(@0)$(find . -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \) -print0)}" )
# Check if any images were found
if (( ${#image_list[@]} == 0 )); then
echo -e "${RED}No images found.${NC}"
exit 0
fi
# Track if any thumbnails were actually created by ImageMagick (not just dry-run/skipped)
local actual_thumbs_created=0
echo -e "${CYAN}Processing images...${NC}"
# Define the function as a string to be passed to bash -c
# We need to escape any single quotes within the function if the outer string uses single quotes.
# Here-doc (<<<) makes this easier.
read -r -d '' PROCESS_IMAGE_FUNC_DEF << 'EOF'
process_image() {
local img="$1"
local thumb_full_path=""
local rel_path="${img#./}"
thumb_full_path="$OUTPUT_DIR/$rel_path"
local thumb_dir=$(dirname "$thumb_full_path")
if [[ $FORCE -eq 0 && -f "$thumb_full_path" ]]; then
echo -e "${YELLOW}Skipping (exists): $thumb_full_path${NC}"
echo "SKIPPED"
return 0
fi
if [[ $DRY_RUN -eq 1 ]]; then
if [[ $FORCE -eq 1 && -f "$thumb_full_path" ]]; then
echo -e "${CYAN}Would recreate: $thumb_full_path${NC}"
else
echo -e "${CYAN}Would create: $thumb_full_path${NC}"
fi
echo "DRY_RUN"
return 0
fi
if ! mkdir -p "$thumb_dir"; then
echo -e "${RED}Error: Failed to create directory: $thumb_dir${NC}" >&2
echo "ERROR"
return 1
fi
local dimensions=$(magick identify -format "%w %h" "$img" 2>/dev/null)
if [ -z "$dimensions" ]; then
echo -e "${RED}Error: Unable to read image: $img${NC}" >&2
echo "ERROR"
return 1
fi
# Use bash array indexing (0-based) inside the bash -c subshell
# We are in a bash subshell, so `read -A` (zsh) won't work, stick to bash's `read -a`
local -a dims_array
read -r -a dims_array <<< "$dimensions"
local width="${dims_array[0]}" # Bash arrays are 0-indexed
local height="${dims_array[1]}"
local size="$THUMBNAIL_DIMENSIONS"
if (( width > height )); then
size="200x150"
else
size="150x200"
fi
if magick "$img" -thumbnail "$size^" -gravity center -extent "$size" "$thumb_full_path"; then
echo -e "${GREEN}Created: $thumb_full_path${NC}"
echo "CREATED"
return 0
else
echo -e "${RED}Error: Failed to convert image: $img${NC}" >&2
echo "ERROR"
return 1
fi
}
EOF
# The xargs command now passes the function definition and then calls it
results=$(printf '%s\0' "${image_list[@]}" | xargs -0 -n 1 -P 12 bash -c "${PROCESS_IMAGE_FUNC_DEF}; process_image \"\$0\"")
# Process results to determine if any actual thumbnails were created
while IFS= read -r line; do
if [[ "$line" == "CREATED" ]]; then
((actual_thumbs_created++))
fi
done <<< "$results"
# --- Cleanup and Final Messages ---
if [[ $DRY_RUN -eq 0 ]]; then
if [[ $actual_thumbs_created -eq 0 ]]; then
if [[ -d "$OUTPUT_DIR" && -z "$(find "$OUTPUT_DIR" -maxdepth 0 -empty -print -quit 2>/dev/null)" ]]; then
true
elif [[ -d "$OUTPUT_DIR" ]]; then
echo -e "${YELLOW}No new thumbnails created. Removing empty directory: $OUTPUT_DIR${NC}"
rmdir "$OUTPUT_DIR" 2>/dev/null
fi
else
echo -e "${GREEN}Successfully created $actual_thumbs_created new thumbnails in $OUTPUT_DIR.${NC}"
fi
else
echo -e "${CYAN}Dry run complete. No files were modified.${NC}"
fi
exit 0
@guyjin

guyjin commented Jul 14, 2025

Copy link
Copy Markdown
Author

This is ABSOLUTELY not my code. Generated originally with ChatGPT and then error checked and modified with Gemini.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment