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

от автора

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

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

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

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

Для каждого 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’е.

ссылка на оригинал статьи http://habrahabr.ru/post/154683/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *