17 KiB
tags | date | zero-link | parents | linked | |||
---|---|---|---|---|---|---|---|
|
2023-11-20 |
|
|
Данный скрипт предназначен для автоматического сжатия изображений форматов PNG и JPEG в заданной директории. Он использует современные утилиты сжатия, обеспечивая уменьшение размера файлов без заметной потери качества. Это особенно полезно для оптимизации веб-сайтов, ускорения загрузки страниц и экономии дискового пространства.
Note
Лучшее, на мой взгляд, приложение для сжатия jpg это JPEGmini Pro. Имеет версию cli для серверов. Но к сожалению оно платное. Его результат сжатия 2.2 мб —> 959 кб.
Установка необходимых утилит
Перед запуском скрипта необходимо установить следующие утилиты:
- pngquant — инструмент для сжатия PNG-изображений.
- zopflipng — утилита для дополнительной оптимизации PNG-файлов от Google.
- mozjpeg — оптимизированный JPEG-кодек от Mozilla для сжатия JPEG-изображений.
# Для Debian/Ubuntu
sudo apt-get install pngquant
sudo apt-get install zopfli
sudo apt-get install mozjpeg
# Для macOS с использованием Homebrew
brew install pngquant
brew install zopfli
brew install mozjpeg
#!/bin/bash
# Настройки
IMAGE_DIR="./images"
COMP_DIR="$IMAGE_DIR/comp"
# Автоматическое определение количества ядер процессора
THREADS=$(getconf _NPROCESSORS_ONLN)
echo "Используется $THREADS потоков для обработки."
# Файлы логирования
LOG_FILE="./zip_image_compression.log"
ERROR_LOG_FILE="./zip_image_error.log"
# Экспортируем необходимые переменные и функции для использования в subshell
export IMAGE_DIR COMP_DIR LOG_FILE ERROR_LOG_FILE
# Функция для определения размера файла
get_file_size() {
local file="$1"
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
stat -c%s "$file"
elif [[ "$OSTYPE" == "darwin"* ]]; then
stat -f%z "$file"
else
# Попытка использовать GNU stat, если установлен
if command -v gstat &> /dev/null; then
gstat -c%s "$file"
else
# В качестве альтернативы используем wc -c
wc -c < "$file" | tr -d ' '
fi
fi}
export -f get_file_size
# Функция для логирования успеха
log_success() {
local message="$1"
echo "$message"
echo "$(date '+%Y-%m-%d %H:%M:%S') $message" >> "$LOG_FILE"
}
# Функция для логирования ошибок
log_error() {
local message="$1"
echo "$message" >&2
echo "$(date '+%Y-%m-%d %H:%M:%S') $message" >> "$ERROR_LOG_FILE"
}
# Экспортируем функции логирования
export -f log_success
export -f log_error
# Функция для обработки PNG файлов
process_png() {
local input_file="$1"
local relative_path="${input_file#$IMAGE_DIR/}"
local output_file="$COMP_DIR/$relative_path"
local output_dir
output_dir="$(dirname "$output_file")"
mkdir -p "$output_dir"
# Проверка файла ошибки
local error_file="${output_file}.error"
if [ -f "$error_file" ]; then
# Файл ошибки существует, пропускаем обработку
return
fi
# Проверка хеша файла
local hash_file="${output_file}.md5"
local current_hash
current_hash="$(md5sum "$input_file" | awk '{print $1}')"
if [ -f "$hash_file" ]; then
local previous_hash
previous_hash="$(cat "$hash_file")"
if [ "$current_hash" == "$previous_hash" ]; then
# Файл не изменился, ничего не делаем
return
fi
fi
# Используем временный файл для обработки
local temp_output_file="${output_file}.tmp"
cp "$input_file" "$temp_output_file"
# Размер до сжатия
local original_size
original_size=$(get_file_size "$input_file")
# Используем pngquant
if ! pngquant --quality=90-100 --speed 1 --output "$temp_output_file" --force "$input_file"; then
local error_msg="Ошибка при сжатии $input_file с помощью pngquant" log_error "$error_msg"
echo "$error_msg" > "$error_file"
rm -f "$temp_output_file"
return 1
fi
# Дополнительная оптимизация с помощью zopflipng
if ! zopflipng -y "$temp_output_file" "$temp_output_file"; then
local error_msg="Ошибка при оптимизации $temp_output_file с помощью zopflipng" log_error "$error_msg"
echo "$error_msg" > "$error_file"
rm -f "$temp_output_file"
return 1
fi
# Размер после сжатия
local new_size
new_size=$(get_file_size "$temp_output_file")
# Проверка, что original_size не равен нулю
if [ "$original_size" -eq 0 ]; then
local error_msg="Ошибка: размер оригинального файла равен 0 для $input_file"
log_error "$error_msg"
echo "$error_msg" > "$error_file"
rm -f "$temp_output_file"
return 1
fi
# Проверка, уменьшился ли размер файла
if [ "$new_size" -ge "$original_size" ]; then
log_success "Сжатие не уменьшило размер файла $input_file, пропускаем сохранение"
# Сохраняем хеш, чтобы не обрабатывать файл снова
echo "$current_hash" > "$hash_file"
rm -f "$temp_output_file"
return
fi
# Процент сжатия
local reduction
reduction=$(awk "BEGIN {printf \"%.2f\", (($original_size - $new_size) / $original_size) * 100}")
log_success "Сжат PNG файл: $input_file на $reduction% ($original_size байт -> $new_size байт)"
# Перемещаем временный файл на место выходного
mv "$temp_output_file" "$output_file"
# Сохраняем хеш
echo "$current_hash" > "$hash_file"
}
export -f process_png
# Функция для обработки JPEG файлов
process_jpeg() {
local input_file="$1"
local relative_path="${input_file#$IMAGE_DIR/}"
local output_file="$COMP_DIR/$relative_path"
local output_dir
output_dir="$(dirname "$output_file")"
mkdir -p "$output_dir"
# Проверка файла ошибки
local error_file="${output_file}.error"
if [ -f "$error_file" ]; then
# Файл ошибки существует, пропускаем обработку
return
fi
# Проверка хеша файла
local hash_file="${output_file}.md5"
local current_hash
current_hash="$(md5sum "$input_file" | awk '{print $1}')"
if [ -f "$hash_file" ]; then
local previous_hash
previous_hash="$(cat "$hash_file")"
if [ "$current_hash" == "$previous_hash" ]; then
# Файл не изменился, ничего не делаем
return
fi
fi
# Используем временный файл для обработки
local temp_output_file="${output_file}.tmp"
cp "$input_file" "$temp_output_file"
# Размер до сжатия
local original_size
original_size=$(get_file_size "$input_file")
# Используем mozjpeg
if ! cjpeg -quality 95 -progressive -optimize -outfile "$temp_output_file" "$input_file"; then
local error_msg="Ошибка при сжатии $input_file с помощью mozjpeg" log_error "$error_msg"
echo "$error_msg" > "$error_file"
rm -f "$temp_output_file"
return 1
fi
# Размер после сжатия
local new_size
new_size=$(get_file_size "$temp_output_file")
# Проверка, что original_size не равен нулю
if [ "$original_size" -eq 0 ]; then
local error_msg="Ошибка: размер оригинального файла равен 0 для $input_file"
log_error "$error_msg"
echo "$error_msg" > "$error_file"
rm -f "$temp_output_file"
return 1
fi
# Проверка, уменьшился ли размер файла
if [ "$new_size" -ge "$original_size" ]; then
log_success "Сжатие не уменьшило размер файла $input_file, пропускаем сохранение"
# Сохраняем хеш, чтобы не обрабатывать файл снова
echo "$current_hash" > "$hash_file"
rm -f "$temp_output_file"
return
fi
# Процент сжатия
local reduction
reduction=$(awk "BEGIN {printf \"%.2f\", (($original_size - $new_size) / $original_size) * 100}")
log_success "Сжат JPEG файл: $input_file на $reduction% ($original_size байт -> $new_size байт)"
# Перемещаем временный файл на место выходного
mv "$temp_output_file" "$output_file"
# Сохраняем хеш
echo "$current_hash" > "$hash_file"
}
export -f process_jpeg
# Обработка PNG файлов
find "$IMAGE_DIR" -type f \
-not -path "$COMP_DIR/*" \
! -name "*-no-comp.*" \
-iname "*.png" -print0 | \
xargs -0 -P "$THREADS" -I {} bash -c 'process_png "$@"' _ {}
# Обработка JPEG файлов
find "$IMAGE_DIR" -type f \
-not -path "$COMP_DIR/*" \
! -name "*-no-comp.*" \
-iregex '.*\.\(jpg\|jpeg\)' -print0 | \
xargs -0 -P "$THREADS" -I {} bash -c 'process_jpeg "$@"' _ {}
Описание работы скрипта
Основные функции скрипта
- Автоматическое сжатие изображений: скрипт обрабатывает все PNG и JPEG файлы в заданной директории, за исключением тех, которые помечены для исключения.
- Параллельная обработка: использует все доступные ядра процессора для ускорения процесса сжатия.
- Избежание повторной обработки: проверяет хеши файлов, чтобы не сжимать уже обработанные или не изменившиеся файлы.
- Логирование: записывает информацию об успешных операциях и ошибках в лог-файлы.
Детали работы
- Настройка окружения:
- Директории:
IMAGE_DIR
: основная директория с изображениями (./images
).COMP_DIR
: директория для сохранения сжатых изображений (./images/comp
).
- Директории:
- Потоки обработки: Скрипт автоматически определяет количество доступных процессорных ядер и использует их для параллельной обработки.
- Логирование:
- Файлы логов:
zip_image_compression.log
: журнал успешных операций.zip_image_error.log
: журнал ошибок.
- Функции логирования:
log_success
: записывает успешные операции.log_error
: записывает ошибки.
- Файлы логов:
- Обработка изображений:
- Проверка изменений:
- Для каждого файла вычисляется MD5-хеш.
- Если файл не изменился с момента последней обработки, он пропускается.
- Если при предыдущей попытке была ошибка (существует файл с расширением
.error
), файл пропускается.
- Сжатие PNG (
process_png
):- Использует
pngquant
для сжатия с качеством 90-100 и максимальной степенью сжатия. - Далее оптимизирует изображение с помощью
zopflipng
. - Сравнивает размер исходного и сжатого файлов; если размер не уменьшился, сжатый файл не сохраняется.
- Использует
- Сжатие JPEG (
process_jpeg
):- Использует mozjpeg (
cjpeg
) для сжатия с качеством 95 и прогрессивной разверткой. - Аналогично проверяет эффективность сжатия перед сохранением.
- Использует mozjpeg (
- Проверка изменений:
- Параллельная обработка:
- Использует команду
find
для поиска всех изображений требуемых форматов. - Пропускает файлы, содержащие
-no-comp
в названии, а также файлы в директорииcomp
. - Команда
xargs
с параметром-P
запускает обработку в несколько потоков.
- Использует команду
Как использовать скрипт
- Подготовка:
- Поместите все изображения, которые необходимо сжать, в директорию
./images
. - Убедитесь, что установлены все необходимые утилиты.
- Поместите все изображения, которые необходимо сжать, в директорию
- Запуск скрипта:
- Сделайте скрипт исполняемым:
chmod +x your_script_name.sh
- Сделайте скрипт исполняемым:
- Запустите скрипт:
./your_script_name.sh
- Результаты обработки:
- Сжатые изображения будут сохранены в директории
./images/comp
, сохраняя структуру исходной директории. - Логи успешных операций и ошибок можно просмотреть в файлах
zip_image_compression.log
иzip_image_error.log
соответственно.
- Сжатые изображения будут сохранены в директории
Пример результатов сжатия
Nginx
Теперь мы научим nginx при запросе изображений сначала пытаться отдать сжатое изображение, и если его не будет, то оригинал:
location ~* ^(/blog/ru/content/images/)(.+)\.(png|jpe?g)$ {
expires 1d;
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
root /var/site/images;
try_files /comp/$2.$3 $uri =404;
}
Мета информация
Область:: ../../meta/zero/00 Снипеты на bash Родитель:: ../fundamental/Сжатие данных Источник:: Автор:: Создана:: 2023-11-20