Skip to content

Instantly share code, notes, and snippets.

@caubry
Last active December 13, 2023 14:50
Show Gist options
  • Save caubry/7103438 to your computer and use it in GitHub Desktop.
Save caubry/7103438 to your computer and use it in GitHub Desktop.
Bash script using pngquant and jpegoptim library to compress PNG, JPG and JPEG, with batch compression as an option.
#!bin/bash
pwd=`pwd`
# Set the quality factor to 80
qualityCompression=80;
# Set a minimum quality factor to 20
qualityLimit=20;
# To run a new compression
second_run_compression=false;
# For batch compression
batch=false;
# Colours for the script
yellow=$(tput setaf 2);
red=$(tput setaf 1);
# Reset colours
textreset=$(tput sgr0);
# Return line, with ">"
cr=`echo -e '\n> '`;
# Ask path to user
echo -n "Please enter the file path: $cr";
# Save path entered by user
read filePath;
# Check if path exists
if [[ ! -f "$filePath" ]]
then
# Exit script if path doesn't exist
echo "File "$filePath" not found!";
exit;
fi
# Grab file directory
currentDir="${filePath%/*}/";
# Grab file path and name, without the extension
originalName="${filePath%.*}";
# Extension to be added after the compression
customExt="";
# Supported image extensions (with or without capitalisation)
pngExt="png";
jpgExt="jpg";
jpegExt="jpeg";
# Create a batch directory, where compressed images can be saved to
function create_batch_directory()
{
# Grab the batch compression folder
batchFolder="${currentDir}batch_compression/";
# Check if the folder exists
if [[ ! -d "$batchFolder" ]]
then
# Create folder if it doesn't exist
mkdir "$batchFolder";
else
# Remove old batch compression folder and create a new one
# if the folder exists
rm -rf "$batchFolder";
mkdir "$batchFolder";
fi
}
# Remove temporary folder
function remove_temp_folder()
{
# Check if temporary folder exists
if [[ -d "$tempDir" ]]
then
# Remove the directory
find -f "temp" \( ! -iname "*.DS_Store" \) | xargs rm -rf
fi
}
# Remove the directory
function make_image_copy()
{
# Store temp directory
tempDir="${pwd}/temp/";
# Check if temporary folder doesn't exists
if [[ ! -d "$tempDir" ]]
then
# Create directory
mkdir "$tempDir";
fi
# If user has chosen to run a batch
if $batch
then
name="${files##*/}";
name="${name%.*}";
if [[ "$imageType" == *$pngExt* ]]
then
cp "$files" "${tempDir}${name}";
tempFilePath="${tempDir}${name}";
else
cp "$files" "${tempDir}${name}${fileExtension}";
tempFilePath="${tempDir}${name}${fileExtension}";
fi
else
cp "$filePath" "${tempDir}_temp";
tempFilePath="${tempDir}_temp";
fi
}
function run_jpegoptim_quality()
{
if $batch
then
make_image_copy;
jpegoptim -v --max=$qualityCompression "$tempFilePath";
mv "$tempFilePath" "$batchFolder";
imageOutput="${batchFolder}${name}${fileExtension}";
fileOutput=$(ls -l "$imageOutput" | awk '{print $5}');
else
if $overwrite
then
currentFile=$filePath;
extension=".$imageType";
else
make_image_copy;
currentFile=$tempFilePath;
extension=$fileExtension;
fi
jpegoptim -v --max=$qualityCompression "$currentFile";
mv "$currentFile" "${originalName}$extension";
fileOutput=$(ls -l "${originalName}$extension" | awk '{print $5}');
fi
if [[ "$fileOutput" -ge $originalLsSize ]]
then
echo -e "\n${red}******************************************************************************** \n********************** No compression has been performed! **********************\n******************************************************************************** \nThe size of the new file is equal or greater than the size of the original file. \n You should decrease the image quality factor to decrease the size of the file. \n********************************************************************************${textreset} \n";
remove_temp_folder;
# rm "$imageOutput";
if ! $batch
then
qualityCompression=$((qualityCompression-1));
if [[ "$qualityCompression" -gt 0 ]]
then
adjustImageQuality="Do you wish to adjust the image quality factor below $qualityCompression? (Y/N): "
elif [[ "$qualityCompression" -eq 0 ]]
then
adjustImageQuality="Do you wish to adjust the image quality factor at $qualityCompression? (Y/N): "
else
exit;
fi
while true; do
read -p "$adjustImageQuality" yn
case $yn in
[Yy]* ) run_jpegoptim_command; break;;
[Nn]* ) exit;;
* ) echo "Please answer Yes or No.";;
esac
done
else
rm "$imageOutput";
fi
else
if $batch
then
outputSize=$(du -h "$imageOutput" | awk '{print $1}');
echo -e "${yellow}\n\xF0\x9f\x8d\xba Compression done! \nOutput: $outputSize, $imageOutput${textreset} \n";
remove_temp_folder;
else
outputSize=$(du -h "$filePath" | awk '{print $1}');
outputSize=$(du -h "${originalName}$extension" | awk '{print $1}');
echo -e "${yellow}\n\xF0\x9f\x8d\xba Compression done! \nOutput: $outputSize, "${originalName}$extension"${textreset} \n";
remove_temp_folder;
exit;
fi
fi
}
function run_jpegoptim_command()
{
if $batch
then
run_jpegoptim_quality;
else
if ! $second_run_compression
then
while true; do
read -p "`echo $'\n> '`Do you wish to overwrite your image (Y/N)? " yn
case $yn in
[Yy]* ) second_run_compression=true; overwrite=true; break;;
[Nn]* ) second_run_compression=true; overwrite=false; break;;
* ) echo "Please answer Yes or No.";;
esac
done
fi
if $once
then
if [[ "$qualityCompression" -gt -1 ]]
then
while true; do
read -p "`echo -e '\n**********************************************************************************'` `echo -e '\n************** Please enter the maximum image quality factor [0-'$qualityCompression'] **************'``echo -e '\n** Note that if you do not enter a value, the default value of' $qualityCompression 'will be used. **'``echo -e '\n**********************************************************************************'` $cr" userQualityCompression;
case $userQualityCompression in
[0-$qualityCompression]*) qualityCompression=$userQualityCompression ; run_jpegoptim_quality;;
* ) run_jpegoptim_quality;;
esac
done
fi
else
# jpegoptim -v --max=$qualityCompression "$filePath";
outputSize=$(ls -l "$filePath" | awk '{print $5}');
run_jpegoptim_twenty_kb;
fi
fi
}
function run_jpegoptim()
{
command -v jpegoptim >/dev/null 2>&1 ||
{
echo -e >&2 "Jpegoptim hasn't been installed.";
while true; do
read -p "`echo $'\n> '`Do you wish to install this program (Y/N)? " yn
case $yn in
[Yy]* ) install_libjpeg;;
[Nn]* ) echo "End"; exit;;
* ) echo "Please answer Yes or No.";;
esac
done
exit;
}
twenty_kb=20;
if $batch
then
run_jpegoptim_command;
else
if [[ $originalLsSize -ge $twenty_kb ]]
then
while true; do
read -p "`echo $'\n> '`Do you need this image to be under a certain size (Y/N)? " yn;
case $yn in
[Yy]* ) once=false; break;;
[Nn]* ) once=true; run_jpegoptim_command;;
* ) echo "Please answer Yes or No.";;
esac
done
if ! $once
then
while true; do
twenty_kb=20;
currentSize=$(ls -l "$filePath" | awk '{print $5}');
currentSize=$((currentSize/1000));
currentSize=$((currentSize-3));
read -p "`echo -e '\n**********************************************************************************'` `echo -e '\n************** Please enter the minimum image size in Kb [0-'$currentSize'] **************'``echo -e '\n** Note that if you do not enter a value, the default value of' $twenty_kb 'will be used. **'``echo -e '\n**********************************************************************************'` $cr" userSizeInput;
if (( 0 <= $userSizeInput && $userSizeInput <= $currentSize ))
then
twenty_kb=$((userSizeInput*1000));
run_jpegoptim_command;
else
twenty_kb=$((twenty_kb*1000));
run_jpegoptim_command;
fi
done
fi
else
once=true;
run_jpegoptim_command;
fi
fi
}
function pause()
{
read -p "$*";
}
function run_pngquant()
{
command -v pngquant >/dev/null 2>&1 ||
{
echo -e >&2 "Pngquant command line tool hasn't been installed.";
while true; do
read -p "Do you wish to install this program (Y/N)? " yn
case $yn in
[Yy]* ) libpng;;
[Nn]* ) echo "End"; exit;;
* ) echo "Please answer Yes or No.";;
esac
done
exit;
}
if $batch
then
make_image_copy;
fileOutput=$(ls -l "$tempFilePath" | awk '{print $5}');
pngquant -vf "$tempFilePath" --ext "$fileExtension";
batchNoExt="${tempFilePath%.*}";
currentImage="${batchNoExt}${fileExtension}";
mv "${currentImage}" "$batchFolder";
imageOutput="${batchFolder}${currentImage##*/}";
else
while true; do
read -p "`echo $'\n> '`Do you wish to overwrite your image (Y/N)? " yn
case $yn in
[Yy]* ) overwrite=true; break;;
[Nn]* ) overwrite=false; break;;
* ) echo "Please answer Yes or No.";;
esac
done
if $overwrite
then
make_image_copy;
pngquant -vf "$tempFilePath" --ext "$fileExtension";
imageOutput="${tempFilePath}$fileExtension";
else
pngquant -vf "$filePath" --ext "$fileExtension";
imageOutput="${originalName}$fileExtension";
fi
fi
output=$(du -h "$imageOutput");
outputSizeFile=$(ls -l "$imageOutput" | awk '{print $5}');
if [ $outputSizeFile -ge $originalLsSize ]
then
if $batch
then
echo -e "\n${red}******************************************************************************** \n********************** No compression has been performed! ********************** \n******************************************************************************** \nThe size of the new file is equal or greater than the size of the original file. \n********************************************************************************${textreset} \n";
rm "$imageOutput";
else
remove_temp_folder;
rm "$imageOutput";
echo -e "\n${red}******************************************************************************** \n********** The compression has already been performed on this file. ************ \n******************************************************************************** \n";
fi
else
if $overwrite
then
if $batch
then
remove_temp_folder;
else
cp "$imageOutput" "$filePath";
output=$(du -h "$filePath");
remove_temp_folder;
fi
fi
echo -e "${yellow}\n\xF0\x9f\x8d\xba Compression done! \nOutput: $output${textreset} \n";
fi
}
function run_batch()
{
create_batch_directory;
batch=true;
while true; do
read -p "`echo -e '\n**********************************************************************************'` `echo -e '\n************** Please enter the maximum image quality factor [0-'$qualityCompression'] **************'``echo -e '\n** Note that if you do not enter a value, the default value of' $qualityCompression 'will be used. **'``echo -e '\n**********************************************************************************'` $cr" userQualityCompression;
case $userQualityCompression in
[0-$qualityCompression]*) qualityCompression=$userQualityCompression; break;;
* ) echo "Please answer Yes or No.";;
esac
done
let totalOriginalFileOutput=0;
while read -r files;
do
imageType="${files##*.}";
filesNoExt="${files%.*}";
fileNameNoExt="${filesNoExt##*/}"
case ${imageType:(-1)} in
[A-Z]*) upperLetter=true;;
[a-z]*) upperLetter=false;;
esac
if $upperLetter
then
fileExtension="${customExt}".$imageType"";
lowerCaseLetters=$(echo $imageType | awk '{print tolower($0)}');
imageType="$lowerCaseLetters";
else
imageType="$imageType";
fileExtension="${customExt}".$imageType"";
fi
originalLsSize=$(ls -l "$files" | awk '{print $5}');
if [[ "$imageType" == *$pngExt* ]]
then
run_pngquant;
elif [[ "$imageType" == *$jpgExt* ]] || [[ "$imageType" == *$jpegExt* ]]
then
run_jpegoptim;
fi
done < <(find "$currentDir" -maxdepth 1 \( ! -regex '.*/\..*' \( -iregex '.*\(jpg\)' -o -iregex '.*\(png\)' -o -iregex '.*\(jpeg\)' \) \))
let totalNewFileOutput=0;
let totalOriginalFileOutput=0;
let totalSpaceSaved=0;
while read -r line;
do
newFiles+=("$line");
originalFiles="${line##*/}";
originalFiles="${originalFiles%$customExt*}";
while read -r originalLines;
do
originalFileOutput=$(ls -l "$originalLines" | awk '{print $5}');
originalFileOutput=$((originalFileOutput / 1000));
originalFileOutput=${originalFileOutput%.*};
let totalOriginalFileOutput=$totalOriginalFileOutput+$originalFileOutput;
done < <(find "$currentDir" -maxdepth 1 \( -iname "*$originalFiles*" \))
newFileOutput=$(du -h "$line" | awk '{print $1}');
newFileOutput=${newFileOutput%?};
newFileOutput=${newFileOutput%.*};
let totalNewFileOutput=$totalNewFileOutput+$newFileOutput;
done < <(find "$batchFolder" -maxdepth 1 \( ! -regex '.*/\..*' \( -iregex '.*\(jpg\)' -o -iregex '.*\(png\)' -o -iregex '.*\(jpeg\)' \) \))
let totalSpaceSaved=$totalOriginalFileOutput-$totalNewFileOutput;
echo -e "\n${yellow}********************** Congratulation! You have saved "$totalSpaceSaved"K! **********************${textreset} \n";
echo -e "The new images are: \n";
for i in "${newFiles[@]}"
do
newFiles=$i;
echo "$newFiles"
newFilesName="${newFiles##*/}";
newFilesName="${newFilesName%$customExt*}";
newFilesList+=("$newFilesName");
done
for y in "${newFilesList[@]}"
do
while read -r files;
do
matchingFiles+=("$files");
done < <(find "$currentDir" -maxdepth 1 \( -iname "*$y*" \))
done
for z in "${originalFileList[@]}"
do
for k in "${matchingFiles[@]}"
do
if [[ "$z" = "$k" ]]
then
for (( i = 0; i < ${#originalFileListCopy[@]}; i++ ))
do
if [ "${originalFileListCopy[$i]}" = "${z}" ]
then
unset "originalFileList[$i]";
fi
done
fi
done
done
if [ "${#originalFileList[@]}" -gt "0" ]
then
if [ "${#originalFileList[@]}" -eq "1" ]
then
imageString="image"
else
imageString="images"
fi
echo -e "\n******************************************************************************** \n\n${red}Error occurred with the following $imageString:${textreset} \n";
for f in "${originalFileList[@]}"
do
echo -e "${red}"$f"${textreset}";
done
echo -e "\n${red}Scroll up for more details.${textreset} \n";
fi
exit;
}
itemsCount=($(find "$currentDir" -maxdepth 1 \( ! -regex '.*/\..*' \( -iregex '.*\(jpg\)' -o -iregex '.*\(png\)' -o -iregex '.*\(jpeg\)' \) \) | grep -v ^l | wc -l));
if [ "$itemsCount" -ge "2" ]
then
echo -e "\n******************************************************************************** \n********************** This directory contains $itemsCount images! ***********************\n******************************************************************************** \n";
while read -r files;
do
echo "$files";
originalFileList+=("$files");
originalFileListCopy+=("$files");
done < <(find "$currentDir" -maxdepth 1 \( ! -regex '.*/\..*' \( -iregex '.*\(jpg\)' -o -iregex '.*\(png\)' -o -iregex '.*\(jpeg\)' \) \))
while true; do
read -p "`echo $'\n> '`Do you want to compress these $itemsCount images? (Y/N): " yn;
case $yn in
[Yy]* ) run_batch; break;;
[Nn]* ) break;;
* ) echo "Please answer Yes or No.";;
esac
done
fi
function libpng()
{
cd /usr/local/include/;
if [[ -f 'libpng15' ]]
then
install_pngquant;
else
brew install libpng
pause 'Press [Enter] key to install pngquant...';
install_pngquant;
fi
}
function install_pngquant()
{
cd ~/Documents/;
git clone git://github.com/pornel/pngquant.git;
if [[ -d 'pngquant' ]]
then
cd pngquant;
make -s;
make -s install;
cd ..;
rm -rf ~/Documents/pngquant;
command -v pngquant >/dev/null 2>&1 ||
{
echo "Something went horribly wrong and pngquant hasn't been installed! (╯°□°)╯︵ ┻━┻";
exit;
}
echo -e "********************************** \n******* Installation done! ******* \n********************************** \n";
pause 'Press [Enter] key to compress your image...';
run_pngquant;
else
echo "Couldn't clone pngquant git repo. \nDo you have an Internet access?";
exit;
fi
}
function install_libjpeg()
{
cd /usr/local/include/;
if [[ -f 'jpeglib.h' ]]
then
install_jpegoptim;
else
cd ~/Documents/;
curl -O http://www.ijg.org/files/jpegsrc.v8c.tar.gz
tar -xf jpegsrc.v8c.tar.gz
rm jpegsrc.v8c.tar.gz
if [[ -d 'jpeg-8c' ]]
then
cd jpeg-8c/;
./configure;
make;
make -s install;
cd ..;
rm -rf jpeg-8c/;
pause 'Press [Enter] key to install jpegoptim...';
install_jpegoptim;
else
echo -e "Something went horribly wrong and libjpeg hasn't been installed! (╯°□°)╯︵ ┻━┻ \nDo you have Internet access?";
exit;
fi
fi
}
function install_jpegoptim()
{
cd ~/Documents/;
git clone https://github.com/tjko/jpegoptim.git;
if [[ -d 'jpegoptim' ]]
then
cd jpegoptim;
./configure;
make -s;
make -s install;
cd ..;
rm -rf ~/Documents/jpegoptim;
command -v jpegoptim >/dev/null 2>&1 ||
{
echo "Something went horribly wrong and jpegoptim hasn't been installed! (╯°□°)╯︵ ┻━┻";
exit;
}
echo -e "********************************** \n******* Installation done! ******* \n********************************** \n";
pause 'Press [Enter] key to compress your image...';
run_jpegoptim;
else
echo -e "Couldn't get jpegoptim. \nDo you have Internet access?";
exit;
fi
}
function run_jpegoptim_twenty_kb()
{
make_image_copy;
copy_ext="_copy";
copyTempFilePath="${tempFilePath}$copy_ext";
cp "$tempFilePath" "$copyTempFilePath";
while [[ $twenty_kb -le $outputSize && $qualityCompression -gt $qualityLimit ]]
do
cp "$copyTempFilePath" "$tempFilePath";
qualityCompression=$((qualityCompression-1));
jpegoptim -v --max=$qualityCompression "$tempFilePath";
outputSize=$(ls -l "$tempFilePath" | awk '{print $5}');
done
if $overwrite
then
extension=".$imageType";
else
extension=$fileExtension;
fi
if [[ -d "$tempDir" ]]; then
rm "$copyTempFilePath";
mv "$tempFilePath" "${originalName}$extension";
fileOutput=$(ls -l "${originalName}$extension" | awk '{print $5}');
fi
if [[ "$fileOutput" -ge "$originalLsSize" ]]
then
echo -e "\n${red}******************************************************************************** \n********************** No compression has been performed! **********************\n******************************************************************************** \nThe size of the new file is equal or greater than the size of the original file. \n**** The minimum image quality factor of 20 might has already been reached. **** \n************* It is not adviced to go below this quality factor. *************** \n********************************************************************************${textreset} \n";
elif [ $twenty_kb -le $outputSize -a $qualityCompression -gt 0 ]
then
echo -e "\n${yellow}******************************************************************************** \n*********************** Compression has been performed! ************************ \n******************************************************************************** \nThe minimum quality factor has been set to 20 to avoid the image to be corrupted \n*********** Note that the size of the new file is over as 20Kb! ************ \n******************************************************************************** \n\n\xF0\x9f\x8d\xba Output: ${originalName}$extension, $outputSize${textreset} \n";
while true; do
userImageSize=$((twenty_kb/1000));
read -p "`echo $'\n> '`Do you still want your file to be below $userImageSize Kb? (Y/N): " yn;
case $yn in
[Yy]* ) qualityLimit=0; run_jpegoptim_twenty_kb; break;;
[Nn]* ) break;;
* ) echo "Please answer Yes or No.";;
esac
done
qualityCompression=19
while true; do
read -p "`echo $'\n> '`Do you wish to manually set the image quality factor below $qualityCompression? (Y/N): " yn;
case $yn in
[Yy]* ) once=true; run_jpegoptim_command; break;;
[Nn]* ) break;;
* ) echo "Please answer Yes or No.";;
esac
done
else
echo -e "${yellow}\n\xF0\x9f\x8d\xba Compression done! \n\nOutput: ${originalName}$extension, $outputSize${textreset} \n";
if [ $qualityCompression -eq 0 ]
then
echo -e "******************************************************************************** \n*************** The image quality factor of 0 has been reached. **************** \n****************** This size is the lowest one you can get. ******************** \n******************************************************************************** \n";
fi
fi
remove_temp_folder;
exit;
}
imageType="${filePath##*.}";
originalDuSizeFile=$(du -h "$filePath" | awk '{print $1}');
originalLsSize=$(ls -l "$filePath" | awk '{print $5}');
originalDuSize=${originalDuSizeFile%?};
case ${imageType:(-1)} in
[A-Z]*) upperLetter=true;;
[a-z]*) upperLetter=false;;
esac
if $upperLetter
then
fileExtension="${customExt}".$imageType"";
lowerCaseLetters=$(echo $imageType | awk '{print tolower($0)}');
imageType="$lowerCaseLetters";
else
imageType="$imageType";
fileExtension="${customExt}".$imageType"";
fi
if ! $batch
then
if [[ "$imageType" == *$pngExt* ]]
then
run_pngquant;
exit;
elif [[ "$imageType" == *$jpgExt* ]] || [[ "$imageType" == *$jpegExt* ]]
then
run_jpegoptim;
exit;
else
echo "Compression works with PNG, JPG and JPEG only.";
exit;
fi
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment