Хеши изображений
Генерирует hazehash, blurhash и thumbhash для растровых изображений — компактные строки, из которых на странице строится размытое превью картинки, пока настоящее изображение ещё грузится, — а по запросу ещё и доминирующий цвет и готовое крошечное превью.
mediatoolz image-hash [paths...] [options]В отличие от остальных команд набора, эта — генератор, а не проверка: она читает изображения и производит данные. На диск она ничего не пишет, пока вы не укажете, куда (-o, --per-file, --out-dir); без этого результат уходит в stdout — его можно передать по конвейеру или скопировать, а --dry-run показывает его таблицей.
Реальный вывод — одно изображение, только blurhash, прямо в stdout:
mediatoolz image-hash imgs/red.jpg -t blurhash{
"imgs/red.jpg": {
"width": 200,
"height": 100,
"blurhash": "L6T9R{,YfQ,Y|cjtfQjtfQfQfQfQ"
}
}В каждой записи стоят реальные width и height изображения (с учётом EXIF-ориентации), так что страница может заранее выделить нужный размер, пока ничего не загрузилось.
Что принимает на вход
[paths...] принимает файлы изображений, директории или и то и другое. Несколько путей можно передать отдельными аргументами или одним аргументом через запятую:
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или в CSSbackground. Весит несколько сотен байт.
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 по умолчанию:
mediatoolz image-hash imgs/sub/green.webp -t hazehash --budget 16
mediatoolz image-hash imgs/sub/green.webp -t hazehash --budget 28{
"imgs/sub/green.webp": {
"width": 64,
"height": 64,
"hazehash": "EG4WmXAAAGwAxjYZmMAQEQ"
}
}{
"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> пишет всё в один файл, создавая недостающие директории:
mediatoolz image-hash imgs -r -t thumbhash -f csv -o hashes.csvfile,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). Имя — это полное имя файла изображения плюс суффикс и расширение:
mediatoolz image-hash imgs -r -f plain --per-fileHashed 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 заменяет расширение, которое иначе зависит от формата. Свои суффикс и расширение:
mediatoolz image-hash imgs -r -t blurhash -f plain --out-dir hashes --suffix .bh --out-ext hashWrote 3 files:
hashes/blue.png.bh.hash
hashes/red.jpg.bh.hash
hashes/sub/green.webp.bh.hashСуществующие выходные файлы перезаписываются. Два изображения, которым достался бы один и тот же выходной файл (скажем, два явно названных файла с одинаковым именем из разных папок, оба в одну --out-dir), попадают в отчёт как проблема, а не затирают друг друга молча.
Предпросмотр в виде таблицы
--dry-run ничего не пишет и вместо этого показывает результат таблицей — удобно, чтобы быстро посмотреть результат до записи файлов и скопировать один хеш:
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 убирает записи, чьего изображения больше нет на диске:
mediatoolz image-hash public/img -r -o hashes.json --update --pruneHashed 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.
mediatoolz image-hash public/img -r -o hashes.json --checkHashed 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.
mediatoolz image-hash imgs -r -t blurhash -f ts --name placeholdersexport 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 без вопроса.
Примеры:
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 — не привязанный к фреймворку вариант, для любого проекта и любого потребителя хешей.