1#!/bin/bash 2# 3# webp-convert.sh 4# 5# Search through HTML and PHP files in a directory, get a list of the image 6# files used (jpg/png), and convert these to webp in the same directory as 7# the originals. Alternatively, convert all jpg/png files in a directory to 8# webp. Can be used with a rewrite rule on the web server to serve the webp 9# file when the original is requested. 10# 11# Andrew Lorimer, January 2021 12# 13 14usagelong="\e[1mUSAGE:\e[0m$(basename "$0")[OPTIONS] [DIRECTORY] 15 16\e[1mOPTIONS:\e[0m 17 -a|--all 18 Convert all image files in DIRECTORY to webp. This is the default mode. 19 This is mutually exclusive with -s|--scan. 20 21 -s|--scan 22 Scan all files with .php or .html extension in DIRECTORY for <img> tags 23 and convert source images to webp. This is mutually exclusive with 24 -a|--all. 25 26 -o|--overwrite 27 Convert all files even if the output filename exists (the default is to 28 ignore files for which a webp already exists). When calculating space 29 savings of files which overwrite existing ones, the saving is relative to 30 the source file, not the file that is overwritten. 31 32 -q|--quiet 33 Do not print any output (apart from errors). Mutually exclusive with 34 -v|--verbose. 35 36 -v|--verbose 37 Print extra output for each file. Mutually exclusive with -q|--quiet. 38 39 -d|--dry 40 Dry run - search for files and attempt to convert them but do not make any 41 permanent changes. Used for checking possible space savings. 42 43 -h|--help 44 Print this help message and exit. 45 46\e[1mDIRECTORY:\e[0m 47 Directory in which to search for PHP or HTML files (in -s|--scan mode) or 48 image files (in -a|--all mode). 49 50Converts JPG and PNG files to WebP using ImageMagick's convert(1). Optionally 51searches for source file in a directory or searches through HTML/PHP files in 52a directory to find references to source files.\n" 53 54usageshort="\e[1mUSAGE:\e[0m 55$(basename "$0")[MODE] [OPTIONS] [DIRECTORY] 56 57For more information, see \e[34m$(basename "$0")--help\e[0m\n" 58 59modehint="\x1b[31m-s|--scan and -a|--all cannot be specified simultaneously\x1b[0m\n\n$usageshort" 60verbosityhint="\x1b[31m-q|--quiet and -v|--verbose cannot be specified simultaneously\x1b[0m\n\n$usageshort" 61dryhint="\x1b[33mDry run mode\x1b[0m\n" 62overwritehint="\x1b[33mOverwriting existing files\x1b[0m\n" 63invalidhint="\x1b[31mInvalid argument$1\x1b[0m\n\n$usageshort" 64 65if(($#>5));then 66printf"$usageshort" 67exit1 68fi 69 70mode=-1# -1: initial, 0: all (default), 1: scan 71overwrite=0 72dry=0 73verbosity=-1# -1: initial, 0: quiet, 1: normal, 2: verbose 74directory="" 75 76while[$#-gt0];do 77case"$1"in 78-a|--all) 79if[[$mode==1]];then 80printf"$modehint" 81exit1 82fi 83 mode=0 84shift 85;; 86-s|--scan) 87if[[$mode==0]];then 88printf"$modehint" 89exit1 90fi 91 mode=1 92shift 93;; 94-o|--overwrite) 95 overwrite=1 96shift 97;; 98-q|--quiet) 99if[[$verbosity==2]];then 100printf"$verbosityhint" 101exit1 102fi 103 verbosity=0 104shift 105;; 106-v|--verbose) 107if[[$verbosity==0]];then 108printf"$verbosityhint" 109exit1 110fi 111 verbosity=2 112shift 113;; 114-d|--dry) 115 dry=1 116shift 117;; 118-h|--help) 119printf"$usagelong" 120exit 121;; 122*) 123if[["$directory"=""&&"$1"!= -* ]];then 124 directory=$1 125shift 126else 127printf"$invalidhint" 128exit1 129fi 130;; 131esac 132done 133 134# Set mode to 0 (all) if not specified 135if[[$mode==-1]];then 136 mode=0 137fi 138 139# Set directory to . if not specified 140if[["$directory"=""]];then 141 directory="." 142fi 143 144# Set verbosity to 1 if not specified 145if[[$verbosity==-1]];then 146 verbosity=1 147fi 148 149# Indicate that we are running in dry mode 150if[[$verbosity>0&&$dry==1]];then 151printf"$dryhint" 152fi 153 154# Indicate that we will overwrite 155if[[$verbosity>0&&$overwrite==1]];then 156printf"$overwritehint" 157fi 158 159savings=() 160savings_total=0 161found_count=0 162convert_count=0 163larger_count=0 164failed_count=0 165 166convert_webp () { 167# Perform the conversion. Takes input filename, output filename. 168 169# Set destination (user-supplied is quoted to handle spaces properly) 170if[[$dry==1]];then 171 suffix="/tmp/webp-convert.webp" 172else 173 suffix="$2" 174fi 175 176if[["$1"== *.png ]];then 177# PNG conversion 178 convert "$1"-quality75-define webp:lossless=true $suffix 179 convert_status=$? 180else 181# JPG conversion 182 convert "$1"-quality75$suffix 183 convert_status=$? 184fi 185 186# Check status code & print feedback 187if[[$convert_status==0]];then 188# Successful 189((convert_count+=1)) 190if[[$verbosity>1]];then 191echo"Converted$1to$2" 192fi 193else 194# Failed 195((failed_count+=1)) 196if[[$verbosity>0]];then 197printf"\x1b[31mFailed to convert$1to$2\x1b[0m\n" 198fi 199return1# Return because we don't need to check file size 200fi 201 202# Check if webp is actually smaller than original and remove if not 203 orig_size=$(stat -c%s "$1") 204if[[$dry]];then 205 webp_size=$(stat -c%s $suffix) 206else 207 webp_size=$(stat -c%s "$2") 208fi 209if(( webp_size > orig_size ));then 210if[[ !$dry]];then 211rm$2 212fi 213if[[$verbosity>0]];then 214printf"\x1b[33mRemoved$2as it was larger than$1($webp_size>$orig_size)\x1b[0m\n" 215fi 216((larger_count+=1)) 217return1 218fi 219 220# Calculate file size saving 221 saving=$((orig_size - webp_size)) 222 savings+=($saving) 223((savings_total+=$saving)) 224 225return0 226} 227 228if[[$mode==0]];then 229# Convert all images in directory 230if[[$verbosity>1]];then 231printf"Converting image files in$directory\n" 232fi 233for file_path in$(find $directory -type f -and \( -iname '*.jpg' -o -iname '*.jpeg' \));do 234((found_count+=1)) 235 webp_path=$(sed 's/.jpe\?g$/.webp/' <<< "$file_path") 236if[[ !-f"$webp_path"||$overwrite==1]];then 237 convert_webp $file_path $webp_path 238fi 239done 240else 241# Scan php and html files for images to convert 242whileread -r srcfile;do 243if[[$verbosity>1]];then 244printf"Scanning for image files referenced in$srcfile\n" 245fi 246while IFS=read -rfile;do 247((found_count+=1)) 248 file_path=$(echo $file | sed "s|^.|$directory/|") 249 webp_path=$(sed 's/\.[^.]*$/.webp/' <<< "$file_path") 250 251# Convert if not already converted 252if[[ !-f"$webp_path"||$overwrite==1]];then 253 convert_webp $file_path $webp_path 254fi 255done< <(sed-n"s:.*<img src=\"\([^\"]*\.\(jpe\?g\|png\)\)\".*:\1:p"$srcfile) 256done<<<"$(find $directory -type f -and \( -iname "*.php" -o -iname "*.html" \))" 257fi 258 259# Calculate statistics 260net=$((convert_count-larger_count)) 261existing=$((found_count-convert_count)) 262if[[$net>0]];then 263 savings_avg=$((savings_total/net)) 264else 265 savings_avg=0 266fi 267 268# Convert sizes to human-readable units if numfmt available 269ifcommand -v numfmt &> /dev/null;then 270 savings_total=$(numfmt --to iec $savings_total) 271 savings_avg=$(numfmt --to iec $savings_avg) 272else 273 savings_total="$savings_totalB" 274 savings_avg="$savings_avgB" 275fi 276 277# Print statistics 278if[[$verbosity>0]];then 279printf"\x1b[32m$found_countfound\n" 280if[[$failed_count>0]];then 281printf"\x1b[31m$failed_countfailed\n" 282fi 283printf"\x1b[32m$convert_countconverted 284$netsmaller than original 285$savings_totalsaved 286$savings_avgsaved average per file\n\x1b[0m" 287fi 288 289# Set exit code 290if[[$failed_count>0]];then 291exit1 292else 293exit0 294fi