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.
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:
mediatoolz image-batch img -r -o out -w 300,600 -f webp,jpg -q high2 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:
mediatoolz image-batch ./src/images -r -o ./public/images -w 400,800,1200 -f avif,webp,jpg -q highSize every image by its long side, whatever the orientation, and keep each file under 200 KB:
mediatoolz image-batch ./photos -r -o ./out --long 1600 -f webp,jpg --max-size 200KBOnly change the format — the size stays as it is, nothing is resized or sharpened:
mediatoolz image-batch ./photos -r -o ./out -f webpThe same set from a saved config, ticking the files from a list first:
mediatoolz image-batch ./src/images -r -o ./public/images -c web --selectShrink oversized originals to at most 1600 px wide, in place, after a preview:
mediatoolz image-batch ./assets -r --replace -w 1600 --dry-run
mediatoolz image-batch ./assets -r --replace -w 1600Where 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. Leaveformatsout, or useoriginal. A file name template has no effect, and--nameis 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-smallerreplaces 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;
--forceignores 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 ajournal.jsonthere.--backup <dir>chooses another place,--no-backupturns 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-runencodes 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:
mediatoolz image-batch photos --replace -w 600 --dry-run3 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:
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-233706A second identical run changes nothing:
0 replaced · 3 already donePutting the originals back
mediatoolz image-batch restore .image-batch-backup/20261007-233706restore 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 asphotos/**/*.jpg. With none, the current folder.-r, --recursivewalks 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, --selectshows 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 orj/kto move, space to tick,aall,nnone,iinvert,/to filter by text, Enter to confirm,qto cancel. It needs a real terminal.--listonly 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.
{
"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 } }
}outputsis a list of recipes. Each recipe says what to make from every source; see Recipes.presetsare 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.defaultsapply to every recipe.matchpicks recipes by the path of the source inside the input folder. The first rule whoseglobmatches replacesoutputsfor that file; a file that matches no rule (and has nooutputs) is left alone and listed in the summary.placeholdersnames the hashes to compute for the manifest.nameanddescriptionare 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 tofit.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— defaultfalse. Withsize, 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@1xand@2x; formegapixelsthe 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, ororiginal(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 withbackground),fill(stretch).position— which partcoverkeeps:centre,north,top,right bottom,entropy,attention, …background— the letterbox color;#rrggbb,#rrggbbaaor a color name.withoutEnlargement— defaulttrue: 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— defaulttrue: turn the image the way its EXIF orientation says.rotate—90,180or270.flip,flop— mirror vertically or horizontally.grayscale,blur(sigma).sharpen— sharpening after the resize: the name of a saved preset, or an object withfor,amount,radius,flat,jaggedandthreshold. 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, orkeep-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 (
sizewithfit) — the image fits inside the box (inside, the default) or fills it exactly (cover,contain,fill). WithmatchOrientation, 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.
{
"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) orglossy(the strongest, for glossy paper).amount—low,standard(default) orhigh.
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 from0.000001to10. The table gives0.6forscreen,1formatteand1.4forglossy.flat— how strongly flat and smooth areas are sharpened,0to1000000. The table gives0.5(low),1(standard) or1.8(high). Lower it to keep skies and skin clean.jagged— how strongly the sharp edges are sharpened,0to1000000. The table gives1.5,3or5.threshold— the contrast that separates a flat area from an edge,0to1000000. Without it sharp uses its own value,2.
{ "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.
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.
| Preset | Settings | For |
|---|---|---|
web-light | screen, low | a light touch for web photos |
web-crisp | screen, standard | everyday sharpening for the web |
web-detail | screen, high, radius 0.8 | fine detail of large web photos |
thumbnail | screen, standard, radius 0.4, flat 0.6, jagged 2.5 | small previews: a fine radius, gentle on flat areas |
print-matte | matte, standard | matte paper |
print-glossy | glossy, standard | glossy 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:
{ "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:
{
"outputs": [
{ "id": "web", "longEdge": [1600], "sharpen": "web-crisp" },
{
"id": "print",
"longEdge": [3000],
"sharpen": { "preset": "web-crisp", "for": "glossy", "amount": "high" }
}
]
}mediatoolz image-batch ./photos -o ./out --long 1600 --sharpen web-crispFields 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).--flatmakes it empty always.{format}— the output format's extension.{width},{height},{size}— the real dimensions of the result,{size}as800x450.{long},{short}— the real longer and shorter side of the result.{mp}— megapixels: the requested value formegapixels, otherwise the real area (0.06).{percent}— the percentage: the requested value forpercent, otherwise the real share of the source width.{scale}— the multiplier fromscale, such as2.{index},{index:3}— the source's number in this run, from 1;:3pads 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:0or4:4:4, the latter for text and sharp edges).png—compressionLevel(0–9),palettewithcolors(2–256),qualityanddither(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(av1by default, orhevc),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:
yoverwrite this one,aoverwrite this and all the following,sskip this one,nskip all the following,qquit. "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.
--overwritereplaces them,--skip-existingskips them and exits 0,--no-overwritetreats any conflict as a failure.--forceregenerates 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:
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
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
.bakcopy 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.