Pull to refresh

Оптимизация изображений bash-скриптом

Reading time 5 min
Views 23K
Original author: Hugo Giraudel
Скорость загрузки любого сайта во многом зависит от количества и качества используемых изображений. Поэтому очень важно уметь их оптимизировать. Существует множество веб сервисов для этого, но большинство из них обладает недостатками:

  • Нет возможности оптимизировать автоматически много файлов
  • Сложно и неудобно использовать в рабочем процессе

Но прежде всего следует отметить, что описанный ниже способ нельзя причислить к самым лучшим хотя бы потому, что в идеале каждое изображение следует оптимизировать индивидуально.

Оптимизация изображений с помощью командой строки


Для каждого png файла используются optipng и pngcrush, а для jpg — jpegtran. Для начала опробуем optipng:



Примечание: С параметр -o7 optipng работает в самом медленном режиме. Для быстрого используется -o0.

Затем pngcrush:



Оптимизация JPG с помощью jpegtran:



Написание скрипта


Готовый скрипт можно посмотреть на GitHub'е. Ниже подробно представлен процесс написания.

Прежде всего необходимо задать основные параметры:

  • -i или --input для исходной папки
  • -o или --output для папки с результатом
  • -q или --quiet для отключения вывода процесса выполнения
  • -s или --no-stats для отключения вывода статистики
  • -h или --help для вызова справки

Две переменные для коротких и полных имен параметров:

SHORTOPTS="h,i:,o:,q,s"
LONGOPTS="help,input:,output:,quiet,no-stats"

Используем getopt для передаваемых в скрипт параметров, цикл для вызова функция или определения переменных для хранения:

Код скрипта
ARGS=$(getopt -s bash --options $SHORTOPTS --longoptions $LONGOPTS --name $PROGNAME -- "$@")
 
eval set -- "$ARGS"
while true; do
	case $1 in
		-h|--help)
			usage
			exit 0
			;;
		-i|--input)
			shift
			INPUT=$1
			;;
		-o|--output)
			shift
			OUTPUT=$1
			;;
		-q|--quiet)
			QUIET='1'
			;;
		-s|--no-stats)
			NOSTATS='1'
			;;
		--)
			shift
			break
			;;
		*)
			shift
			break
			;;
	esac
	shift
done


HELP


Создаем две функции:

  • usage(), в цикле, для вызова справки
  • main() для оптимизации изображений

Они должны быть объявлены до цикла.
Код скрипта
PROGNAME=${0##*/}

usage()
{
  cat <<EO
Usage: $PROGNAME [options]
 
Script to optimize JPG and PNG images in a directory.
 
Options:
EO
cat <<EO | column -s\& -t
	-h, --help  	   & shows this help
	-q, --quiet 	   & disables output
	-i, --input [dir]  & specify input directory (current directory by default)
	-o, --output [dir] & specify output directory ("output" by default)
	-ns, --no-stats    & no stats at the end
EO
}

SHORTOPTS="h,i:,o:,q,s"
LONGOPTS="help,input:,output:,quiet,no-stats"
ARGS=$(getopt -s bash --options $SHORTOPTS --longoptions $LONGOPTS --name $PROGNAME -- "$@")

Проверим, что получилось.



Примечание: если возникают ошибки, вроде "./optimize.sh: line 2: $'\r': command not found", то необходимо открыть скрипт в Sublime Text 2 и включить Unix Mode в View > Line endings > Unix.

Главная функция (main)


Код скрипта
main()
{
	# If $INPUT is empty, then we use current directory
	if [[ "$INPUT" == "" ]]; then
		INPUT=$(pwd)
	fi
 
	# If $OUTPUT is empty, then we use the directory "output" in the current directory
	if [[ "$OUTPUT" == "" ]]; then
		OUTPUT=$(pwd)/output
	fi
 
	# We create the output directory
	mkdir -p $OUTPUT
 
	# To avoid some troubles with filename with spaces, we store the current IFS (Internal File Separator)...
	SAVEIFS=$IFS
	# ...and we set a new one
	IFS=$(echo -en "\n\b")
 
	max_filelength=`get_max_file_length`
	pad=$(printf '%0.1s' "."{1..600})
	sDone=' [ DONE ]'
	linelength=$(expr $max_filelength + ${#sDone} + 5)
 
	# Search of all jpg/jpeg/png in $INPUT
	# We remove images from $OUTPUT if $OUTPUT is a subdirectory of $INPUT
	IMAGES=$(find $INPUT -regextype posix-extended -regex '.*\.(jpg|jpeg|png)' | grep -v $OUTPUT)
 
	if [ "$QUIET" == "0" ]; then
		echo --- Optimizing $INPUT ---
		echo
	fi
	for CURRENT_IMAGE in $IMAGES; do
		filename=$(basename $CURRENT_IMAGE)
		if [ "$QUIET" == "0" ]; then
		    printf '%s ' "$filename"
		    printf '%*.*s' 0 $((linelength - ${#filename} - ${#sDone} )) "$pad"
		fi
 
		optimize_image $CURRENT_IMAGE $OUTPUT/$filename
 
		if [ "$QUIET" == "0" ]; then
		    printf '%s\n' "$sDone"
		fi
	done
 
	# we restore the saved IFS
	IFS=$SAVEIFS
 
	if [ "$NOSTATS" == "0" -a "$QUIET" == "0" ]; then
		echo
		echo "Input: " $(human_readable_filesize $max_input_size)
		echo "Output: " $(human_readable_filesize $max_output_size)
		space_saved=$(expr $max_input_size - $max_output_size)
		echo "Space save: " $(human_readable_filesize $space_saved)
	fi
}

Необходимо дать возможность задать директории, либо выполнять скрипт в текущей, используя команду mkdir. Далее необходимо заставить скрипт корректно работать с файлами, в названиях которых есть пробелы. Для этого используем IFS (Internal File Separator). Функция optimize_image, оптимизирующая изображения, имеет два параметра — для исходной и финальной директорий.

optimize_image:

# $1: input image
# $2: output image 
optimize_image()
{
	input_file_size=$(stat -c%s "$1")
	max_input_size=$(expr $max_input_size + $input_file_size)
 
	if [ "${1##*.}" = "png" ]; then
		optipng -o1 -clobber -quiet $1 -out $2
		pngcrush -q -rem alla -reduce $1 $2 >/dev/null
	fi
	if [ "${1##*.}" = "jpg" -o "${1##*.}" = "jpeg" ]; then
		jpegtran -copy none -progressive $1 > $2
	fi
 
	output_file_size=$(stat -c%s "$2")
	max_output_size=$(expr $max_output_size + $output_file_size)
}


Выходная информация


Результат выполнения скрипта должен наглядно отображаться, например так:

file1 ...................... [ DONE ]
file2 ...................... [ DONE ]
file_with_a_long_name ...... [ DONE ]
...

Сначала необходимо сделать следующие шаги:

  1. Определить длины названий файлов
  2. Заменить промежутки точками
  3. Задать максимальную длина названия и текста " [ DONE ]"

В итоге строки должны содержать название файла, точки и DONE и должны быть одинаковой длины.

Код скрипта
max_filelength=`get_max_file_length`
	pad=$(printf '%0.1s' "."{1..600})
	sDone=' [ DONE ]'
	linelength=$(expr $max_filelength + ${#sDone} + 5)
 
	# Search of all jpg/jpeg/png in $INPUT
	# We remove images from $OUTPUT if $OUTPUT is a subdirectory of $INPUT
	IMAGES=$(find $INPUT -regextype posix-extended -regex '.*\.(jpg|jpeg|png)' | grep -v $OUTPUT)
 
	if [ "$QUIET" == "0" ]; then
		echo --- Optimizing $INPUT ---
		echo
	fi
	for CURRENT_IMAGE in $IMAGES; do
		filename=$(basename $CURRENT_IMAGE)
		if [ "$QUIET" == "0" ]; then
		    printf '%s ' "$filename"
		    printf '%*.*s' 0 $((linelength - ${#filename} - ${#sDone} )) "$pad"
		fi
 
		optimize_image $CURRENT_IMAGE $OUTPUT/$filename
 
		if [ "$QUIET" == "0" ]; then
		    printf '%s\n' "$sDone"
		fi
	done

Проверим скрипт, запустив с параметрами:

# All parameters to default
./optimize.sh
# Or with custom options
./optimize.sh --input images --output optimized-images
# Or with custom options and shorthand
./optimize.sh -i images -o optimized-images



Статистика


Для отображения статистики работы скрипта используем input_file_size и output_file_size, которые возвращают исходный и конечный размер изображения. Для удобства чтения информации используем human_readable_filesize().

Запускаем скрипт еще раз и видим статистику:



Осталось только отображать процесс выполнения оптимизации:

if [ "$QUIET" == "0" ]; then
		echo --- Optimizing $INPUT ---
		echo
	fi
	for CURRENT_IMAGE in $IMAGES; do
		filename=$(basename $CURRENT_IMAGE)
		if [ "$QUIET" == "0" ]; then
		    printf '%s ' "$filename"
		    printf '%*.*s' 0 $((linelength - ${#filename} - ${#sDone} )) "$pad"
		fi
 
		optimize_image $CURRENT_IMAGE $OUTPUT/$filename
 
		if [ "$QUIET" == "0" ]; then
		    printf '%s\n' "$sDone"
		fi
	done

Все! В результате получился скрипт, который умеет автоматически оптимизировать изображения. Скачать на GitHub'е.
Tags:
Hubs:
+18
Comments 25
Comments Comments 25

Articles