Compare commits
2 Commits
ca09ce9d97
...
190ea59670
Author | SHA1 | Date | |
---|---|---|---|
190ea59670 | |||
2c40204d67 |
@ -1,26 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
find ./images/ -type f -not -path "./images/comp/*" ! -name "*-no-comp.*" $option -iname "*.png" -exec sh -c '
|
|
||||||
png_file="${1/\/images\//\/images\/comp\/}"
|
|
||||||
png_dir="$(dirname "$png_file")"
|
|
||||||
mkdir -p "$png_dir"
|
|
||||||
cp "$1" "${png_file}"
|
|
||||||
optipng -o7 "${png_file}"
|
|
||||||
advpng -z4 "${png_file}"
|
|
||||||
pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time -ow "${png_file}"
|
|
||||||
' _ {} \;
|
|
||||||
|
|
||||||
find ./images/ -type f-not -path "./images/comp/*" ! -name "*-no-comp.*" $option -iregex '.*\.\(jpg\|jpeg\)' -exec sh -c '
|
|
||||||
jpg_file="${1/\/images\//\/images\/comp\/}"
|
|
||||||
jpg_dir="$(dirname "$jpg_file")"
|
|
||||||
mkdir -p "$jpg_dir"
|
|
||||||
cp "$1" "${jpg_file}"
|
|
||||||
jpegoptim --all-progressive "${jpg_file}"
|
|
||||||
' _ {} \;
|
|
||||||
|
|
||||||
find ./images/comp -type f -iregex '.*\.\(jpg\|jpeg\|png\)' -not -iregex '.*no-comp\.\(jpg\|jpeg\|png\)' $option -exec sh -c '
|
|
||||||
webp_file="${1/\/images\/comp\//\/images\/webp\/}"
|
|
||||||
webp_dir="$(dirname "$webp_file")"
|
|
||||||
mkdir -p "$webp_dir"
|
|
||||||
cwebp -mt -af -progress -m 6 -q 75 -pass 10 "$1" -o "${webp_file%.*}.webp"
|
|
||||||
' _ {} \;
|
|
@ -30,20 +30,340 @@ cwebp -mt -af -progress -m 6 -q 80 -pass 10 input.jpg -o output.webp
|
|||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> `-lossless` позволяет использовать сжатие без потерь. Но тогда ваше новое изображение может оказаться существенно тяжелее исходного.
|
> `-lossless` позволяет использовать сжатие без потерь. Но тогда ваше новое изображение может оказаться существенно тяжелее исходного.
|
||||||
|
|
||||||
Улучшим [скрипт сжатия изображений](Сжатие%20изображений%20без%20потери%20качества.md) и добавим преобразование в webp:
|
Улучшим [скрипт сжатия изображений](Сжатие%20изображений%20без%20потери%20качества.md) и добавим также преобразование в webp:
|
||||||
|
|
||||||
```bash
|
```bash title="zip_image.sh" {6, 15}
|
||||||
find ./images/comp -type f -iregex '.*\.\(jpg\|jpeg\|png\)' -not -iregex '.*no-comp\.\(jpg\|jpeg\|png\)' $option -exec sh -c '
|
#!/bin/bash
|
||||||
webp_file="${1/\/images\/comp\//\/images\/webp\/}"
|
|
||||||
webp_dir="$(dirname "$webp_file")"
|
# Настройки
|
||||||
mkdir -p "$webp_dir"
|
IMAGE_DIR="./images"
|
||||||
cwebp -mt -af -progress -m 6 -q 75 -pass 10 "$1" -o "${webp_file%.*}.webp"
|
COMP_DIR="$IMAGE_DIR/comp"
|
||||||
' _ {} \;
|
WEBP_DIR="$IMAGE_DIR/webp"
|
||||||
|
# Автоматическое определение количества ядер процессора
|
||||||
|
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 WEBP_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
|
||||||
|
|
||||||
|
# Функция для конвертации в WebP
|
||||||
|
process_webp() {
|
||||||
|
local input_file="$1"
|
||||||
|
local relative_path="${input_file#$IMAGE_DIR/}"
|
||||||
|
local output_file="$WEBP_DIR/$relative_path"
|
||||||
|
output_file="${output_file%.*}.webp"
|
||||||
|
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 original_size
|
||||||
|
original_size=$(get_file_size "$input_file")
|
||||||
|
|
||||||
|
# Создаем временный файл для вывода
|
||||||
|
local temp_output_file="${output_file}.tmp"
|
||||||
|
|
||||||
|
if ! cwebp -mt -af -quiet -m 6 -q 95 -pass 10 "$input_file" -o "$temp_output_file"; then
|
||||||
|
local error_msg="Ошибка при конвертации $input_file в WebP" 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 "Конвертация в WebP не уменьшила размер файла $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 "Конвертирован в WebP: $input_file на $reduction% ($original_size байт -> $new_size байт)"
|
||||||
|
# Перемещаем временный файл на место выходного
|
||||||
|
mv "$temp_output_file" "$output_file"
|
||||||
|
|
||||||
|
# Сохраняем хеш
|
||||||
|
echo "$current_hash" > "$hash_file"
|
||||||
|
}
|
||||||
|
export -f process_webp
|
||||||
|
|
||||||
|
# Обработка PNG файлов
|
||||||
|
find "$IMAGE_DIR" -type f \
|
||||||
|
-not -path "$COMP_DIR/*" \
|
||||||
|
-not -path "$WEBP_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/*" \
|
||||||
|
-not -path "$WEBP_DIR/*" \
|
||||||
|
! -name "*-no-comp.*" \
|
||||||
|
-iregex '.*\.\(jpg\|jpeg\)' -print0 | \
|
||||||
|
xargs -0 -P "$THREADS" -I {} bash -c 'process_jpeg "$@"' _ {}
|
||||||
|
|
||||||
|
# Конвертация в WebP из исходных файлов
|
||||||
|
find "$IMAGE_DIR" -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' \) \
|
||||||
|
-not -path "$COMP_DIR/*" \
|
||||||
|
-not -path "$WEBP_DIR/*" \
|
||||||
|
! -name "*-no-comp.*" \
|
||||||
|
-print0 | \
|
||||||
|
xargs -0 -P "$THREADS" -I {} bash -c 'process_webp "$@"' _ {}
|
||||||
```
|
```
|
||||||
|
|
||||||
Мы берем сжатые изображения из папки `comp` и преобразуем их в WebP, складывая в отдельную папку `webp`. Если вы захотите использовать другие параметры сжатия, вы всегда сможете пересоздать изображения с новыми параметрами.
|
Мы берем сжатые изображения из папки `comp` и преобразуем их в WebP, складывая в отдельную папку `webp`. Если вы захотите использовать другие параметры сжатия, вы всегда сможете пересоздать изображения с новыми параметрами.
|
||||||
## Тесты преобразования
|
## Тесты преобразования
|
||||||
Продолжим наши эксперименты со сжатием. Теперь сожмем наши файл размером в 2,7 мб и 2.2 в формат WebP с разными параметрами качества:
|
Сожмем наши файл размером в 2,7 мб и 2.2 в формат WebP с разными параметрами качества:
|
||||||
- -q 90: Размер 325 кб.
|
- -q 90: Размер 325 кб.
|
||||||
- -q 85: Размер 267 кб.
|
- -q 85: Размер 267 кб.
|
||||||
- -q 80: Размер 229 кб.
|
- -q 80: Размер 229 кб.
|
||||||
@ -56,6 +376,9 @@ find ./images/comp -type f -iregex '.*\.\(jpg\|jpeg\|png\)' -not -iregex '.*no-c
|
|||||||
- -q 20: Размер 115 кб.
|
- -q 20: Размер 115 кб.
|
||||||
- -q 10: Размер 90 кб.
|
- -q 10: Размер 90 кб.
|
||||||
- -q 1: Размер 70 кб. Для экстренных случаев. Например, вы заблудились в лесу и надо отправить фото по спутниковой сети 😅
|
- -q 1: Размер 70 кб. Для экстренных случаев. Например, вы заблудились в лесу и надо отправить фото по спутниковой сети 😅
|
||||||
|
|
||||||
|
Пример использования на реальных изображениях моего блога
|
||||||
|
![[../../meta/files/images/Pasted image 20240925144640.png]]
|
||||||
## Nginx
|
## Nginx
|
||||||
Теперь мы научим nginx при запросе изображений сначала пытаться найти WebP файл, и только потом отдавать сжатый PNG/JPG, а если и сжатого нет, то отдавать обычный файл.
|
Теперь мы научим nginx при запросе изображений сначала пытаться найти WebP файл, и только потом отдавать сжатый PNG/JPG, а если и сжатого нет, то отдавать обычный файл.
|
||||||
|
|
||||||
@ -63,20 +386,12 @@ find ./images/comp -type f -iregex '.*\.\(jpg\|jpeg\|png\)' -not -iregex '.*no-c
|
|||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
location ~* ^(/blog/ru/content/images/)(.+)\.(png|jpe?g)$ {
|
location ~* ^(/blog/ru/content/images/)(.+)\.(png|jpe?g)$ {
|
||||||
expires max;
|
expires 1d;
|
||||||
alias /var/struchkov.dev/ghost/www/images;
|
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
|
||||||
set $webp_image_subdir "/webp/";
|
root /var/site/images;
|
||||||
set $basename $2;
|
try_files /webp/$2.webp /comp/$2.$3 $uri =404;
|
||||||
try_files $webp_image_subdir$basename$webp_suffix $uri;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Данный `location` обрабатывает все запросы к адресам, которые начинаются с `/images/`, за которым следует любое количество символов, затем точка, а затем формат файла png/jpeg/jpg.
|
|
||||||
|
|
||||||
Для запросов, соответствующих этому шаблону, выполняются следующие действия:
|
|
||||||
- Директива `alias` указывает путь к локальной директории, из которой будут отдаваться файлы. В данном случае путь к директории `/images`.
|
|
||||||
- Директивы `set` назначают переменные, которые будут использоваться в последующих директивах. Переменная `$webp_image_subdir` устанавливается в `/webp/`, а переменная `$basename` устанавливается в захваченную подстроку шаблона регулярного выражения (т.е. имя файла без расширения).
|
|
||||||
- Директива `try_files` пытается отдать Webp-версию запрошенного файла изображения, добавляя к переменным `$webp_image_subdir` и `$basename` суффикс `.webp`. Если NGINX сможет найти Webp-версию запрашиваемого файла изображения, он отдаст его. Если он не может найти WebP-версию, то отдаст сжатый JPG/PNG, если и сжатого файла не будет, то отдаст не сжатое.
|
|
||||||
***
|
***
|
||||||
## Мета информация
|
## Мета информация
|
||||||
**Область**:: [[../../meta/zero/00 Снипеты на bash|00 Снипеты на bash]]
|
**Область**:: [[../../meta/zero/00 Снипеты на bash|00 Снипеты на bash]]
|
||||||
|
@ -5,109 +5,330 @@ date: 2023-11-20
|
|||||||
zero-link:
|
zero-link:
|
||||||
- "[[../../meta/zero/00 Снипеты на bash|00 Снипеты на bash]]"
|
- "[[../../meta/zero/00 Снипеты на bash|00 Снипеты на bash]]"
|
||||||
parents:
|
parents:
|
||||||
- "[[../garden/ru/dev/fundamental/Сжатие данных|Сжатие данных]]"
|
- "[[../fundamental/Сжатие данных|Сжатие данных]]"
|
||||||
linked:
|
linked:
|
||||||
---
|
---
|
||||||
Размер изображений составляет существенную часть от размера страницы сайта. Поэтому часто я сжимаю изображения на своих сайтах. В этой заметке рассказываю какими способами я это делаю.
|
Данный скрипт предназначен для автоматического сжатия изображений форматов PNG и JPEG в заданной директории. Он использует современные утилиты сжатия, обеспечивая уменьшение размера файлов без заметной потери качества. Это особенно полезно для оптимизации веб-сайтов, ускорения загрузки страниц и экономии дискового пространства.
|
||||||
|
|
||||||
```shell
|
|
||||||
#!/bin/bash
|
|
||||||
file=comp.flag
|
|
||||||
|
|
||||||
if [ -f "$file" ]; then
|
|
||||||
option="-newer $file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
find ./images/ -type f -not -path "./images/comp/*" ! -name "*-no-comp.*" $option -iname "*.png" -exec sh -c '
|
|
||||||
png_file="${1/\/images\//\/images\/comp\/}"
|
|
||||||
png_dir="$(dirname "$png_file")"
|
|
||||||
mkdir -p "$png_dir"
|
|
||||||
cp "$1" "${png_file}"
|
|
||||||
optipng -o7 "${png_file}"
|
|
||||||
advpng -z4 "${png_file}"
|
|
||||||
pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time -ow "${png_file}"
|
|
||||||
' _ {} \;
|
|
||||||
|
|
||||||
find ./images/ -type f-not -path "./images/comp/*" ! -name "*-no-comp.*" $option -iregex '.*\.\(jpg\|jpeg\)' -exec sh -c '
|
|
||||||
jpg_file="${1/\/images\//\/images\/comp\/}"
|
|
||||||
jpg_dir="$(dirname "$jpg_file")"
|
|
||||||
mkdir -p "$jpg_dir"
|
|
||||||
cp "$1" "${jpg_file}"
|
|
||||||
jpegoptim --all-progressive "${jpg_file}"
|
|
||||||
' _ {} \;
|
|
||||||
|
|
||||||
touch $file
|
|
||||||
echo "$(date)" > $file
|
|
||||||
```
|
|
||||||
|
|
||||||
Этот скрипт сжимает изображения без потери качества. Он размещается рядом с каталогом `images`, в котором находятся ваши изображения. Разберемся, как он работает.
|
|
||||||
|
|
||||||
Сначала скрипт проверяет, существует ли в текущем каталоге файл `comp.flag`. Если файл существует, он устанавливает значение `-newer $file` в переменную `option`, которая будет использоваться в качестве фильтра для поиска только тех файлов, которые были изменены после даты создания файла `optimg.flag`. Если файл не существует, переменная `option` будет пустой.
|
|
||||||
|
|
||||||
Затем скрипт использует команду `find` для рекурсивного поиска файлов в каталоге `images` и его подкаталогах, которые:
|
|
||||||
- `-type f`. Являются обычными файлами.
|
|
||||||
- Имеют расширение имени файла `.png`, `.jpeg` или `.jpg`.
|
|
||||||
- `-not -path`_._ Не находятся в указанных каталогах_._ В данном случае это каталоги `./images/comp`. Мы будем складывать туда сжатые изображения, и мы не хотим заново по ним проходить поиском и сжимать снова.
|
|
||||||
- `! -name "-no-comp."`. Оставим возможность не сжимать изображение, если его имя заканчивается на `-no-comp`.
|
|
||||||
- Для каждого из этих файлов сценарий использует свою команду оптимизации.
|
|
||||||
|
|
||||||
Мы не будем затирать оригиналы изображений. Вместо этого мы создадим дополнительную папку `comp` в каталоге `images`, в которую и сложим преобразованные изображения. Создаваемая структура подкаталогов в `comp` будет повторять структуру подкатологов в `images`.
|
|
||||||
|
|
||||||
Для файлов PNG сначала используется `optipng` для сжатия с самым высоким уровнем оптимизации (`-o7`). Далее используем `advpng` для дальнейшего сжатия с уровнем сжатия 4 (`-z4`). И наконец `pngcrush` для удаления из файла определенных фрагментов, которые можно безопасно удалить для уменьшения размера файла.
|
|
||||||
|
|
||||||
Для файлов JPEG используется `jpegoptim` для их оптимизации со сжатием без потерь (`--all-progressive --strip-all`).
|
|
||||||
|
|
||||||
После оптимизации всех подходящих изображений сценарий пересоздает файл `comp.flag`. Это гарантирует, что скрипт будет оптимизировать только те файлы, которые были изменены с момента последнего запуска.
|
|
||||||
## Тесты на сжатие
|
|
||||||
Возьмем два одинаковых изображения (3456 x 2234): одно форматом jpg и размером 2.2 мб, второе форматом png и размером 2,7 мб.
|
|
||||||
|
|
||||||
Запускаем скрипт и смотрим на результат сжатия.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
** Processing: ./images/comp/image-two.png
|
|
||||||
3456x2234 pixels, 4x8 bits/pixel, RGB+alpha
|
|
||||||
Input IDAT size = 2664231 bytes
|
|
||||||
Input file size = 2666952 bytes
|
|
||||||
|
|
||||||
Trying:
|
|
||||||
zc = 9 zm = 9 zs = 0 f = 0 IDAT size = 2374591
|
|
||||||
zc = 9 zm = 8 zs = 0 f = 0 IDAT size = 2368803
|
|
||||||
zc = 9 zm = 9 zs = 0 f = 1 IDAT size = 2142264
|
|
||||||
zc = 9 zm = 8 zs = 0 f = 1 IDAT size = 2137390
|
|
||||||
zc = 9 zm = 9 zs = 1 f = 1 IDAT size = 2134232
|
|
||||||
zc = 9 zm = 8 zs = 1 f = 1 IDAT size = 2128765
|
|
||||||
zc = 9 zm = 9 zs = 0 f = 4 IDAT size = 2074935
|
|
||||||
zc = 9 zm = 8 zs = 0 f = 4 IDAT size = 2071117
|
|
||||||
zc = 9 zm = 9 zs = 1 f = 4 IDAT size = 2055799
|
|
||||||
zc = 9 zm = 8 zs = 1 f = 4 IDAT size = 2054390
|
|
||||||
zc = 9 zm = 9 zs = 0 f = 5 IDAT size = 2046026
|
|
||||||
zc = 9 zm = 8 zs = 0 f = 5 IDAT size = 2040193
|
|
||||||
zc = 9 zm = 9 zs = 1 f = 5 IDAT size = 2031130
|
|
||||||
zc = 9 zm = 8 zs = 1 f = 5 IDAT size = 2024568
|
|
||||||
|
|
||||||
Selecting parameters:
|
|
||||||
zc = 9 zm = 8 zs = 1 f = 5 IDAT size = 2024568
|
|
||||||
|
|
||||||
Output IDAT size = 2024568 bytes (639663 bytes decrease)
|
|
||||||
Output file size = 2025345 bytes (641607 bytes = 24.06% decrease)
|
|
||||||
|
|
||||||
2025345 2025345 100% ./images/comp/image-two.png (Bigger 2208631)
|
|
||||||
2025345 2025345 100%
|
|
||||||
Warning: versions are different between png.h and png.c
|
|
||||||
png.h version: 1.6.34
|
|
||||||
png.c version: 1.6.37
|
|
||||||
|
|
||||||
Recompressing IDAT chunks in ./images/comp/image-two.png
|
|
||||||
Total length of data found in critical chunks = 2024625
|
|
||||||
Best pngcrush method = 10 (ws 15 fm 6 zl 9 zs 1) = 2055326
|
|
||||||
CPU time decode 1.074758, encode 16.759768, other 0.018592, total 17.899076 sec
|
|
||||||
./images/comp/image-one.jpg 3456x2234 24bit N Exif XMP JFIF [OK] 2158567 --> 1947377 bytes (9.78%), optimized.
|
|
||||||
```
|
|
||||||
|
|
||||||
В результате мы смогли сжать png до 2.1 мб, а jpg до 1.9 мб. При этом сохранив исходный размер изображения и без потерь качества.
|
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Лучшее, на мой взгляд, приложение для сжатия jpg это JPEGmini Pro. Имеет версию cli для серверов. Но к сожалению оно платное. Его результат сжатия 2.2 мб —> 959 кб.
|
> Лучшее, на мой взгляд, приложение для сжатия jpg это JPEGmini Pro. Имеет версию cli для серверов. Но к сожалению оно платное. Его результат сжатия 2.2 мб —> 959 кб.
|
||||||
|
## Установка необходимых утилит
|
||||||
|
Перед запуском скрипта необходимо установить следующие утилиты:
|
||||||
|
- **pngquant** — инструмент для сжатия PNG-изображений.
|
||||||
|
- **zopflipng** — утилита для дополнительной оптимизации PNG-файлов от Google.
|
||||||
|
- **mozjpeg** — оптимизированный JPEG-кодек от Mozilla для сжатия JPEG-изображений.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Для 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
|
||||||
|
```
|
||||||
|
- [[../linux/rhel/Установка утилиты mozjpeg на RHEL|Установка утилиты mozjpeg на RHEL]]
|
||||||
|
|
||||||
|
|
||||||
|
```shell title="zip_image.sh"
|
||||||
|
#!/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 и прогрессивной разверткой.
|
||||||
|
- Аналогично проверяет эффективность сжатия перед сохранением.
|
||||||
|
- **Параллельная обработка**:
|
||||||
|
- Использует команду `find` для поиска всех изображений требуемых форматов.
|
||||||
|
- Пропускает файлы, содержащие `-no-comp` в названии, а также файлы в директории `comp`.
|
||||||
|
- Команда `xargs` с параметром `-P` запускает обработку в несколько потоков.
|
||||||
|
## Как использовать скрипт
|
||||||
|
1. **Подготовка**:
|
||||||
|
1. Поместите все изображения, которые необходимо сжать, в директорию `./images`.
|
||||||
|
2. Убедитесь, что установлены все необходимые утилиты.
|
||||||
|
2. **Запуск скрипта**:
|
||||||
|
1. Сделайте скрипт исполняемым: `chmod +x your_script_name.sh`
|
||||||
|
3. Запустите скрипт: `./your_script_name.sh`
|
||||||
|
4. **Результаты обработки**:
|
||||||
|
1. Сжатые изображения будут сохранены в директории `./images/comp`, сохраняя структуру исходной директории.
|
||||||
|
2. Логи успешных операций и ошибок можно просмотреть в файлах `zip_image_compression.log` и `zip_image_error.log` соответственно.
|
||||||
|
## Пример результатов сжатия
|
||||||
|
![[../../meta/files/images/Pasted image 20240925150631.png]]
|
||||||
|
## Nginx
|
||||||
|
Теперь мы научим 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;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
***
|
***
|
||||||
|
BIN
meta/files/images/Pasted image 20240925144640.png
Normal file
BIN
meta/files/images/Pasted image 20240925144640.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 MiB |
BIN
meta/files/images/Pasted image 20240925150631.png
Normal file
BIN
meta/files/images/Pasted image 20240925150631.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 MiB |
BIN
meta/files/images/comp/Pasted image 20240925144640.png
Normal file
BIN
meta/files/images/comp/Pasted image 20240925144640.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 859 KiB |
@ -0,0 +1 @@
|
|||||||
|
26c46c942b83a86396577c667e1c76ff
|
BIN
meta/files/images/comp/Pasted image 20240925150631.png
Normal file
BIN
meta/files/images/comp/Pasted image 20240925150631.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 605 KiB |
@ -0,0 +1 @@
|
|||||||
|
d7022072fd2eda793f69449c31de699a
|
BIN
meta/files/images/webp/Pasted image 20240925144640.webp
Normal file
BIN
meta/files/images/webp/Pasted image 20240925144640.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
@ -0,0 +1 @@
|
|||||||
|
26c46c942b83a86396577c667e1c76ff
|
BIN
meta/files/images/webp/Pasted image 20240925150631.webp
Normal file
BIN
meta/files/images/webp/Pasted image 20240925150631.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 759 KiB |
@ -0,0 +1 @@
|
|||||||
|
d7022072fd2eda793f69449c31de699a
|
@ -12,10 +12,6 @@ echo "Используется $THREADS потоков для обработки
|
|||||||
LOG_FILE="./zip_image_compression.log"
|
LOG_FILE="./zip_image_compression.log"
|
||||||
ERROR_LOG_FILE="./zip_image_error.log"
|
ERROR_LOG_FILE="./zip_image_error.log"
|
||||||
|
|
||||||
# Инициализируем файлы логов (удалены строки, чтобы не затирать логи)
|
|
||||||
# : > "$LOG_FILE"
|
|
||||||
# : > "$ERROR_LOG_FILE"
|
|
||||||
|
|
||||||
# Экспортируем необходимые переменные и функции для использования в subshell
|
# Экспортируем необходимые переменные и функции для использования в subshell
|
||||||
export IMAGE_DIR COMP_DIR WEBP_DIR LOG_FILE ERROR_LOG_FILE
|
export IMAGE_DIR COMP_DIR WEBP_DIR LOG_FILE ERROR_LOG_FILE
|
||||||
|
|
||||||
@ -320,6 +316,7 @@ export -f process_webp
|
|||||||
# Обработка PNG файлов
|
# Обработка PNG файлов
|
||||||
find "$IMAGE_DIR" -type f \
|
find "$IMAGE_DIR" -type f \
|
||||||
-not -path "$COMP_DIR/*" \
|
-not -path "$COMP_DIR/*" \
|
||||||
|
-not -path "$WEBP_DIR/*" \
|
||||||
! -name "*-no-comp.*" \
|
! -name "*-no-comp.*" \
|
||||||
-iname "*.png" -print0 | \
|
-iname "*.png" -print0 | \
|
||||||
xargs -0 -P "$THREADS" -I {} bash -c 'process_png "$@"' _ {}
|
xargs -0 -P "$THREADS" -I {} bash -c 'process_png "$@"' _ {}
|
||||||
@ -327,6 +324,7 @@ xargs -0 -P "$THREADS" -I {} bash -c 'process_png "$@"' _ {}
|
|||||||
# Обработка JPEG файлов
|
# Обработка JPEG файлов
|
||||||
find "$IMAGE_DIR" -type f \
|
find "$IMAGE_DIR" -type f \
|
||||||
-not -path "$COMP_DIR/*" \
|
-not -path "$COMP_DIR/*" \
|
||||||
|
-not -path "$WEBP_DIR/*" \
|
||||||
! -name "*-no-comp.*" \
|
! -name "*-no-comp.*" \
|
||||||
-iregex '.*\.\(jpg\|jpeg\)' -print0 | \
|
-iregex '.*\.\(jpg\|jpeg\)' -print0 | \
|
||||||
xargs -0 -P "$THREADS" -I {} bash -c 'process_jpeg "$@"' _ {}
|
xargs -0 -P "$THREADS" -I {} bash -c 'process_jpeg "$@"' _ {}
|
||||||
|
@ -403,3 +403,7 @@
|
|||||||
2024-09-25 10:19:38 Конвертирован в WebP: ./images/Pasted image 20231120092753.png на 81,28% (621233 байт -> 116264 байт)
|
2024-09-25 10:19:38 Конвертирован в WebP: ./images/Pasted image 20231120092753.png на 81,28% (621233 байт -> 116264 байт)
|
||||||
2024-09-25 10:19:38 Конвертирован в WebP: ./images/Pasted image 20240229204146.png на 91,84% (1343128 байт -> 109608 байт)
|
2024-09-25 10:19:38 Конвертирован в WebP: ./images/Pasted image 20240229204146.png на 91,84% (1343128 байт -> 109608 байт)
|
||||||
2024-09-25 10:19:40 Конвертирован в WebP: ./images/Pasted image 20230914145442.png на 74,19% (1371026 байт -> 353872 байт)
|
2024-09-25 10:19:40 Конвертирован в WebP: ./images/Pasted image 20230914145442.png на 74,19% (1371026 байт -> 353872 байт)
|
||||||
|
2024-09-25 15:14:55 Сжат PNG файл: ./images/Pasted image 20240925150631.png на 73.91% (2374543 байт -> 619599 байт)
|
||||||
|
2024-09-25 15:15:01 Сжат PNG файл: ./images/Pasted image 20240925144640.png на 75.64% (3611250 байт -> 879864 байт)
|
||||||
|
2024-09-25 15:15:06 Конвертирован в WebP: ./images/Pasted image 20240925150631.png на 67.28% (2374543 байт -> 776964 байт)
|
||||||
|
2024-09-25 15:15:08 Конвертирован в WebP: ./images/Pasted image 20240925144640.png на 68.31% (3611250 байт -> 1144526 байт)
|
||||||
|
Loading…
Reference in New Issue
Block a user