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>
4.4 KiB
The pipeline
What av-club is meant to do, and — just as importantly — what has not been decided yet.
None of this is built. This document exists so the decisions get made deliberately rather than being settled by whatever gets written first. Where something is open, it says so.
The problem
Someone uploads a file. It might be:
- a 12 MB photo straight off a phone, 4000px wide, in HEIC
- a 4K video in a codec Safari will not play
- a JPEG carrying the GPS coordinates of the room it was taken in
- a file named
photo.jpgthat is actually a PNG, or a PDF, or nothing at all - something enormous, uploaded by accident
What a page needs is none of those: a few sizes in a modern format, a poster frame for video, dimensions known ahead of time so layout does not shift, and nothing embedded that the person who uploaded it did not mean to publish.
What it should do
1. Identify, do not trust
Establish what a file actually is by inspecting it, never from its extension or the
Content-Type a browser attached. A rejected upload should say something a non-technical person can
act on, not a MIME type.
2. Strip what should not travel
GPS coordinates, camera serial numbers, timestamps, editing history. sharp drops these by default
on re-encode — see docs/TOOLING.md. Video metadata needs the same treatment and is not free.
3. Probe, and keep the numbers
Width, height, duration, codec. These get stored, because knowing an image's aspect ratio before it loads is what lets a page reserve the space and not shift as it arrives.
4. Derive
Images: a small number of widths, in a modern format, never upscaled past the original. Video: something every browser plays, plus a poster frame.
5. Name outputs so they can be cached forever
Content-addressed or otherwise unique, so a derivative can be served immutable honestly and
replacing an image never serves a stale one from a cache.
What is NOT decided
These are real forks. Picking one by accident is how a project ends up with an answer nobody chose.
How a consumer talks to this
| Option | Consequence |
|---|---|
| A library the consumer imports | Simplest. Processing happens in the web server's process — a big video transcode blocks a worker and competes for CPU with page rendering. |
| A CLI the consumer shells out to | Clean isolation, no shared runtime. Every call pays process startup, and errors arrive as exit codes and stderr rather than as values. |
| A service the consumer calls | Scales and isolates properly. It is another thing to deploy, monitor and secure, which is real work for a small site. |
Whether processing is synchronous
Does an admin wait for a 4K video to transcode while staring at a spinner, or does the upload return immediately and the derivative appear later?
Asynchronous is better for them and means a job queue, a way to show "still processing", and a decision about what a page renders in the meantime. Synchronous is far simpler and is fine for images; it is not fine for video.
These two questions are related — a queue implies something long-lived, which pushes toward a service.
Where output goes
Not decided, and it interacts with the consumer's deployment: whether a deploy replaces the working
directory determines whether generated files can live inside it at all. This is currently an open
question in fofos/platform too.
The derivative ladder
Which widths, which formats, what quality. Deliberately unanswered — it should follow from a real design, and guessing produces sizes nobody uses and gaps where one was needed.
Externally hosted media
A consumer may store a URL to a video hosted elsewhere rather than a file. Whether av-club has anything to say about those — probing them, fetching poster frames, or ignoring them entirely — is not decided.
Principles worth keeping
The original is never destroyed. Derivatives are derivatives. A quality setting judged wrong in six months should be re-runnable, and it cannot be if the source was thrown away.
Rejections explain themselves. The person uploading is not a developer. "That file is 180 MB; the limit is 50 MB" is usable. "Unsupported media type" is not.
Verify by running, not by asserting. scripts/verify.sh is the existing example: it encodes a
video and reads it back rather than checking a version string. The same standard applies to the
processing code.