Skip to content

Хеши изображений ​

Генерирует hazehash, blurhash и thumbhash для растровых изображений — компактные строки, из которых на странице строится размытое превью картинки, пока настоящее изображение ещё грузится, — а по запросу ещё и доминирующий цвет и готовое крошечное превью.

bash
mediatoolz image-hash [paths...] [options]

В отличие от остальных команд набора, эта — генератор, а не проверка: она читает изображения и производит данные. На диск она ничего не пишет, пока вы не укажете, куда (-o, --per-file, --out-dir); без этого результат уходит в stdout — его можно передать по конвейеру или скопировать, а --dry-run показывает его таблицей.

Реальный вывод — одно изображение, только blurhash, прямо в stdout:

bash
mediatoolz image-hash imgs/red.jpg -t blurhash
json
{
  "imgs/red.jpg": {
    "width": 200,
    "height": 100,
    "blurhash": "L6T9R{,YfQ,Y|cjtfQjtfQfQfQfQ"
  }
}

В каждой записи стоят реальные width и height изображения (с учётом EXIF-ориентации), так что страница может заранее выделить нужный размер, пока ничего не загрузилось.

Что принимает на вход ​

[paths...] принимает файлы изображений, директории или и то и другое. Несколько путей можно передать отдельными аргументами или одним аргументом через запятую:

bash
mediatoolz image-hash hero.jpg logo.png
mediatoolz image-hash hero.jpg,logo.png,public/img

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

Кроме путей, аргументом может быть URL с http:// или https://: изображение скачивается (таймаут 30 секунд, предел 100 МБ), а ключом служит сам URL. URL с запятой не разбивается. --files-from <file> добавляет пути и URL из файла, по одному на строку (пустые строки и строки, начинающиеся с #, игнорируются); --files-from - читает список из stdin, поэтому работает git ls-files '*.png' | mediatoolz image-hash --files-from -.

Директория даёт только изображения, лежащие прямо в ней. -r/--recursive заходит и в поддиректории. --ext задаёт, какие расширения подбираются из директорий (по умолчанию .jpg,.jpeg,.png,.webp,.gif,.avif,.tif,.tiff, подходят и в верхнем регистре, например .JPG). Файл, названный явно, пробуется всегда, каким бы ни было расширение. node_modules, dist, .git и остальные привычные для набора шумные директории пропускаются, плюс то, что добавят .gitignore проекта и --ignore.

Что генерируется ​

-t/--type принимает список, что генерировать, через запятые или пробелы (PowerShell передаёт a,b одним аргументом a b, это тоже работает): hazehash, blurhash, thumbhash, color, preview. both (по умолчанию) означает blurhash,thumbhash, а all — всё сразу, включая hazehash.

Сначала каждое изображение уменьшается так, чтобы его длинная сторона была не больше --size пикселей (по умолчанию 100 — максимум, который принимает thumbhash), с применением EXIF-поворота и сохранением прозрачности. Все хеши считаются по этой одной маленькой копии, поэтому большая фотография стоит примерно столько же, сколько маленькая. Поэтому hazehash, посчитанный здесь, может немного отличаться от результата hazehash encode для исходного файла, который смотрит на пиксели полного размера; оба восстанавливают одинаковое по виду превью.

  • hazehash — строка base64url вроде Ed7UwRWKKv5znndNd2Ba284jhm2TLgpUMa0UkQ (38 символов для фотографии при бюджете по умолчанию). При том же размере он самый точный из трёх, хранит соотношение сторон и, только если у изображения есть прозрачные пиксели, альфа-канал. --budget <bytes> — наибольший размер одного хеша, от 7 до 48 (по умолчанию 28; формат настроен на диапазон 16–48). Это потолок, а не фиксированный размер: плоскому или плавному изображению нужно меньше байт, а однотонная красная картинка это просто 7-байтовый заголовок, FGez7WAAAA. О том, как декодировать его на странице, см. HazeHash.
  • blurhash — короткая строка вроде L6T9R{,YfQ,Y|cjtfQjtfQfQfQfQ. --components 4x3 задаёт детализацию по горизонтали и вертикали (каждая 1–9); чем больше компонентов, тем тоньше размытие и длиннее строка.
  • thumbhash хранится в base64. Он сохраняет соотношение сторон и альфа-канал, чего blurhash не умеет.
  • color — доминирующий цвет в виде #rrggbb: самый частый цвет уменьшенной копии без учёта прозрачных пикселей. У изображения без единого непрозрачного пикселя цвета нет, и поле опускается.
  • preview — крошечный PNG в виде URI data:image/png;base64,…, восстановленный из thumbhash, готовый для подстановки прямо в src или в CSS background. Весит несколько сотен байт.
bash
mediatoolz image-hash imgs --dry-run -t all --components auto
┌───────────────┬─────────┬────────────┬──────────────────────────────┬──────────────────────────────┬─────────┬──────────────────────────────┐
│ File          │    Size │ HazeHash   │ BlurHash                     │ ThumbHash                    │ Color   │ Preview                      │
├───────────────┼─────────┼────────────┼──────────────────────────────┼──────────────────────────────┼─────────┼──────────────────────────────┤
│ imgs/blue.png │ 120×300 │ CuvTNlAAAA │ T704c9gSfQf:fRfQfQfQfQf:fRfQ │ 3xEBAwB4h3d3f3iIAIUHmIg=     │ #0077ff │ data:image/png;base64,iVBOR… │
│ imgs/red.jpg  │ 200×100 │ FGez7WAAAA │ L6T9R{,YfQ,Y|cjtfQjtfQfQfQfQ │ 1fsDBICHh4h3h4d3iHD3iXifiA== │ #fe0000 │ data:image/png;base64,iVBOR… │
└───────────────┴─────────┴────────────┴──────────────────────────────┴──────────────────────────────┴─────────┴──────────────────────────────┘

Hashed 2 images.

(dry run — nothing written)

Бюджет hazehash — это потолок. Одно и то же изображение при 16 байтах и при 28 по умолчанию:

bash
mediatoolz image-hash imgs/sub/green.webp -t hazehash --budget 16
mediatoolz image-hash imgs/sub/green.webp -t hazehash --budget 28
json
{
  "imgs/sub/green.webp": {
    "width": 64,
    "height": 64,
    "hazehash": "EG4WmXAAAGwAxjYZmMAQEQ"
  }
}
json
{
  "imgs/sub/green.webp": {
    "width": 64,
    "height": 64,
    "hazehash": "EDr2mXAAANjAAGMDNjY"
  }
}

При 16 байтах энкодер тратит все (22 символа), а при 28 этой картинке нужно всего 14 (19 символов). Бюджет меньше необходимого не считается ошибкой, но изображение с прозрачностью не уместится меньше чем в 9 байт, и о таком изображении сообщается как о проблеме.

--components auto подбирает компоненты blurhash по соотношению сторон вместо фиксированных 4x3: 4x3 для фото 16:9, 3x4 для портретного, 4x4 для квадратного. Портретное фото с 4x3 размывается неравномерно; auto этого избегает.

Анимированные изображения (GIF, анимированный WebP) хешируются по первому кадру. CMYK-изображения сначала переводятся в sRGB. Файл, который оборван или не является изображением, попадает в отчёт как проблема, а не хешируется по тому, что удалось прочитать, а изображение больше лимита пикселей (по умолчанию у sharp около 268 миллионов пикселей) отклоняется с сообщением, называющим --max-pixels, который поднимает лимит или, при 0, снимает его.

Ключи в выводе ​

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

  • --key-base <dir> делает ключи относительными этой директории: с --key-base public путь public/img/hero.jpg превращается в img/hero.jpg.
  • --key-prefix <text> ставит текст перед каждым ключом: добавьте --key-prefix / — и получится /img/hero.jpg.

Обе опции меняют только ключи; файлы по-прежнему читаются и пишутся там, где лежат. (В Git Bash на Windows аргумент, начинающийся с /, превращается в путь Windows — задайте MSYS_NO_PATHCONV=1 или используйте //.) У входного URL ключом остаётся сам URL.

Куда пишется результат ​

stdout ​

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

Один файл ​

-o <file> пишет всё в один файл, создавая недостающие директории:

bash
mediatoolz image-hash imgs -r -t thumbhash -f csv -o hashes.csv
file,width,height,thumbhash
imgs/blue.png,120,300,3xEBAwB4h3d3f3iIAIUHmIg=
imgs/red.jpg,200,100,1fsDBICHh4h3h4d3iHD3iXifiA==
imgs/sub/green.webp,64,64,EXsABwC/iYjfioireJiIiJh3BgIhEFYO

Файл на каждое изображение ​

--per-file пишет файл рядом с каждым изображением; --out-dir <dir> пишет их в указанную директорию, повторяя структуру папок относительно переданного вами пути (и подразумевает --per-file). Имя — это полное имя файла изображения плюс суффикс и расширение:

bash
mediatoolz image-hash imgs -r -f plain --per-file
Hashed 3 images.

Wrote 6 files:
  imgs/blue.png.blurhash.txt
  imgs/blue.png.thumbhash.txt
  imgs/red.jpg.blurhash.txt
  imgs/red.jpg.thumbhash.txt
  imgs/sub/green.webp.blurhash.txt
  imgs/sub/green.webp.thumbhash.txt

С --format plain каждый хеш получает свой файл, в котором лежит только текст хеша без перевода строки, так что программа может прочитать его как есть. С любым другим форматом на изображение приходится один файл со всем запрошенным, а {type} превращается в hash, если запрошено больше одного типа.

--suffix заменяет суффикс по умолчанию .{type}; {type} означает hazehash, blurhash, thumbhash или hash. --out-ext заменяет расширение, которое иначе зависит от формата. Свои суффикс и расширение:

bash
mediatoolz image-hash imgs -r -t blurhash -f plain --out-dir hashes --suffix .bh --out-ext hash
Wrote 3 files:
  hashes/blue.png.bh.hash
  hashes/red.jpg.bh.hash
  hashes/sub/green.webp.bh.hash

Существующие выходные файлы перезаписываются. Два изображения, которым достался бы один и тот же выходной файл (скажем, два явно названных файла с одинаковым именем из разных папок, оба в одну --out-dir), попадают в отчёт как проблема, а не затирают друг друга молча.

Предпросмотр в виде таблицы ​

--dry-run ничего не пишет и вместо этого показывает результат таблицей — удобно, чтобы быстро посмотреть результат до записи файлов и скопировать один хеш:

bash
mediatoolz image-hash imgs -r -t blurhash --dry-run
┌─────────────────────┬─────────┬──────────────────────────────┐
│ File                │    Size │ BlurHash                     │
├─────────────────────┼─────────┼──────────────────────────────┤
│ imgs/blue.png       │ 120×300 │ L704c9gSfQgSf:fRfQfRfQfQfQfQ │
│ imgs/red.jpg        │ 200×100 │ L6T9R{,YfQ,Y|cjtfQjtfQfQfQfQ │
│ imgs/sub/green.webp │   64×64 │ L207Z?hWfjhVhWf*fjfjfQfQfQfQ │
└─────────────────────┴─────────┴──────────────────────────────┘

Hashed 3 images.

(dry run — nothing written)

В таблице — столбец на каждый запрошенный хеш, реальный размер и путь к файлу, который слева сокращается, если терминал слишком узкий. В сочетании с -o, --per-file или --out-dir она ещё и перечисляет файлы, которые были бы записаны, но ни один из них не пишет. --format на таблицу не влияет. С --json печатается полный отчёт, как обычно.

Как держать выходной файл актуальным ​

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

--cache [file] запоминает время изменения и размер каждого изображения и использованные настройки в JSON-файле (по умолчанию .mediatoolz-image-hash-cache.json в --cwd; добавьте его в .gitignore). Неизменившееся изображение берётся из кэша, а если из кэша взяты все, sharp даже не загружается. В отчёте указано, сколько изображений пришло из кэша:

Hashed 3 images, 3 from the cache.

Изменение файла, --size или --components, а также новый запрошенный тип пересчитывают это изображение. --dry-run и --check кэш никогда не пишут.

--update вливает результат в существующий -o-файл вместо его замены: изображения, хешированные сейчас, перезаписывают свои записи, а записи тех, что в этом запуске не упомянуты, остаются. Так можно хешировать только новую папку в общий файл, а хеши других типов, уже лежащие в файле, сохраняются. Работает с json, ts, js и csv, на файле, который сгенерировала эта команда; файл, переформатированный Prettier, прочитать обратно уже нельзя. --prune вместе с --update убирает записи, чьего изображения больше нет на диске:

bash
mediatoolz image-hash public/img -r -o hashes.json --update --prune
Hashed 2 images.

Wrote 1 file:
  out/h.json

Removed 1 entry whose image no longer exists.

--check — для CI. Он генерирует всё как обычно, сравнивает с тем, что лежит на диске, ничего не пишет и завершается с кодом 1, если выходной файл отсутствует или отличается, и с 0, если всё актуально. Ему нужен выход, с которым сравнивать (-o, --per-file или --out-dir), и его нельзя сочетать с --dry-run или --update.

bash
mediatoolz image-hash public/img -r -o hashes.json --check
Hashed 3 images.

1 of 1 output file out of date:
  out/h.json — differs from the images

(regenerate them by running the same command without --check)

Сочетайте --cache с --check, чтобы запуск в CI оставался быстрым.

Форматы вывода ​

-f/--format — один из:

  • json (по умолчанию) — объект, где ключ — путь к файлу относительно --cwd, а значение содержит width, height и запрошенные хеши. Файл на изображение содержит только этот объект.
  • plain — только текст хеша. В агрегированном виде (stdout или -o) — по одному изображению на строку: path, затем каждый хеш, через табуляцию.
  • csv — строка заголовка, затем file,width,height и по столбцу на каждый хеш. Поля с запятой или кавычкой заключаются в кавычки.
  • ts — модуль, который можно импортировать: export const imageHashes = { … } as const. Файл на изображение использует export default. --name меняет имя константы.
  • js — то же без as const.
bash
mediatoolz image-hash imgs -r -t blurhash -f ts --name placeholders
ts
export const placeholders = {
  "imgs/blue.png": {
    "width": 120,
    "height": 300,
    "blurhash": "L704c9gSfQgSf:fRfQfRfQfQfQfQ"
  },
  "imgs/red.jpg": {
    "width": 200,
    "height": 100,
    "blurhash": "L6T9R{,YfQ,Y|cjtfQjtfQfQfQfQ"
  },
  "imgs/sub/green.webp": {
    "width": 64,
    "height": 64,
    "blurhash": "L207Z?hWfjhVhWf*fjfjfQfQfQfQ"
  }
} as const

Файл записывается в простой JSON-раскладке; если проект форматирует сгенерированный код, прогоните его через Prettier.

Библиотека sharp ​

Для декодирования изображений нужна sharp — нативная библиотека с готовыми бинарниками для распространённых платформ. Она идёт вместе с mediatoolz: npm ставит бинарник для вашей платформы одновременно с пакетом, так что обычно ничего делать не нужно.

Бинарника может не оказаться, если пакет поставили с --omit=optional или на платформе, для которой у sharp его нет. Тогда, когда она впервые понадобится image-hash:

  • если sharp установлена в проекте (--cwd) или с прошлого запуска, используется она как есть;
  • иначе в терминале команда спрашивает, ставить ли её в ~/.mediatoolz/deps, и при согласии запускает там npm install и продолжает работу; при отказе завершается, ничего не сделав;
  • без терминала (CI, конвейер) она завершается с сообщением, если не передан -y/--yes, который заранее соглашается на установку.

Управляемая копия лежит в домашней директории, а не в каком-либо проекте, поэтому package.json и node_modules проекта никогда не затрагиваются.

Опции ​

[paths...] ​

Файлы изображений и/или директории; можно несколько, в том числе через запятую.

--cwd <path> ​

Относительно чего разрешаются пути и от чего считаются пути файлов в выводе. По умолчанию — текущая директория.

-r, --recursive ​

Заходить и в поддиректории каждой переданной директории.

--ext <list> ​

Расширения изображений через запятую, которые подбираются из директорий.

--ignore <glob> ​

Дополнительный паттерн игнорирования (повторяемый) поверх встроенных по умолчанию.

--no-respect-gitignore ​

Не учитывать .gitignore проекта.

--files-from <file> ​

Прочитать ещё пути и URL из файла, по одному на строку; - читает stdin. Пустые строки и строки, начинающиеся с #, игнорируются.

-t, --type <list> ​

Что генерировать, через запятую: hazehash, blurhash, thumbhash, color, preview. both — это blurhash,thumbhash, all — всё сразу. По умолчанию: both.

--budget <bytes> ​

Только для hazehash: наибольший размер одного хеша в байтах, вместе с заголовком, от 7 до 48. Формат настроен на диапазон 16–48. По умолчанию: 28. Меньший бюджет даёт более короткие хеши с меньшей детализацией; см. Что генерируется. Смена значения заставляет --cache пересчитать изображения.

--components <XxY|auto> ​

Компоненты blurhash, каждая сторона 1–9, или auto, чтобы подобрать их по соотношению сторон. По умолчанию: 4x3.

--size <px> ​

Длинная сторона, до которой изображение уменьшается перед хешированием, 1–100. По умолчанию: 100.

--max-pixels <n> ​

Отклонять изображения, в которых пикселей больше этого значения; 0 снимает лимит. По умолчанию — лимит sharp, около 268 миллионов.

-f, --format <format> ​

json, plain, csv, ts или js — формат генерируемых хешей. По умолчанию: json. Чтобы получить полный отчёт (записи, записанные файлы, проблемы), используйте --json.

--name <identifier> ​

Имя экспортируемой константы для --format ts/js. По умолчанию: imageHashes.

--key-base <dir> ​

Сделать ключи файлов в выводе относительными этой директории вместо --cwd.

--key-prefix <text> ​

Поставить этот текст перед каждым ключом файла, например /.

-o, --out <file> ​

Записать всё в один файл вместо stdout. Нельзя сочетать с --per-file/--out-dir.

--update ​

Влить результат в существующий -o-файл вместо его замены. Работает с json, ts, js и csv.

--prune ​

Вместе с --update убрать записи, чьего изображения больше нет.

--per-file ​

Записать по файлу на изображение, рядом с изображением.

--out-dir <dir> ​

Записать файлы по изображениям в эту директорию, повторяя структуру папок. Подразумевает --per-file.

--suffix <text> ​

Суффикс имени файла по изображению после имени файла изображения; {type} — это hazehash, blurhash, thumbhash или hash. По умолчанию: .{type}. С --format plain и несколькими типами обязан содержать {type}.

--out-ext <ext> ​

Расширение файла по изображению, с точкой или без. По умолчанию зависит от формата: .json, .txt (plain), .csv, .ts, .js.

--check ​

Ничего не писать; завершиться с кодом 1, если выходные файлы отсутствуют или отличаются от изображений. Нужны -o, --per-file или --out-dir.

--cache [file] ​

Пропускать изображения, которые не менялись с прошлого запуска, запоминая это в указанном файле. Файл по умолчанию: .mediatoolz-image-hash-cache.json.

--concurrency <n> ​

Сколько изображений обрабатывается параллельно. По умолчанию: 4.

--dry-run ​

Ничего не писать — вместо этого показать результат таблицей.

-y, --yes ​

Установить недостающую библиотеку sharp без вопроса.

Примеры:

bash
mediatoolz image-hash public/img                                  # оба хеша, JSON в stdout
mediatoolz image-hash a.jpg,b.png,photos -r -t blurhash -f csv -o hashes.csv
mediatoolz image-hash public/img -r -t hazehash --budget 24 -o hashes.json   # hazehash не больше 24 байт
mediatoolz image-hash public/img -r -f plain --per-file           # photo.jpg.blurhash.txt + photo.jpg.thumbhash.txt
mediatoolz image-hash public/img -r -f ts -t thumbhash -o src/placeholders.ts --name placeholders
mediatoolz image-hash public/img -r -t all --dry-run              # хеши, доминирующий цвет и превью таблицей
mediatoolz image-hash public/img -r -o hashes.json --cache --update --prune   # инкрементально, в актуальном состоянии
mediatoolz image-hash public/img -r -o hashes.json --check        # CI: упасть, если hashes.json устарел
mediatoolz image-hash public/img -r --key-base public --key-prefix / -o src/hashes.json
git ls-files '*.png' | mediatoolz image-hash --files-from - -f csv
mediatoolz image-hash https://example.com/a.jpg -t color
mediatoolz image-hash public/img -r --json                        # полный отчёт, с хешами

Проблемы и коды возврата ​

Изображение, которое не удалось прочитать (битый или оборванный файл, неподдерживаемый формат, слишком много пикселей, несуществующий путь, неудавшаяся загрузка), попадает в отчёт с путём и причиной простыми словами, а остальные изображения продолжают обрабатываться. В терминале при хешировании большого набора показывается счётчик прогресса; он идёт в stderr, так что с данными не смешивается. Код возврата — 0, если всё захешировано (а с --check — и все выходные файлы актуальны), 1, если сообщено хотя бы об одной проблеме или выходной файл устарел, и 2 при ошибке в опциях (неверный --type, --out вместе с --per-file) или когда sharp отсутствует и установить её не удалось.

--json печатает полный отчёт: все записи, записанные файлы и проблемы. image-hash генерирует данные, а не диагностирует проект, поэтому ему нечего делать в общей проверке вроде full-check из devtoolz.

Если в проекте используется vue-image-kit, его собственная команда placeholders записывает результат прямо в шаблоны <VImage> и в манифест; image-hash — не привязанный к фреймворку вариант, для любого проекта и любого потребителя хешей.