runtime: edit a batch of pictures with libvips, shared out across the pool #730

Merged
admin merged 1 commits from image-edits into dev 2026-10-25 05:35:17 +00:00
Owner

Closes #729. Part of #584: the catalog's "Batch image edits".

A new fixed-image container workload, images, built the same way as ocr and prep. It runs libvips' vipsthumbnail from Alpine 3.20's vips-tools (libvips 8.15).

What a job does

Each picture is:

  • turned upright by its EXIF orientation;
  • fitted within width and/or height, keeping its shape. It is only shrunk unless enlarge is set;
  • saved as jpeg, png, webp, tiff or gif, or in its own format (same, the default). JPEG and WebP use quality, 85 by default;
  • stripped of its metadata unless keep_metadata is set. A phone's photo can say where it was taken.

The pictures are shared out across the pool in contiguous runs, as prep does with its systems. Each task is given only its own pictures. There are never more tasks than pictures, and at least enough that no task has more than 16. A job can have up to 256 pictures.

The result lists each picture with its CID, name, format, size and bytes, or with an error and libvips' last lines when it could not be read. It also has a files map by name (image-000.webp and so on) and a zip of them all. A broken picture is listed on its own and does not fail the job; the job fails only if no picture is made.

What is trusted

  • Split: the coordinator reads each file's first 12 bytes and refuses anything that is not PNG, JPEG, WebP, TIFF or GIF, or is over 256 MiB.
  • Container: libvips runs with VIPS_BLOCK_UNTRUSTED=1, so its loaders for SVG, PDF, FITS and the like stay closed whatever a file is called. It also runs on one thread.
  • Execute: the worker rebuilds the size from settings it checks itself. It runs only its own image and script, and refuses any input except the job's pictures by their fixed names.
  • Aggregate: it takes only summary.json and the files named from each picture's index and format. It checks that every picture was edited once and in order, with the job's settings, and that each one fits the box and begins as its format does. The names in files and the zip are made by the coordinator, never taken from a worker.
  • Varies is true. On one machine libvips gives the same bytes, and the test checks this across splits. But each worker builds the image from Alpine's packages on the day, and libvips picks its vector code by processor, so comparing bytes across workers would fail honest ones.

Tests

  • Unit tests: cover splitting, its defaults, and every refusal; that the image is built only where it is missing; that a tampered payload is not run; that results are put together in order; and every check Aggregate makes. Every statement in images.go is covered. go test -race -count=5 passes.
  • TestRealPicturesAreEdited (realimages, about 5 s once the image is built): it uses an 800×600 JPEG made in the test, with an EXIF block saying "turn a quarter clockwise" and naming a camera, plus a 40×30 PNG and a broken JPEG.
    • Fitted to 300×300 as WebP, the photo comes out upright at 225×300 with its EXIF gone. The PNG stays 40×30, and the broken one is listed with VipsJpeg: Premature end of JPEG file.
    • With enlarge and keep_metadata, both keep their formats at 90×120 and 120×90, and the photo keeps its EXIF.
    • Split into two tasks, the job gives the same CIDs as in one.
  • Desktop: the Run page card has an icon and a title and description in all seven languages, and the output-names test shows each edited picture saved under its own name.

Docs

  • apps/sisyphusd/README.md: a new "Batch image edits" section, plus the workload list, the list of workloads with forms, and the hardened-worker list.
  • docs/security-model.md: what images trusts, and its place in the non-determinism and hardened lists. The hardened list there was also missing traj and prep, which the README already names.
  • docs/user-guide.md and docs/desktop-backend.md: the new card and its parameters.

Upscaling with Real-ESRGAN is left for later: it needs a graphics card and a model download.

Closes #729. Part of #584: the catalog's "Batch image edits". A new fixed-image container workload, `images`, built the same way as `ocr` and `prep`. It runs libvips' `vipsthumbnail` from Alpine 3.20's `vips-tools` (libvips 8.15). ## What a job does Each picture is: - turned upright by its EXIF orientation; - fitted within `width` and/or `height`, keeping its shape. It is only shrunk unless `enlarge` is set; - saved as `jpeg`, `png`, `webp`, `tiff` or `gif`, or in its own format (`same`, the default). JPEG and WebP use `quality`, 85 by default; - stripped of its metadata unless `keep_metadata` is set. A phone's photo can say where it was taken. The pictures are shared out across the pool in contiguous runs, as `prep` does with its systems. Each task is given only its own pictures. There are never more tasks than pictures, and at least enough that no task has more than 16. A job can have up to 256 pictures. The result lists each picture with its CID, name, format, size and bytes, or with an `error` and libvips' last lines when it could not be read. It also has a `files` map by name (`image-000.webp` and so on) and a zip of them all. A broken picture is listed on its own and does not fail the job; the job fails only if no picture is made. ## What is trusted - **Split:** the coordinator reads each file's first 12 bytes and refuses anything that is not PNG, JPEG, WebP, TIFF or GIF, or is over 256 MiB. - **Container:** libvips runs with `VIPS_BLOCK_UNTRUSTED=1`, so its loaders for SVG, PDF, FITS and the like stay closed whatever a file is called. It also runs on one thread. - **Execute:** the worker rebuilds the size from settings it checks itself. It runs only its own image and script, and refuses any input except the job's pictures by their fixed names. - **Aggregate:** it takes only `summary.json` and the files named from each picture's index and format. It checks that every picture was edited once and in order, with the job's settings, and that each one fits the box and begins as its format does. The names in `files` and the zip are made by the coordinator, never taken from a worker. - `Varies` is true. On one machine libvips gives the same bytes, and the test checks this across splits. But each worker builds the image from Alpine's packages on the day, and libvips picks its vector code by processor, so comparing bytes across workers would fail honest ones. ## Tests - **Unit tests:** cover splitting, its defaults, and every refusal; that the image is built only where it is missing; that a tampered payload is not run; that results are put together in order; and every check Aggregate makes. Every statement in `images.go` is covered. `go test -race -count=5` passes. - **`TestRealPicturesAreEdited` (realimages, about 5 s once the image is built):** it uses an 800×600 JPEG made in the test, with an EXIF block saying "turn a quarter clockwise" and naming a camera, plus a 40×30 PNG and a broken JPEG. - Fitted to 300×300 as WebP, the photo comes out upright at 225×300 with its EXIF gone. The PNG stays 40×30, and the broken one is listed with `VipsJpeg: Premature end of JPEG file`. - With `enlarge` and `keep_metadata`, both keep their formats at 90×120 and 120×90, and the photo keeps its EXIF. - Split into two tasks, the job gives the same CIDs as in one. - **Desktop:** the Run page card has an icon and a title and description in all seven languages, and the output-names test shows each edited picture saved under its own name. ## Docs - `apps/sisyphusd/README.md`: a new "Batch image edits" section, plus the workload list, the list of workloads with forms, and the hardened-worker list. - `docs/security-model.md`: what `images` trusts, and its place in the non-determinism and hardened lists. The hardened list there was also missing `traj` and `prep`, which the README already names. - `docs/user-guide.md` and `docs/desktop-backend.md`: the new card and its parameters. Upscaling with Real-ESRGAN is left for later: it needs a graphics card and a model download.
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Sisyphus/Sisyphus#730