Media processing pulled out of the website that first needed it. ffmpeg, ImageMagick, libvips and exiftool are a real installation rather than an npm install, and encoder settings get revisited as browser support moves — neither of which belongs on the same clock as page templates and database schemas. No processing code yet. What is here is the tooling requirement, an installer, and docs/PIPELINE.md writing down what has NOT been decided — how a consumer talks to this, whether processing blocks an upload, where output goes — so those get chosen deliberately instead of by whatever gets written first. verify.sh checks by running: it encodes a video and probes the result against what ffmpeg was told to make, loads sharp under Bun specifically, and asserts a re-encode still strips GPS. That last one guards a real leak — if a future sharp changes its default, this fails a check rather than quietly publishing the coordinates of a client's living room. Confirmed non-vacuous by hiding ffmpeg and watching it exit 1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.8 KiB
Tooling
What is installed to process images and video, what each piece is for, and the failures that look like something else.
Bringing a machine up
./scripts/install.sh
Verified on Ubuntu 24.04. What it does, if you would rather do it by hand:
sudo apt-get install -y ffmpeg libimage-exiftool-perl imagemagick libvips-tools webp libavif-bin
bun install
./scripts/verify.sh
What each is for
| Tool | Verified at | Used for |
|---|---|---|
ffmpeg / ffprobe |
6.1.1 | Video and audio. Transcoding, extracting poster frames, and probing a file for duration, codec and dimensions rather than trusting what a browser claimed about it. |
sharp (bun dep) |
0.35.3, libvips 8.18.3 | The image path the code actually calls. In-process resize and re-encode to WebP/AVIF/JPEG, libvips-backed, so no process spawn per image. |
exiftool |
12.76 | Reading and stripping metadata. Client photos routinely carry GPS coordinates and camera serial numbers. |
imagemagick |
6.9.12 (convert, identify) |
Inspecting a file by hand when something looks wrong, and formats sharp does not cover. |
libvips-tools |
8.15.1 (vipsthumbnail) |
The CLI side of the same library sharp uses — for comparing behaviour outside the app. |
webp |
1.3.2 (cwebp, dwebp) |
WebP at the command line. |
libavif-bin |
1.0.4 (avifenc, avifdec) |
AVIF at the command line. |
The command-line encoders are for inspection and one-off work. Processing goes through sharp and
ffmpeg.
Two things that bite
Both of these fail in a way that looks like a broken installation and is not.
There is no magick command
Ubuntu 24.04 ships ImageMagick 6, which provides convert and identify. The single magick
entrypoint is ImageMagick 7. A script written against magick fails with "command not found" on
a machine that has ImageMagick fully installed and working.
Use convert and identify, or install IM7 deliberately and write down that you did.
sharp must be resolved from the repo
Run from anywhere else — bun -e in /tmp, a script executed from another directory — Bun resolves
sharp from its global install cache and lands on the wasm32 build, which is missing
@emnapi/runtime and dies with:
TypeError: sharp.libvipsVersion is not a function
The native linux-x64 binary is in this repo's node_modules; the process just has to be able to
see it. Anything using sharp runs with the repo as its working directory. scripts/verify.sh does
exactly this, and the reason is commented at the call site.
Metadata is stripped by default, and that is load-bearing
A sharp re-encode drops EXIF unless .withMetadata() is called explicitly.
Measured on a JPEG carrying injected GPS and camera tags: 34 tags before, 19 after, and the 19
survivors are filesystem and image-structure facts only — ImageWidth, MIMEType, FileSize and
similar. GPS latitude and longitude, Make and Model were all gone.
So the default is the safe one, and .withMetadata() is the call that publishes a client's
location. Do not add it without a reason. scripts/verify.sh asserts this on every run, so if a
future sharp version changes the default, it fails a check rather than quietly publishing
coordinates.
Behaviour worth remembering
sharp(...).resize(400) enlarges a smaller source. A 300×300 input comes out 400×400 and soft.
Anywhere a derivative size is requested will want withoutEnlargement: true, or small uploads get
upscaled into blur that nobody asked for.
Verifying
./scripts/verify.sh
Every check runs the tool and compares the result against what was asked for — a version string proves a binary exists, not that its encoders are present or that a native module loads. The script exits non-zero on failure, so it is usable in CI.