I can never remember ImageMagick’s flags. Something like this:
magick mogrify -path out -format webp -quality 80 -resize 1600x1600\> -unsharp 0x1 photos/*.jpg
Enter fullscreen mode Exit fullscreen mode
I don’t think I’ve ever typed that correctly from memory. So I made photu, where the same pipeline is just normal shell pipes:
photu read "photos/*.jpg" | photu resize 1600 | photu sharpen | photu write "out/{name}.webp" quality=80
Enter fullscreen mode Exit fullscreen mode
You can chain as many of these as you want:
photu read "photos/*.jpg" \
| photu resize 1600 \
| photu crop 1200x630 gravity=center \
| photu rotate -3 background=white \
| photu adjust saturation=1.3 hue=15 \
| photu sharpen \
| photu overlay logo.png gravity=southeast opacity=0.35 \
| photu pad 24 color=white \
| photu write "social/{name}.webp" quality=82
Enter fullscreen mode Exit fullscreen mode
The pipe between stages isn’t actually carrying images though. Each command just adds one op to a small JSON plan and passes it along, and only the last stage (write) hands the whole thing to libvips and runs it. So it doesn’t matter how many stages you chain, each image still only gets decoded and encoded once, at the end.
You can see the plan mid-pipeline with photu explain:
$ photu read "photos/*.jpg" | photu resize 800x600 fit=cover | photu explain
photu plan (protocol 1)
files (50):
...
ops (1):
1. resize width=800 height=600 fit="cover" upscale=false
Enter fullscreen mode Exit fullscreen mode
Since it’s just text, this also works unchanged in PowerShell and cmd, not just bash/zsh.
Is this just a libvips wrapper? Pretty much, yeah. libvips does the actual pixel work and gets all the credit for the speed. What photu adds is that the vips CLI runs one operation per process, so chaining means writing intermediate files or piping raw pixels yourself – photu just passes a plan instead and fuses the whole thing. Also adds globs, URLs as inputs, output templates, that kind of glue. If vipsthumbnail already covers what you need, use that.
On speed: I grabbed 50 public-domain images from the Met Museum (1,130px to 4,000px, 116MB of JPEGs total), resized them to 800px wide and wrote out as WebP q80, timed with hyperfine:
time photu 2.0 s ImageMagick Q8 with-define jpeg:size=1600x1200
12.6 s
ImageMagick Q8
22.2 s
ImageMagick Q16-HDRI (the default download)
23.0 s
Most of that is just libvips being libvips – it decodes JPEGs at reduced scale when it can, and threads well. photu’s number here still includes spinning up four separate Node processes.
npm i -g photu
Enter fullscreen mode Exit fullscreen mode
Needs Node 22+, nothing to compile since libvips ships prebuilt with the sharp dependency.
There’s a WASM build in the browser too, at tryphotu.vercel.app, if you want to try it without installing anything – images stay local, nothing gets uploaded anywhere.
Repo’s at github.com/u84u/photu.
Show HN thread
답글 남기기