#!/usr/bin/env bash # Image conversion utility supporting multiple formats # Usage: convert.sh [options] [output] set -e # Default values QUALITY=90 RESIZE="" FORMAT="" VERBOSE=false # Show usage usage() { cat <<'EOF' Usage: convert.sh [options] [output] Options: -f, --format Output format (png, jpg, webp, gif, bmp, tiff, avif, heic) -q, --quality <1-100> Quality for lossy formats (default: 85) -r, --resize Resize (e.g., 800x600, 50%, 1920x) -v, --verbose Verbose output -h, --help Show this help Examples: convert.sh input.png output.jpg convert.sh -f webp -q 90 input.png output.webp convert.sh -f jpg -r 1920x input.png output.jpg convert.sh -f webp *.png # Batch convert EOF exit 0 } # Check dependencies check_deps() { local has_im=false local has_cwebp=false local has_dwebp=false if command -v convert &>/dev/null || command -v magick &>/dev/null; then has_im=true fi if command -v cwebp &>/dev/null; then has_cwebp=true fi if command -v dwebp &>/dev/null; then has_dwebp=true fi echo "$has_im|$has_cwebp|$has_dwebp" } # Get ImageMagick command # Prefers 'magick' (ImageMagick 7+) over deprecated 'convert' get_im_cmd() { if command -v magick &>/dev/null; then echo "magick" else echo "convert" fi } # Get file extension (lowercase) get_extension() { local filename="$1" echo "${filename##*.}" | tr '[:upper:]' '[:lower:]' } # Get basename without extension get_basename() { local filename="$1" echo "${filename%.*}" } # Convert using ImageMagick convert_with_im() { local input="$1" local output="$2" local format="$3" local quality="$4" local resize="$5" local cmd cmd=("$(get_im_cmd)") cmd+=("$input") if [[ -n "$resize" ]]; then cmd+=("-resize" "$resize") fi if [[ "$format" == "jpg" || "$format" == "jpeg" ]]; then cmd+=("-quality" "$quality") elif [[ "$format" == "webp" ]]; then cmd+=("-quality" "$quality" "-define" "webp:target-quality=$quality") fi cmd+=("$output") if [[ "$VERBOSE" == true ]]; then echo "Running: ${cmd[*]}" fi "${cmd[@]}" } # Convert WebP with cwebp (better quality/size ratio) convert_to_webp() { local input="$1" local output="$2" local quality="$3" local resize="$4" local opts=() if [[ -n "$resize" ]]; then # Parse resize geometry (e.g., "800x600" or "50%") if [[ "$resize" == *%* ]]; then local percent="${resize%%%}" opts+=("-size" "${percent}%") else opts+=("-resize" "$resize") fi fi opts+=("-q" "$quality" "$input" "-o" "$output") if [[ "$VERBOSE" == true ]]; then echo "Running: cwebp ${opts[*]}" fi cwebp "${opts[@]}" } # Convert from WebP with dwebp convert_from_webp() { local input="$1" local output="$2" local resize="$3" local opts=() if [[ -n "$resize" ]]; then # dwebp doesn't support resize directly, use ImageMagick convert_with_im "$input" "$output" "" "" "$resize" return fi opts+=("$input" "-o" "$output") if [[ "$VERBOSE" == true ]]; then echo "Running: dwebp ${opts[*]}" fi dwebp "${opts[@]}" } # Detect output format from filename detect_format() { local output="$1" local ext ext=$(get_extension "$output") case "$ext" in png) echo "png" ;; jpg | jpeg) echo "jpg" ;; webp) echo "webp" ;; gif) echo "gif" ;; bmp) echo "bmp" ;; tiff | tif) echo "tiff" ;; avif) echo "avif" ;; heic | heif) echo "heic" ;; *) echo "" ;; esac } # Convert a single file convert_file() { local input="$1" local output="$2" if [[ ! -f "$input" ]]; then echo "Error: Input file not found: $input" >&2 return 1 fi local input_ext input_ext=$(get_extension "$input") # Auto-detect output format if not specified if [[ -z "$FORMAT" && -n "$output" ]]; then FORMAT=$(detect_format "$output") fi if [[ -z "$FORMAT" ]]; then echo "Error: Could not detect output format from: $output" >&2 return 1 fi # Determine output filename if [[ -z "$output" ]]; then output="$(get_basename "$input").$FORMAT" fi # Create output directory if needed local out_dir out_dir=$(dirname "$output") if [[ "$out_dir" != "." && ! -d "$out_dir" ]]; then mkdir -p "$out_dir" fi # Use specialized tools when available if [[ "$input_ext" == "webp" && "$FORMAT" != "webp" ]]; then local deps deps=$(check_deps) if [[ "$deps" == *"|true|true"* ]] && [[ "$FORMAT" =~ ^(png|ppm|pam|pgm)$ ]]; then convert_from_webp "$input" "$output" "$RESIZE" return fi fi if [[ "$FORMAT" == "webp" && "$input_ext" != "webp" ]]; then local deps deps=$(check_deps) if [[ "$deps" == *"|true|"* ]]; then convert_to_webp "$input" "$output" "$QUALITY" "$RESIZE" return fi fi # Fallback to ImageMagick local deps deps=$(check_deps) if [[ "$deps" == *"true|"* ]]; then convert_with_im "$input" "$output" "$FORMAT" "$QUALITY" "$RESIZE" else echo "Error: ImageMagick not found. Please install ImageMagick." >&2 return 1 fi } # Main main() { local inputs=() local output="" # Parse arguments while [[ $# -gt 0 ]]; do case "$1" in -f | --format) FORMAT="$2" shift 2 ;; -q | --quality) QUALITY="$2" shift 2 ;; -r | --resize) RESIZE="$2" shift 2 ;; -v | --verbose) VERBOSE=true shift ;; -h | --help) usage ;; -*) echo "Unknown option: $1" >&2 exit 1 ;; *) inputs+=("$1") shift ;; esac done if [[ ${#inputs[@]} -eq 0 ]]; then echo "Error: No input files specified" >&2 usage fi # Determine if last argument is an output file (not batch mode) # Output file detection: single input + different extension OR explicit format mismatch if [[ ${#inputs[@]} -eq 2 ]]; then local in_ext out_ext in_ext=$(get_extension "${inputs[0]}") out_ext=$(get_extension "${inputs[1]}") # If extensions differ, treat second as output if [[ "$in_ext" != "$out_ext" ]]; then output="${inputs[1]}" inputs=("${inputs[0]}") # If explicit format specified and matches second file's extension, treat as output elif [[ -n "$FORMAT" ]] && [[ "$FORMAT" == "$out_ext" || "$FORMAT" == "jpg" && "$out_ext" == "jpeg" || "$FORMAT" == "jpeg" && "$out_ext" == "jpg" ]]; then output="${inputs[1]}" inputs=("${inputs[0]}") fi fi # Batch conversion mode if [[ ${#inputs[@]} -gt 1 ]]; then if [[ -z "$FORMAT" ]]; then echo "Error: Format required for batch conversion. Use -f " >&2 exit 1 fi for input in "${inputs[@]}"; do local out out="$(get_basename "$input").$FORMAT" if [[ "$VERBOSE" == true ]]; then echo "Converting: $input -> $out" fi convert_file "$input" "$out" done return fi # Single file conversion convert_file "${inputs[0]}" "$output" } main "$@"