...

четверг, 8 августа 2013 г.

[Перевод] Оптимизация изображений 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'е.

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends: 'You Say What You Like, Because They Like What You Say' - http://www.medialens.org/index.php/alerts/alert-archive/alerts-2013/731-you-say-what-you-like-because-they-like-what-you-say.html


Комментариев нет:

Отправить комментарий