Skip to content

Image Batch ​

Produces resized, converted and recompressed versions of raster images in bulk. What to produce — sizes, formats, codec settings and file names — is described by a set of rules, either in a reusable config or directly in flags. Where to read from and where to put the results is decided on every run, so a single config serves any project.

bash
mediatoolz image-batch [paths...] (-o <dir> | --beside | --replace) [options]

The command is a generator, like image-hash: it reads images and writes files. You choose where the results go:

  • --out <dir> — into a separate folder, repeating the source's folder structure. The sources stay untouched.
  • --beside — into the folder of each source, next to it. The sources stay untouched.
  • --replace — over the source files themselves, each in its own format: shrink oversized originals or recompress them in place. The originals are backed up first, and a single command puts them back.

Real output — two images, two widths, two formats, into a separate folder:

bash
mediatoolz image-batch img -r -o out -w 300,600 -f webp,jpg -q high
text
2 images → out

┌───────────┬─────────────────┬─────────┬─────────┬─────────┐
│ Source    │ Output          │    Size │   Bytes │ Status  │
├───────────┼─────────────────┼─────────┼─────────┼─────────┤
│ a.png     │ a-300w.webp     │ 300×200 │   898 B │ written │
│ a.png     │ a-600w.webp     │ 600×400 │ 2.37 KB │ written │
│ a.png     │ a-300w.jpg      │ 300×200 │ 2.04 KB │ written │
│ a.png     │ a-600w.jpg      │ 600×400 │ 4.13 KB │ written │
│ sub/b.png │ sub/b-300w.webp │ 300×450 │ 1.76 KB │ written │
│ sub/b.png │ sub/b-600w.webp │ 600×900 │ 4.45 KB │ written │
│ sub/b.png │ sub/b-300w.jpg  │ 300×450 │ 4.12 KB │ written │
│ sub/b.png │ sub/b-600w.jpg  │ 600×900 │ 9.63 KB │ written │
└───────────┴─────────────────┴─────────┴─────────┴─────────┘

8 written
Wrote 8 files, 29.4 KB (the sources they came from: 240 KB).

Each row is one produced file. The sizes are the real dimensions of the result, computed before anything is written.

Quick start ​

A responsive set for a site — three widths, modern formats first, into public/images:

bash
mediatoolz image-batch ./src/images -r -o ./public/images -w 400,800,1200 -f avif,webp,jpg -q high

Size every image by its long side, whatever the orientation, and keep each file under 200 KB:

bash
mediatoolz image-batch ./photos -r -o ./out --long 1600 -f webp,jpg --max-size 200KB

Only change the format — the size stays as it is, nothing is resized or sharpened:

bash
mediatoolz image-batch ./photos -r -o ./out -f webp

The same set from a saved config, ticking the files from a list first:

bash
mediatoolz image-batch ./src/images -r -o ./public/images -c web --select

Shrink oversized originals to at most 1600 px wide, in place, after a preview:

bash
mediatoolz image-batch ./assets -r --replace -w 1600 --dry-run
mediatoolz image-batch ./assets -r --replace -w 1600

Where the results go ​

Exactly one of --out, --beside and --replace is given. Without any of them the command stops and says so; in a terminal it asks for the output folder instead.

Into a separate folder ​

-o <dir> writes the results into dir. The subfolder structure of each input folder is repeated inside it; --flat puts everything straight into dir instead. A file name template decides how each result is named (see File names).

If dir lies inside an input folder, it is skipped while the input is read, so results are never processed a second time.

Next to the sources ​

--beside writes every result into the folder of its source: photos/a.png produces photos/a-400w.webp. The name template must give a name different from the source's ({width}, a different format, or a suffix); a template that would land on the source itself is an error that points at --replace.

Running it again does not treat the results of the earlier run as new sources. The command keeps a record of the files it has made, and when that record is missing, it recognizes a file that is the planned result of another source in the same run. Such files are left alone and listed in the summary.

Over the sources ​

--replace rewrites each source file with the result, in the same format and under the same name. Typical uses are shrinking images that are larger than needed (-w 1600 means "no wider than 1600") and recompressing them (-q high, a png palette, mozjpeg).

A run that overwrites files you cannot easily recreate is guarded in several ways:

  • One result per image, in the image's own format. A recipe that would produce several files per image (widths: [400, 800]) or another format (formats: ["webp"]) is an error before anything is written. Leave formats out, or use original. A file name template has no effect, and --name is rejected.
  • Only if it gets smaller. A file is replaced only when the result is smaller than the original; otherwise it stays as it is and is reported as kept (not smaller). Re-encoding a JPEG at the same quality only loses detail, and this rule keeps it from happening for nothing. --no-only-if-smaller replaces regardless.
  • Never processed twice. Every file the command has rewritten, or has examined and kept, is remembered together with its settings. On the next run with the same settings it is reported as already done and not touched, so repeated runs do not degrade the images further. Change the settings (a smaller width, another quality), and the file is processed again; --force ignores the record.
  • A backup first. Before a file is overwritten, the original is copied to .image-batch-backup/<date>/ in the current folder, keeping its relative path, and the copies are listed in a journal.json there. --backup <dir> chooses another place, --no-backup turns the copy off.
  • A confirmation. In a terminal the command first shows how many images and how many bytes it is about to replace, and where the originals go, and asks. Without a terminal it requires --yes; without it, it stops with an error.
  • A preview with real numbers. --dry-run encodes every file in memory and shows, for each, the size now and the size it would have, and the total saving — and writes nothing.
  • Atomic writes. A file is written next to its destination and renamed into place, so an interrupted run never leaves a half-written image.

SVG files are vectors with nothing to shrink; they are left alone and counted in the summary, not reported as errors.

Real output — three images shrunk to at most 600 px wide; the third is already compact, so it stays:

bash
mediatoolz image-batch photos --replace -w 600 --dry-run
text
3 images → in place

┌──────────────┬─────────┬────────┬────────┬───────────────┐
│ File         │    Size │    Was │    Now │ Status        │
├──────────────┼─────────┼────────┼────────┼───────────────┤
│ ai-block.png │ 600×400 │ 407 KB │ 376 KB │ would replace │
│ airport.png  │ 600×354 │ 237 KB │ 193 KB │ would replace │
│ avatar.png   │ 357×570 │ 250 KB │ 250 KB │ would keep    │
└──────────────┴─────────┴────────┴────────┴───────────────┘

2 would be replaced · 1 would be kept
Would save 75.3 KB (12% of 644 KB): 644 KB → 569 KB.

(dry run — nothing written; drop --dry-run to write the files)

Without --dry-run, the same command asks for confirmation (or takes --yes), writes the files, and ends with the way back:

text
2 replaced · 1 kept (not smaller)
Saved 75.3 KB (12% of 644 KB): 644 KB → 569 KB.
Originals saved to /work/site/.image-batch-backup/20261007-233706 — undo with: mediatoolz image-batch restore /work/site/.image-batch-backup/20261007-233706

A second identical run changes nothing:

text
0 replaced · 3 already done

Putting the originals back ​

bash
mediatoolz image-batch restore .image-batch-backup/20261007-233706

restore reads the journal.json in the backup folder and copies every saved original back over its file. Before it overwrites a file, it checks that the file is still the one --replace wrote; a file edited since then is left alone and listed (--force restores it anyway). A backup copy that is gone or no longer matches what was saved is reported as such. --dry-run shows what would be restored. In a terminal it asks first; without one it requires --yes. After a restore, the next --replace run processes those files again.

The backup folder stays where it is; delete it when you no longer need the originals.

Choosing the images ​

  • [paths...] — image files, folders and globs such as photos/**/*.jpg. With none, the current folder.
  • -r, --recursive walks subfolders. Folders are read for jpg, jpeg, png, webp, gif, avif, tif, tiff and svg; a file named explicitly is read whatever its extension.
  • --include <glob>, --exclude <glob> narrow the set by the path inside the input folder. Repeatable.
  • --files-from <file> reads more paths from a file, one per line.
  • -i, --select shows every file found — its path, dimensions, format and size — all ticked, and lets you untick the ones you do not want before anything is processed. Keys: arrows or j/k to move, space to tick, a all, n none, i invert, / to filter by text, Enter to confirm, q to cancel. It needs a real terminal.
  • --list only prints what would be processed and writes nothing, in or out of a terminal.

The .image-batch-backup folder is never read as input.

Configs ​

A config is a JSON file with the rules. It holds no input or output paths, so one config fits any folder and any project.

json
{
  "name": "web",
  "description": "Responsive images for the site",
  "defaults": { "fit": "inside", "withoutEnlargement": true },
  "presets": {
    "responsive": { "widths": [400, 800, 1200], "formats": ["avif", "webp", "jpg"] }
  },
  "outputs": [
    { "id": "main", "preset": "responsive", "quality": "high" },
    {
      "id": "thumb",
      "size": "200x200",
      "fit": "cover",
      "formats": { "webp": { "quality": 70 } },
      "name": "{dir}/{name}-thumb.{format}"
    }
  ],
  "match": [
    { "glob": "hero/**", "outputs": [{ "widths": [1600, 2400], "formats": ["avif", "jpg"] }] }
  ],
  "placeholders": { "hazehash": { "budget": 28 } }
}
  • outputs is a list of recipes. Each recipe says what to make from every source; see Recipes.
  • presets are named sets of recipe fields. A recipe pulls one in with "preset", and a preset can build on another with "extends"; the recipe's own fields win.
  • defaults apply to every recipe.
  • match picks recipes by the path of the source inside the input folder. The first rule whose glob matches replaces outputs for that file; a file that matches no rule (and has no outputs) is left alone and listed in the summary.
  • placeholders names the hashes to compute for the manifest.
  • name and description are labels shown in lists.

The sizes a config holds can be switched off for one run with --no-resize; the formats, quality and the other settings still apply.

The config's own name is its file name: .mediatoolz/image-batch/web.json is web. Configs are looked up in the project (.mediatoolz/image-batch/, walking up from the current folder to the repository root, the folder with .git) and in ~/.mediatoolz/image-batch/; a project config wins over a global one of the same name. .js, .mjs and .ts files work too — they export the object, or a function returning it — but configs cannot edit them, and a .ts file is transpiled on its own, so it cannot import other files.

-c <name|file> selects a config. With no -c, a single config found is applied automatically, unless flags such as -w or -f already describe the job; with several, a terminal asks which one, and without a terminal it is an error that lists them. Flags always override the config for one run.

Recipes ​

A recipe is one entry of outputs. Everything is optional; an empty recipe copies each file at its own size and format.

  • widths — list of whole numbers. One output per width; the height follows the aspect ratio.
  • heights — the same, by height.
  • size — "800x600": one output that fills this box according to fit.
  • longEdge — list of whole numbers. One output per value: the longer side of the image becomes this size, the other follows the aspect ratio. It works the same for landscape and portrait images.
  • shortEdge — the same, by the shorter side.
  • megapixels — list of numbers such as [2, 0.5]. One output per value with about that many million pixels, the aspect ratio kept.
  • percent — list of numbers such as [50, 25]. One output per value, as a percentage of the source size.
  • matchOrientation — default false. With size, turns the box around (300×200 becomes 200×300) for an image of the other orientation.
  • scale — list of multipliers applied to every size above ([1, 2] makes @1x and @2x; for megapixels the area grows by the square); with no size given, it scales the source itself.
  • maxBytes — a size such as "200KB" or "1.5MB" that no file may exceed; see Limiting the file size.
  • formats — jpg, png, webp, avif, gif, tiff, jp2, heif, or original (keep the source format; an SVG becomes a png). A list, or a map of format → codec options.
  • quality — a number 1–100, a level (low, medium, high, best), or a map per format. See Codec options.
  • fit — how the image fills the box: inside (default; the whole image, aspect ratio kept), outside, cover (crop to the exact box), contain (letterbox with background), fill (stretch).
  • position — which part cover keeps: centre, north, top, right bottom, entropy, attention, …
  • background — the letterbox color; #rrggbb, #rrggbbaa or a color name.
  • withoutEnlargement — default true: a size above the source size (a percentage above 100 included) is clamped to the source. Several requested sizes that clamp to the same file become one file, and the summary says how many were merged. SVG sources are vectors and are always drawn at the size asked for.
  • name — the file name template.
  • autoOrient — default true: turn the image the way its EXIF orientation says.
  • rotate — 90, 180 or 270.
  • flip, flop — mirror vertically or horizontally.
  • grayscale, blur (sigma).
  • sharpen — sharpening after the resize: the name of a saved preset, or an object with for, amount, radius, flat, jagged and threshold. See Sharpening.
  • flatten — put transparency on a solid background: true (white) or a color. JPEG output is always flattened onto white unless this says otherwise.
  • metadata — strip (default), keep, or keep-icc.
  • id — a name for the recipe, used in messages and in the config editor.

Sizes are computed before anything is written, so {width} and {height} in a name are the real dimensions of the result.

Size methods ​

A recipe can ask for sizes in several ways at once; every value of every method produces its own result, and results that come out at the same size are merged into one file.

  • By width or height (widths, heights) — one side is given, the other follows the aspect ratio. The usual choice when the layout is defined by a width.
  • By a box (size with fit) — the image fits inside the box (inside, the default) or fills it exactly (cover, contain, fill). With matchOrientation, a 1920×1080 box serves portrait images as 1080×1920, so one box fits a mixed set.
  • By the long or short side (longEdge, shortEdge) — independent of the orientation: longEdge: [2000] makes a landscape image 2000 px wide and a portrait one 2000 px tall. The natural choice for mixed galleries and for --replace.
  • By area (megapixels) — about the given number of million pixels whatever the proportions; handy for print and for bounding the memory a decoded image takes.
  • By percentage (percent) — a share of the source size, for a ladder of fractions or a quick halving.

Without any size a recipe does not resize: each result keeps the dimensions of its source, and only the format, the quality and the other settings apply. That is the way to convert a folder to another format.

The aspect ratio is kept by every method except the exact boxes (cover, contain, fill). Nothing is enlarged unless withoutEnlargement is false, so percent: [200] or longEdge: [4000] on a smaller image leaves it as it is. With --replace, a size is a maximum.

Sharpening ​

Resizing softens an image, and a small copy that is sharpened for the place it is shown in looks cleaner. sharpen adds that last step after the resize. It follows the output sharpening of Lightroom: you say what the result is for and how much, and for the rare case that needs more control you can set the numbers yourself.

json
{
  "id": "web",
  "longEdge": [1600],
  "formats": ["jpg"],
  "sharpen": { "for": "screen", "amount": "standard" }
}
  • for — screen (default; a light radius for the web and displays), matte (a medium one for matte paper) or glossy (the strongest, for glossy paper).
  • amount — low, standard (default) or high.

The pair picks the values from a fixed table. They do not depend on the size of the result. Naming either one switches sharpening on and the other takes its default, so "sharpen": { "amount": "high" } means screen and high. A recipe that does not mention sharpen is not sharpened at all.

Fine settings ​

Four more numbers replace single values of the table. They are passed to the sharpening of the sharp library as they are:

  • radius — the width of the sharpened edge, a sigma from 0.000001 to 10. The table gives 0.6 for screen, 1 for matte and 1.4 for glossy.
  • flat — how strongly flat and smooth areas are sharpened, 0 to 1000000. The table gives 0.5 (low), 1 (standard) or 1.8 (high). Lower it to keep skies and skin clean.
  • jagged — how strongly the sharp edges are sharpened, 0 to 1000000. The table gives 1.5, 3 or 5.
  • threshold — the contrast that separates a flat area from an edge, 0 to 1000000. Without it sharp uses its own value, 2.
json
{ "sharpen": { "for": "matte", "radius": 1.2, "flat": 0.4, "jagged": 4 } }

A number outside its range stops the command before anything is written, and the message names the field and the range. Sharpening works on the lightness of the image, so it does not add colored halos.

Sharpening presets ​

A set of sharpening values can be saved once and used by name in any config, and from the command line. The presets are managed by their own command, image-batch sharpen, because a preset belongs to no single config.

bash
mediatoolz image-batch sharpen                    # pick a preset: show, edit, copy, delete
mediatoolz image-batch sharpen new                # create one with a few questions
mediatoolz image-batch sharpen new --name web-crisp --sharpen-for screen --sharpen-radius 0.8
mediatoolz image-batch sharpen list               # just the table (--json for scripts)
mediatoolz image-batch sharpen show web-crisp     # the settings and what sharp receives
mediatoolz image-batch sharpen rm web-crisp --yes # delete (a .bak copy stays unless --force)

Built-in presets ​

Six presets come with the command, so there is something to start from. They are listed by sharpen list as built-in, work in any project without creating a file, and cannot be edited or deleted. To change one, copy it to the project from the sharpen list; a preset of your own with the same name replaces the built-in one.

PresetSettingsFor
web-lightscreen, lowa light touch for web photos
web-crispscreen, standardeveryday sharpening for the web
web-detailscreen, high, radius 0.8fine detail of large web photos
thumbnailscreen, standard, radius 0.4, flat 0.6, jagged 2.5small previews: a fine radius, gentle on flat areas
print-mattematte, standardmatte paper
print-glossyglossy, standardglossy paper

Your own presets ​

A preset is a small JSON file named after the preset, with the same fields as sharpen, and an optional description shown in the list:

json
{ "description": "Web photos", "for": "screen", "radius": 0.8, "flat": 0.4 }

Your presets are kept in .mediatoolz/image-batch/sharpen/ of the project (looked up from the working folder up to the repository root) or in ~/.mediatoolz/image-batch/sharpen/ for all projects. A project preset hides a global preset with the same name, and both hide a built-in one.

Use a preset, built-in or your own, by its name:

json
{
  "outputs": [
    { "id": "web", "longEdge": [1600], "sharpen": "web-crisp" },
    {
      "id": "print",
      "longEdge": [3000],
      "sharpen": { "preset": "web-crisp", "for": "glossy", "amount": "high" }
    }
  ]
}
bash
mediatoolz image-batch ./photos -o ./out --long 1600 --sharpen web-crisp

Fields written next to preset replace the same fields of the preset. Across the layers of a config (defaults, a recipe preset, the recipe) and the flags, sharpening is combined field by field: a later layer changes only what it names. A layer that names a different preset starts from that preset and drops what the earlier layers set. A preset that does not exist stops the command before anything is written and lists the presets that do. The results are redone when the values behind a preset change, and left alone when only its name or description does.

File names ​

The default name follows the size method: {dir}/{name}-{width}w.{format} for widths, -{height}h for heights, -{size} for size, -{long}l for longEdge, -{short}s for shortEdge, -{mp}mp for megapixels, -{percent}pct for percent, @{scale}x for a bare scale, and {dir}/{name}.{format} otherwise. When a recipe mixes methods, each result gets the name of its own method. Set name in a recipe, or --name for one run, to choose your own. With --replace the name is always the file's own.

  • {name} — the source file name without its extension.
  • {ext} — the source extension, without the dot.
  • {orig} — the full source file name, hero.png.
  • {dir} — the source's folder relative to its input folder; empty for files in the root (the / after it disappears). --flat makes it empty always.
  • {format} — the output format's extension.
  • {width}, {height}, {size} — the real dimensions of the result, {size} as 800x450.
  • {long}, {short} — the real longer and shorter side of the result.
  • {mp} — megapixels: the requested value for megapixels, otherwise the real area (0.06).
  • {percent} — the percentage: the requested value for percent, otherwise the real share of the source width.
  • {scale} — the multiplier from scale, such as 2.
  • {index}, {index:3} — the source's number in this run, from 1; :3 pads with zeros (007).
  • {hash}, {hash:6} — the start of a hash of the source's content (8 characters by default, up to 40).
  • {date} — today, 2026-10-07.

A template is relative to the output folder. A leading /, a drive letter, a .., an unknown {variable}, or a character not allowed in file names is an error before anything is written. Two outputs that would get the same name are an error too, and the message lists them; when the sources differ only by extension (ui.png and ui.webp), it suggests adding {ext}.

Codec options ​

Each format has the options that matter for size and quality. Set them in a config (defaults.codecs, a preset, a recipe's formats map or codecs) or from the command line.

  • jpg — quality, mozjpeg (better compression at the same quality, slower), progressive, chromaSubsampling (4:2:0 or 4:4:4, the latter for text and sharp edges).
  • png — compressionLevel (0–9), palette with colors (2–256), quality and dither (an 8-bit palette: the biggest saving, with loss), effort, progressive.
  • webp — quality, lossless, nearLossless, alphaQuality, effort (0–6), smartSubsample, preset (photo, picture, drawing, icon, text), minSize.
  • avif — quality, lossless, effort (0–9), chromaSubsampling, bitdepth (8, 10, 12), tune.
  • gif — colors (2–256), effort, dither, loop, delay, reuse. An animated gif stays animated.
  • tiff — compression (jpeg, deflate, lzw, packbits, webp, zstd, …), quality, predictor, bitdepth, xres, yres.
  • jp2 — quality, lossless, chromaSubsampling. Available only when sharp's libvips was built with OpenJPEG; the prebuilt one is not, and the command says so before writing anything.
  • heif — quality, lossless, effort, compression (av1 by default, or hevc), chromaSubsampling.

The same quality number means different things in different codecs, so a level is often the better choice: it expands to a number per format (for jpg low/medium/high/best are 65/78/85/92, for webp 60/75/82/90, for avif 40/50/60/75). A bare number in quality applies to every format that has a quality; a map sets one per format: { "jpg": 82, "webp": "high" }.

Options layer from weak to strong: built-in defaults → defaults → preset → recipe (the quality shortcut, then the formats map and codecs) → command-line flags. The built-in defaults are: jpg quality 82 with mozjpeg and progressive; webp quality 80, effort 4; avif quality 55, effort 4; png compressionLevel 9, lossless; heif compression av1. Lossy options are never switched on silently: a png is quantized only if you set palette.

An option a format does not have (lossless on jpg), an unknown name, or a value out of range is an error with a hint (Did you mean "mozjpeg"?), found when the config is read.

From the command line: -q 82, -q high, -q jpg=82,webp=high, and --codec jpg.mozjpeg=false --codec png.palette=true (repeatable) for everything else.

Limiting the file size ​

maxBytes (--max-size 200KB) caps the size of every result: "200KB", "1.5MB" or a number of bytes, at least 1 KB. A file that already fits is written as it is. A bigger one is encoded again at a lower quality, as high as the limit allows; the search takes a handful of encodes per file. It works for the formats that have a quality — jpg, webp, avif, heif, jp2 and a png with palette; for any other format the command stops before writing and says so.

If the limit cannot be reached even at the lowest quality, the file is not written; it is reported together with the smallest size that was possible, and the exit code is 1 — ask for a smaller size as well (--long 1200). The limit is part of the settings: changing it makes the files again, and with --replace it works like any other setting.

When a result already exists ​

This applies to --out and --beside; with --replace the file in question is the source, and the rules above apply.

A result that an earlier run made from the same source with the same settings is left alone and counted as up to date; that is what makes a second run fast. The record of what was made is kept in ~/.mediatoolz/cache/image-batch/.

Anything else already in the place is a conflict: a file the command did not make, or one it made from a source or with settings that have since changed.

  • In a terminal the first conflict asks what to do: y overwrite this one, a overwrite this and all the following, s skip this one, n skip all the following, q quit. "All" stays in force until the end of the run.
  • Without a terminal nothing can be asked: the conflicting files are skipped and listed, and the exit code is 1 so that CI notices.
  • --overwrite replaces them, --skip-existing skips them and exits 0, --no-overwrite treats any conflict as a failure.
  • --force regenerates everything, ignoring the record.

A source file is never overwritten by --out or --beside, even if a template points at it.

Hashes and the manifest ​

--hazehash, --blurhash, --thumbhash and --dominant-color (or placeholders in a config) compute a placeholder of every source; --budget <bytes> sets the size of a hazehash (7–48, 28 by default). They are written into the file named by --emit:

bash
mediatoolz image-batch img -r -o out -c web --hazehash --budget 20 --emit out/images.json --key-prefix /images/

The manifest has sources (each source's size, format, hashes and the list of its outputs) and placeholders: one entry per produced file, keyed by its path with --key-prefix in front, holding the hashes and the file's own width and height — the shape a placeholders manifest takes. Outputs that were already up to date are listed too.

Managing configs ​

bash
mediatoolz image-batch init                       # create a config with a few questions
mediatoolz image-batch config                     # pick a config: apply, show, edit, copy, delete
mediatoolz image-batch config list               # just the table (--json for scripts)
mediatoolz image-batch config show web            # what a config produces
mediatoolz image-batch config rm web --yes        # delete (a .bak copy stays unless --force)

init asks for the name, whether to save it for the project or for every project, how the size is chosen (a list of the size methods: width, height, box, long side, short side, megapixels, percentage or the original size, followed by the values; a box also asks how the image fills it and whether to turn it around for the other orientation), the formats (a checklist with avif, webp and jpg ticked: space ticks, Enter confirms), a quality level, the file name template (the variables are listed; Enter keeps the default for the size method, or type your own), an optional thumbnail recipe an optional sharpening (a list of the targets and your saved presets, then the amount) and an optional hazehash. With --name, a size flag (-w, --heights, --size with --fit and --match-orientation, --long, --short, --megapixels, --percent), -f, -q, --file-name, --sharpen and the other --sharpen-* flags, --global, --thumbnail and --hazehash it asks nothing; --file-name <template> sets the file name template of the main recipe.

Configs are handled by two commands: init creates a config (config new does the same), config manages the existing ones. Sharpening presets have their own command, sharpen.

config in a terminal opens a list of every config found, with where it lives and how many recipes it has. Pick one to open a menu:

  • Apply to a folder asks for the input and output folders and runs it.
  • Show prints each recipe: sizes, formats, fit, the file name template.
  • Edit opens a list of the recipes, rules, defaults and placeholders; open a recipe to switch its size method (the first entry; the old method is replaced and the current values are the starting text) or to change one field at a time (an empty answer removes the field), and add or remove recipes; a new recipe starts by asking for its size method. Every change is checked against the whole config before it is saved; an invalid one is refused and the old value stays. The file keeps its indentation.
  • Duplicate, Rename, Copy to global (or to the project), Delete (with a confirmation; a .bak copy is kept).

Only .json configs can be edited this way.

Options ​

[paths...] ​

Image files, folders and globs. With none, the current folder.

-c, --config <name|file> ​

The rules to apply: a config name, or a path to a config file.

-o, --out <dir> ​

Write the results into this folder. Asked for in a terminal when no place is given.

--beside ​

Write each result next to its source.

--replace ​

Rewrite each source file in place, in its own format. Asks for confirmation; needs --yes without a terminal.

--backup [dir], --no-backup ​

With --replace: where the originals are saved first (default .image-batch-backup/<date> in the current folder), or do not keep a copy.

--no-only-if-smaller ​

With --replace: replace a file even when the result is not smaller.

-r, --recursive ​

Also walk the subfolders of every folder given.

--flat ​

With --out: put every result directly into the output folder, without the source subfolders.

--include <glob>, --exclude <glob> ​

Keep only, or leave out, files whose path inside the input folder matches. Repeatable.

--ext <list> ​

Extensions picked up from folders.

--ignore <glob>, --no-respect-gitignore ​

Extra ignore patterns, and whether the project's .gitignore is honored.

--files-from <file> ​

More paths, one per line (- is stdin, # starts a comment).

-w, --widths <list>, --heights <list> ​

Output widths or heights in pixels, comma-separated. With --replace they act as a maximum.

--long <list>, --short <list> ​

Size by the long or the short side in pixels, comma-separated, whatever the orientation. With --replace they act as a maximum.

--megapixels <list>, --percent <list> ​

Size by area in megapixels (2,0.5) or as a percentage of the source (50,25), comma-separated.

--match-orientation ​

Turn a size box around for images of the other orientation.

--no-resize ​

Disable the sizes of the config for this run: every size it sets (widths, size, longEdge, percent and the rest, as well as scale) is ignored, so only the format and the other settings apply, and the default file name has no size in it. A run without a config or a size flag does not resize anyway. It cannot be combined with a size flag.

--max-size <size> ​

Limit every file to this size, such as 200KB or 1.5MB, by lowering the quality as far as needed.

--sharpen <preset> ​

Sharpen the results with a saved sharpening preset.

--sharpen-for <target> ​

Sharpen the results for screen, matte or glossy. See Sharpening.

--sharpen-amount <amount> ​

Sharpening amount: low, standard or high.

--sharpen-radius <sigma>, --sharpen-flat <n>, --sharpen-jagged <n>, --sharpen-threshold <n> ​

The fine settings of the sharpening. Each replaces the value the table (or the preset) gives; a value outside its range exits with code 2.

-f, --formats <list> ​

Output formats, comma-separated.

-q, --quality <n|level|fmt=n,…> ​

Quality: a number, a level, or a value per format.

--codec <fmt.option=value> ​

One codec option, such as jpg.mozjpeg=true. Repeatable.

--fit <mode> ​

cover, contain, inside, outside or fill.

--name <template> ​

The file name template.

--flatten [color] ​

Put transparency on a solid background, white unless a color is given.

--hazehash, --blurhash, --thumbhash, --dominant-color ​

Compute these for every source; the result goes into --emit.

--budget <bytes> ​

The size of a hazehash, 7–48.

--emit <file>, --key-prefix <text> ​

Write the JSON manifest, and put this text in front of the paths in its placeholders.

-i, --select, --list ​

Pick files from a list, or only print the files.

--overwrite, --no-overwrite, --skip-existing, --force ​

What to do with results that already exist; see above.

--cache [file], --no-cache ​

Where the record of what was already made lives, or do not use it.

--concurrency <n> ​

Images processed in parallel, 4 by default.

--max-pixels <n> ​

Refuse images with more pixels than this (0 removes the limit).

--dry-run ​

Write nothing; show what would be done, and which files would be overwritten or replaced.

-y, --yes ​

Agree without asking: install the missing sharp library, confirm --replace.

--json, --quiet, --verbose, --plain, --color ​

Machine-readable output; silence on success; list every produced file (a big run lists only the problems by default); no color; force color.

The restore command takes <dir>, --dry-run, --force, --yes, --json, --cwd, --plain and --color.

Exit codes ​

0 — everything was written, replaced, kept, already done, or skipped on request (--skip-existing). 1 — a file could not be read or written, a conflict was found with nobody to ask (or --no-overwrite), the run was stopped with q or declined at the --replace confirmation; for restore, a file was left alone, or its backup copy is missing or damaged. 2 — a usage error: a bad flag, config, template or option, a collision between outputs, or --replace without --yes outside a terminal, reported before anything is written.

The sharp library ​

The work is done by sharp, the same native library as in image-hash. It comes with mediatoolz; only when its binary is missing (see Image Hash) does the first run offer to install it into ~/.mediatoolz/deps, or do it right away with --yes.