Skip to content

Instantly share code, notes, and snippets.

@svemat01
Created May 6, 2026 17:25
Show Gist options
  • Select an option

  • Save svemat01/e12f6f94a389241ae82d47a7bd976665 to your computer and use it in GitHub Desktop.

Select an option

Save svemat01/e12f6f94a389241ae82d47a7bd976665 to your computer and use it in GitHub Desktop.
IMG Optimization script
#!/usr/bin/env bash
# img – lightweight helpers for image workflows.
#
# Usage:
# img [--dir path] <command> [files...]
#
# Subcommands:
# webp Convert non-WebP assets to WebP via cwebp.
# pngquant Optimize PNG files with pngquant.
# resize Resize a single image with flexible strategies.
#
# Key ideas:
# - Keep each subcommand self-contained and focused.
# - Share a simple picker (gum filter by default, fzf optional).
# - Never overwrite existing files unless the user explicitly chooses to.
#
# Requirements:
# gum (brew install gum)
# cwebp (brew install webp) for `img webp`
# pngquant (brew install pngquant) for `img pngquant`
# Optional:
# fzf (required selector; brew install fzf)
# identify (ImageMagick; brew install imagemagick) for fzf preview
#
# Environment knobs:
# IMG_DEFAULT_DIR – default search root (defaults to cwd)
# IMG_DEFAULT_QUALITY – default WebP quality (0-100, default 85)
# IMG_PNG_QUALITY – default pngquant quality range (default 65-90)
# IMG_PICKER – "gum" (default) or "fzf"
# IMG_DEBUG – if set, show debug logs
set -euo pipefail
GUM_BIN=${GUM_BIN:-gum}
FZF_BIN=${FZF_BIN:-fzf}
IMG_VERSION="0.3.0"
QUICK_MODE=false
# Trap for cleanup on exit/interrupt
declare -a TEMP_FILES=()
cleanup() {
if [[ ${#TEMP_FILES[@]} -gt 0 ]]; then
for f in "${TEMP_FILES[@]}"; do
rm -f "$f" 2>/dev/null
done
fi
}
trap cleanup EXIT
DEFAULT_SEARCH_ROOT=${IMG_DEFAULT_DIR:-$(pwd)}
DEFAULT_WEBP_QUALITY=${IMG_DEFAULT_QUALITY:-85}
DEFAULT_PNG_QUALITY=${IMG_PNG_QUALITY:-65-90}
GLOBAL_DIR="$DEFAULT_SEARCH_ROOT"
usage() {
cat <<'USAGE'
img – focused helpers for common image chores.
Examples:
img webp # Select images (non-WebP) and convert to WebP
img webp assets/*.png # Convert specific files without the picker
img --quick webp *.png # Quick mode: skip prompts, use defaults
img pngquant # Optimize PNG files via pngquant
img info photo.jpg # Show image metadata
Options:
-d, --dir <path> Base directory for interactive selection (default: cwd or $IMG_DEFAULT_DIR)
-q, --quick Skip interactive prompts, use sensible defaults
--version Show version number
-h, --help Show this message
Subcommands:
webp Convert assets to WebP (cwebp)
pngquant Optimize PNG files (pngquant)
resize Resize a single image (ImageMagick)
info Display image metadata (dimensions, format, size)
Set IMG_PICKER=fzf to use fzf instead of gum filter. Logs route through `gum log`.
USAGE
}
has_cmd() {
command -v "$1" >/dev/null 2>&1
}
die() {
"$GUM_BIN" log --level error "$1" >&2
exit 1
}
log_info() {
"$GUM_BIN" log --level info "$@"
}
log_warn() {
"$GUM_BIN" log --level warn "$@"
}
log_debug() {
if [[ -n "${IMG_DEBUG:-}" ]]; then
"$GUM_BIN" log --level debug "$@"
fi
}
log_progress() {
local current="$1" total="$2" label="$3"
"$GUM_BIN" log --level info "[$current/$total] $label"
}
get_file_size() {
stat -f%z "$1" 2>/dev/null || stat -c%s "$1" 2>/dev/null || echo 0
}
format_bytes() {
local bytes="$1"
if [[ $bytes -ge 1073741824 ]]; then
awk -v b="$bytes" 'BEGIN{printf "%.2f GB", b/1073741824}'
elif [[ $bytes -ge 1048576 ]]; then
awk -v b="$bytes" 'BEGIN{printf "%.2f MB", b/1048576}'
elif [[ $bytes -ge 1024 ]]; then
awk -v b="$bytes" 'BEGIN{printf "%.2f KB", b/1024}'
else
printf '%d B' "$bytes"
fi
}
print_summary() {
local count="$1" original_size="$2" output_size="$3" start_time="$4"
local end_time elapsed savings_pct
end_time=$(date +%s)
elapsed=$((end_time - start_time))
if [[ $original_size -gt 0 ]]; then
savings_pct=$(awk -v orig="$original_size" -v out="$output_size" 'BEGIN{printf "%.1f", (1 - out/orig) * 100}')
else
savings_pct="0"
fi
echo ""
"$GUM_BIN" style --border rounded --padding "0 1" --border-foreground 212 \
"$(printf 'Summary\n───────────────────\nFiles processed: %d\nOriginal size: %s\nOutput size: %s\nSpace saved: %s%%\nTime elapsed: %ds' \
"$count" "$(format_bytes "$original_size")" "$(format_bytes "$output_size")" "$savings_pct" "$elapsed")"
}
copy_to_clipboard() {
local -a paths=("$@")
if [[ "$QUICK_MODE" == "true" ]]; then
return
fi
local clip_cmd=""
if has_cmd pbcopy; then
clip_cmd="pbcopy"
elif has_cmd xclip; then
clip_cmd="xclip -selection clipboard"
elif has_cmd xsel; then
clip_cmd="xsel --clipboard --input"
else
return
fi
if "$GUM_BIN" confirm "Copy output path(s) to clipboard?"; then
printf '%s\n' "${paths[@]}" | $clip_cmd
log_info "Copied ${#paths[@]} path(s) to clipboard"
fi
}
require_cmd() {
local bin="$1"
local hint="$2"
if ! has_cmd "$bin"; then
die "${bin} is required. ${hint}"
fi
}
tolower() {
printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
}
contains_item() {
local needle="$1"
shift || true
for candidate in "$@"; do
if [[ "$needle" == "$candidate" ]]; then
return 0
fi
done
return 1
}
scan_tree() {
local dir="$1"
local tmp
tmp=$(mktemp)
TEMP_FILES+=("$tmp")
"$GUM_BIN" spin --spinner line --title "Scanning ${dir}" -- bash -c '
set -euo pipefail
dir="$1"
out="$2"
find "$dir" \
\( -path "*/node_modules" -o -path "*/.git" -o -path "*/.hg" -o -path "*/.svn" -o -path "*/.next" -o -path "*/dist" -o -path "*/build" -o -path "*/.cache" -o -path "*/.turbo" -o -path "*/.venv" -o -path "*/.*" \) -prune -o \
-type f -print | sort >"$out"
' _ "$dir" "$tmp"
cat "$tmp"
rm -f "$tmp"
}
list_candidates() {
local dir="$1"
local allowed_csv="${2:-}"
local disallowed_csv="${3:-}"
local -a allowed=()
local -a disallowed=()
if [[ -n "$allowed_csv" ]]; then
IFS=',' read -r -a allowed <<< "$allowed_csv"
fi
if [[ -n "$disallowed_csv" ]]; then
IFS=',' read -r -a disallowed <<< "$disallowed_csv"
fi
while IFS= read -r path; do
[[ -z "$path" ]] && continue
local ext="${path##*.}"
if [[ "$path" == "$ext" ]]; then
continue
fi
ext=$(tolower "$ext")
if [[ ${#allowed[@]} -gt 0 ]] && ! contains_item "$ext" "${allowed[@]}"; then
continue
fi
if [[ ${#disallowed[@]} -gt 0 ]] && contains_item "$ext" "${disallowed[@]}"; then
continue
fi
printf '%s\n' "$path"
done < <(scan_tree "$dir")
}
fzf_select() {
require_cmd "$FZF_BIN" "Install fzf (brew install fzf)."
local placeholder="$1"
local mode="$2" # multi or single
local preview_cmd=$'identify -verbose {} 2>/dev/null | awk \'/Geometry|Format|Filesize/{print $0}\'; file {} 2>/dev/null'
local opts=(--preview "$preview_cmd" --border --prompt "$placeholder > " --scheme=path)
if [[ "$mode" == "multi" ]]; then
opts+=(--multi)
else
opts+=(--no-multi)
fi
"$FZF_BIN" "${opts[@]}"
}
select_assets() {
local dir="$1"
local placeholder="$2"
local allowed="$3"
local disallowed="${4:-}"
local mode="${5:-multi}"
local -a candidates=()
while IFS= read -r path; do
[[ -z "$path" ]] && continue
candidates+=("$path")
done < <(list_candidates "$dir" "$allowed" "$disallowed")
if [[ ${#candidates[@]} -eq 0 ]]; then
die "No matching files found under ${dir}"
fi
log_debug "Selector prepared ${#candidates[@]} candidate(s) from ${dir}"
local selection=""
selection=$(printf '%s\n' "${candidates[@]}" | fzf_select "$placeholder" "$mode") || return 1
[[ -z "$selection" ]] && return 1
printf '%s\n' "$selection"
}
next_available_path() {
local desired="$1"
if [[ ! -e "$desired" ]]; then
printf '%s\n' "$desired"
return
fi
local dir base ext stem counter=1 candidate
dir=$(dirname "$desired")
base=$(basename "$desired")
ext="${base##*.}"
stem="${base%.*}"
while true; do
candidate="${dir}/${stem}_${counter}.${ext}"
if [[ ! -e "$candidate" ]]; then
printf '%s\n' "$candidate"
return
fi
counter=$((counter + 1))
done
}
copy_path_with_suffix() {
local src="$1"
local suffix="$2"
local forced_ext="${3:-}"
local dir base ext stem
dir=$(dirname "$src")
base=$(basename "$src")
ext="${base##*.}"
stem="${base%.*}"
if [[ -n "$forced_ext" ]]; then
ext="$forced_ext"
fi
local desired="${dir}/${stem}${suffix}.${ext}"
next_available_path "$desired"
}
select_or_args() {
local allowed="$1"
local disallowed="$2"
local mode="multi"
if [[ $# -ge 3 ]]; then
case "$3" in
multi|single)
mode="$3"
shift 3
;;
*)
shift 2
;;
esac
else
shift 2
fi
local -a provided=("$@")
local -a files=()
if [[ ${#provided[@]} -gt 0 ]]; then
local -a allowed_arr=()
local -a disallowed_arr=()
[[ -n "$allowed" ]] && IFS=',' read -r -a allowed_arr <<< "$allowed"
[[ -n "$disallowed" ]] && IFS=',' read -r -a disallowed_arr <<< "$disallowed"
for path in "${provided[@]}"; do
if [[ ! -f "$path" ]]; then
log_warn "Skipping missing file: $path"
continue
fi
local ext="${path##*.}"
ext=$(tolower "$ext")
if [[ ${#allowed_arr[@]} -gt 0 ]] && ! contains_item "$ext" "${allowed_arr[@]}"; then
log_warn "Skipping unsupported file type: $path"
continue
fi
if [[ ${#disallowed_arr[@]} -gt 0 ]] && contains_item "$ext" "${disallowed_arr[@]}"; then
log_warn "Skipping disallowed file type: $path"
continue
fi
files+=("$path")
done
fi
if [[ "$mode" == "single" && ${#files[@]} -gt 1 ]]; then
log_warn "Multiple files provided; using ${files[0]}"
files=("${files[0]}")
fi
if [[ ${#files[@]} -gt 0 ]]; then
printf '%s\n' "${files[@]}"
return 0
fi
local selection=""
if ! selection=$(select_assets "$GLOBAL_DIR" "Select files" "$allowed" "$disallowed" "$mode"); then
return 1
fi
local -a chosen=()
while IFS= read -r line; do
[[ -z "$line" ]] && continue
chosen+=("$line")
done <<< "$selection"
printf '%s\n' "${chosen[@]}"
}
cmd_webp() {
require_cmd cwebp "Install via brew install webp."
local data
if ! data=$(select_or_args "png,jpg,jpeg,bmp,gif,tif,tiff,heic,avif" "webp" "multi" "$@"); then
return 1
fi
local -a files=()
while IFS= read -r item; do
[[ -z "$item" ]] && continue
files+=("$item")
done <<< "$data"
log_debug "cmd_webp: Parsed ${#files[@]} file(s) from selection"
if [[ ${#files[@]} -eq 0 ]]; then
die "No eligible files selected for WebP conversion."
fi
local quality="$DEFAULT_WEBP_QUALITY"
if [[ "$QUICK_MODE" != "true" ]]; then
quality=$("$GUM_BIN" input \
--placeholder "$DEFAULT_WEBP_QUALITY" \
--value "$DEFAULT_WEBP_QUALITY" \
--header "WebP quality (0-100)\n60-75=small file · 80-90=crisp hero · 100=lossless")
quality=${quality:-$DEFAULT_WEBP_QUALITY}
fi
local custom_dir=""
if [[ "$QUICK_MODE" != "true" ]]; then
local dest_choice
dest_choice=$("$GUM_BIN" choose --header "Where should .webp files be saved? Same dir keeps assets together · Custom dir good for build outputs" \
"Same directory" \
"Custom directory")
if [[ "$dest_choice" == "Custom directory" ]]; then
custom_dir=$("$GUM_BIN" input --placeholder "$GLOBAL_DIR/webp" --header "Output directory")
custom_dir=${custom_dir:-"$GLOBAL_DIR/webp"}
mkdir -p "$custom_dir"
log_debug "cmd_webp: Using custom directory: $custom_dir"
else
log_debug "cmd_webp: Using same directory as source files"
fi
else
log_debug "cmd_webp: Quick mode - using same directory as source files"
fi
local -a outputs=()
local total=${#files[@]}
local current=0
local start_time total_original_size=0 total_output_size=0
start_time=$(date +%s)
for src in "${files[@]}"; do
current=$((current + 1))
local base dir target_dir desired out src_size out_size
base=$(basename "$src")
base="${base%.*}"
dir=$(dirname "$src")
target_dir=${custom_dir:-$dir}
mkdir -p "$target_dir"
desired="${target_dir}/${base}.webp"
out=$(next_available_path "$desired")
log_debug "cmd_webp: Converting $src -> $out (quality=$quality, target_dir=$target_dir)"
log_progress "$current" "$total" "Converting $(basename "$src")"
"$GUM_BIN" spin --title "Converting $(basename "$src")" -- cwebp -q "$quality" "$src" -o "$out"
src_size=$(get_file_size "$src")
out_size=$(get_file_size "$out")
total_original_size=$((total_original_size + src_size))
total_output_size=$((total_output_size + out_size))
log_info "Saved $out"
outputs+=("$out")
done
print_summary "$total" "$total_original_size" "$total_output_size" "$start_time"
# Offer to delete originals
if [[ "$QUICK_MODE" != "true" && ${#files[@]} -gt 0 ]]; then
if "$GUM_BIN" confirm "Delete ${#files[@]} original file(s)?"; then
for f in "${files[@]}"; do
rm -f "$f"
done
log_info "Deleted ${#files[@]} original file(s)"
fi
fi
# # Offer to copy to clipboard
# copy_to_clipboard "${outputs[@]}"
printf '%s\n' "${outputs[@]}"
}
cmd_pngquant() {
require_cmd pngquant "Install via brew install pngquant."
local data
if ! data=$(select_or_args "png" "" "multi" "$@"); then
return 1
fi
local -a files=()
while IFS= read -r item; do
[[ -z "$item" ]] && continue
files+=("$item")
done <<< "$data"
if [[ ${#files[@]} -eq 0 ]]; then
die "No PNG files selected."
fi
local quality_range="$DEFAULT_PNG_QUALITY"
local speed="3"
local output_mode="Write copies with suffix (_optimized)"
local suffix="_optimized"
local overwrite=false
if [[ "$QUICK_MODE" != "true" ]]; then
quality_range=$("$GUM_BIN" input \
--placeholder "$DEFAULT_PNG_QUALITY" \
--value "$DEFAULT_PNG_QUALITY" \
--header "pngquant quality range (min-max)\n65-80=smaller, 80-95=safer")
quality_range=${quality_range:-$DEFAULT_PNG_QUALITY}
speed=$("$GUM_BIN" input \
--placeholder "1-10" \
--value "3" \
--header "pngquant speed (1=best quality, 10=fastest)")
speed=${speed:-3}
output_mode=$("$GUM_BIN" choose --header "Output handling\nCopies keep originals · Overwrite saves space if you're sure" \
"Write copies with suffix (_optimized)" \
"Overwrite originals")
if [[ "$output_mode" == "Overwrite originals" ]]; then
if "$GUM_BIN" confirm "Overwrite the selected PNG files?"; then
overwrite=true
else
output_mode="Write copies with suffix (_optimized)"
fi
fi
fi
local -a outputs=()
local -a originals_to_delete=()
local total=${#files[@]}
local current=0
local start_time total_original_size=0 total_output_size=0
start_time=$(date +%s)
for src in "${files[@]}"; do
current=$((current + 1))
local dest src_size out_size
if [[ "$output_mode" == "Write copies with suffix (_optimized)" ]]; then
dest=$(copy_path_with_suffix "$src" "$suffix" "png")
originals_to_delete+=("$src")
else
dest="$src"
fi
src_size=$(get_file_size "$src")
total_original_size=$((total_original_size + src_size))
log_progress "$current" "$total" "Optimizing $(basename "$src")"
"$GUM_BIN" spin --title "Optimizing $(basename "$src")" -- pngquant --quality "$quality_range" --speed "$speed" --strip --force --output "$dest" "$src"
out_size=$(get_file_size "$dest")
total_output_size=$((total_output_size + out_size))
log_info "Saved $dest"
outputs+=("$dest")
done
print_summary "$total" "$total_original_size" "$total_output_size" "$start_time"
# Offer to delete originals (only if we made copies, not if we overwrote)
if [[ "$QUICK_MODE" != "true" && ${#originals_to_delete[@]} -gt 0 ]]; then
if "$GUM_BIN" confirm "Delete ${#originals_to_delete[@]} original file(s)?"; then
for f in "${originals_to_delete[@]}"; do
rm -f "$f"
done
log_info "Deleted ${#originals_to_delete[@]} original file(s)"
fi
fi
# # Offer to copy to clipboard
# copy_to_clipboard "${outputs[@]}"
printf '%s\n' "${outputs[@]}"
}
cmd_resize() {
require_cmd magick "Install ImageMagick (brew install imagemagick)."
local data
if ! data=$(select_or_args "png,jpg,jpeg,bmp,gif,tif,tiff,heic,avif,webp" "" "single" "$@"); then
return 1
fi
local -a files=()
while IFS= read -r item; do
[[ -z "$item" ]] && continue
files+=("$item")
done <<< "$data"
local src="${files[0]:-}"
if [[ -z "$src" ]]; then
die "No image selected for resizing."
fi
local meta
meta=$(magick identify -format "%w %h %m" "$src") || die "Unable to inspect $src"
local orig_w orig_h format
read -r orig_w orig_h format <<< "$meta"
local base=$(basename "$src")
local strategy
strategy=$("$GUM_BIN" choose --header "Resize $base (${orig_w}x${orig_h} $format)\nPick how you'd like to transform it." \
"Scale by multiplier • Quick % shrink/grow" \
"Common preset width • Hero/device sizes" \
"Custom width • Keep aspect ratio" \
"Custom height • Keep aspect ratio" \
"Exact size • Fit / fill / stretch")
local -a ops=(-auto-orient)
case "$strategy" in
"Scale by multiplier • Quick % shrink/grow")
local mult_choice
mult_choice=$("$GUM_BIN" choose --header "Pick multiplier\n0.25×=moodboard, 0.5×=retina export, 2×=upscale" \
"0.25× – Quarter size for previews" \
"0.5× – Half size for retina/base" \
"0.75× – Slight reduction" \
"2× – Upscale for retina assets" \
"Custom multiplier…")
local factor=""
case "$mult_choice" in
"0.25× – Quarter size for previews") factor="25%";;
"0.5× – Half size for retina/base") factor="50%";;
"0.75× – Slight reduction") factor="75%";;
"2× – Upscale for retina assets") factor="200%";;
"Custom multiplier…")
local custom_mult
custom_mult=$("$GUM_BIN" input --placeholder "e.g. 1.3" --header "Enter multiplier (e.g. 0.8, 1.5)") || return 1
custom_mult=${custom_mult:-1}
factor=$(awk -v m="$custom_mult" 'BEGIN{printf "%.2f%%", m*100}')
;;
esac
ops+=(-resize "$factor")
;;
"Common preset width • Hero/device sizes")
local preset
preset=$("$GUM_BIN" choose --header "Choose a width (height scales automatically)" \
"3840 – 4K / large hero" \
"2560 – Desktop retina hero" \
"1920 – HD hero/banner" \
"1440 – Laptop breakpoint" \
"1280 – Tablet landscape" \
"1024 – Small desktop / blog" \
"720 – Social preview" \
"Custom width…")
local width_target=""
case "$preset" in
"3840 – 4K / large hero") width_target=3840;;
"2560 – Desktop retina hero") width_target=2560;;
"1920 – HD hero/banner") width_target=1920;;
"1440 – Laptop breakpoint") width_target=1440;;
"1280 – Tablet landscape") width_target=1280;;
"1024 – Small desktop / blog") width_target=1024;;
"720 – Social preview") width_target=720;;
"Custom width…")
width_target=$("$GUM_BIN" input --placeholder "$orig_w" --header "Target width in pixels (aspect preserved)") || return 1
;;
esac
ops+=(-resize "${width_target}x")
;;
"Custom width • Keep aspect ratio")
local width_input
width_input=$("$GUM_BIN" input --placeholder "$orig_w" --header "Enter width in pixels (height scales to match)") || return 1
ops+=(-resize "${width_input}x")
;;
"Custom height • Keep aspect ratio")
local height_input
height_input=$("$GUM_BIN" input --placeholder "$orig_h" --header "Enter height in pixels (width scales to match)") || return 1
ops+=(-resize "x${height_input}")
;;
"Exact size • Fit / fill / stretch")
local dims
dims=$("$GUM_BIN" input --placeholder "1920x1080" --header "Enter final size as WIDTHxHEIGHT") || return 1
if [[ ! "$dims" =~ ^[0-9]+x[0-9]+$ ]]; then
die "Invalid dimensions: $dims"
fi
local target_w="${dims%x*}"
local target_h="${dims#*x}"
local fit_choice
fit_choice=$("$GUM_BIN" choose --header "How should ${target_w}x${target_h} be produced?\nFit keeps entire image, Cover crops, Stretch distorts." \
"Contain (fit inside, may letterbox)" \
"Cover (fill + crop center)" \
"Stretch (force exact, can distort)")
case "$fit_choice" in
"Contain (fit inside, may letterbox)")
ops+=(-resize "${target_w}x${target_h}")
;;
"Cover (fill + crop center)")
ops+=(-resize "${target_w}x${target_h}^" -gravity center -extent "${target_w}x${target_h}")
;;
"Stretch (force exact, can distort)")
ops+=(-resize "${target_w}x${target_h}!")
;;
esac
;;
esac
local destination_mode
destination_mode=$("$GUM_BIN" choose --header "Where should the resized file go?\nCopies keep originals safe; overwrite only when sure." \
"Copy with suffix (_resized)" \
"Copy into custom directory" \
"Overwrite original")
local dest=""
case "$destination_mode" in
"Copy with suffix (_resized)")
local suffix
suffix=$("$GUM_BIN" input --placeholder "_resized" --value "_resized" --header "Suffix to append before extension") || return 1
suffix=${suffix:-_resized}
dest=$(copy_path_with_suffix "$src" "$suffix")
;;
"Copy into custom directory")
local custom_dir
custom_dir=$("$GUM_BIN" input --placeholder "$GLOBAL_DIR/resized" --header "Directory for resized copies") || return 1
custom_dir=${custom_dir:-"$GLOBAL_DIR/resized"}
mkdir -p "$custom_dir"
dest=$(next_available_path "$custom_dir/$(basename "$src")")
;;
"Overwrite original")
if "$GUM_BIN" confirm "Really overwrite $base?"; then
dest="$src"
else
dest=$(copy_path_with_suffix "$src" "_resized")
fi
;;
esac
"$GUM_BIN" spin --title "Resizing $base" -- magick "$src" "${ops[@]}" "$dest"
log_info "Saved $dest"
# # Offer to copy to clipboard
# copy_to_clipboard "$dest"
printf '%s\n' "$dest"
}
cmd_info() {
require_cmd magick "Install ImageMagick (brew install imagemagick)."
local data
if ! data=$(select_or_args "png,jpg,jpeg,bmp,gif,tif,tiff,heic,avif,webp,svg" "" "single" "$@"); then
return 1
fi
local -a files=()
while IFS= read -r item; do
[[ -z "$item" ]] && continue
files+=("$item")
done <<< "$data"
local src="${files[0]:-}"
if [[ -z "$src" ]]; then
die "No image selected."
fi
local meta size_bytes size_human
meta=$(magick identify -format "Format: %m\nDimensions: %wx%h\nColor: %[colorspace]\nDepth: %z-bit\nType: %[type]" "$src" 2>/dev/null) || die "Unable to inspect $src"
size_bytes=$(get_file_size "$src")
size_human=$(format_bytes "$size_bytes")
local filename
filename=$(basename "$src")
echo ""
"$GUM_BIN" style --border rounded --padding "0 1" --border-foreground 39 \
"$(printf '%s\n───────────────────\n%s\nFile size: %s\nPath: %s' "$filename" "$meta" "$size_human" "$src")"
}
main() {
require_cmd "$GUM_BIN" "Install gum (brew install gum)."
local args=("$@")
local idx=0
while [[ $idx -lt ${#args[@]} ]]; do
case "${args[$idx]}" in
-d|--dir)
(( idx + 1 < ${#args[@]} )) || die "--dir requires a value"
GLOBAL_DIR="${args[$((idx + 1))]}"
idx=$((idx + 2))
;;
-q|--quick)
QUICK_MODE=true
idx=$((idx + 1))
;;
--version)
echo "$IMG_VERSION"
return 0
;;
-h|--help)
usage
return 0
;;
--)
idx=$((idx + 1))
break
;;
*)
break
;;
esac
done
local -a remaining=()
while [[ $idx -lt ${#args[@]} ]]; do
remaining+=("${args[$idx]}")
idx=$((idx + 1))
done
local subcommand=""
if [[ ${#remaining[@]} -gt 0 ]]; then
subcommand="${remaining[0]}"
if [[ ${#remaining[@]} -gt 1 ]]; then
remaining=("${remaining[@]:1}")
else
remaining=()
fi
else
usage
return 0
fi
case "$subcommand" in
webp)
if [[ ${#remaining[@]} -gt 0 ]]; then
cmd_webp "${remaining[@]}"
else
cmd_webp
fi
;;
pngquant)
if [[ ${#remaining[@]} -gt 0 ]]; then
cmd_pngquant "${remaining[@]}"
else
cmd_pngquant
fi
;;
resize)
if [[ ${#remaining[@]} -gt 0 ]]; then
cmd_resize "${remaining[@]}"
else
cmd_resize
fi
;;
info)
if [[ ${#remaining[@]} -gt 0 ]]; then
cmd_info "${remaining[@]}"
else
cmd_info
fi
;;
help)
usage
;;
*)
usage
die "Unknown command: $subcommand"
;;
esac
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment