From baa17b0fc7cfc664f60d8e174fbf7ed0f5823ed0 Mon Sep 17 00:00:00 2001 From: Matthew Jackson Date: Sat, 14 Mar 2026 10:01:42 -0700 Subject: [PATCH] Add PDF-to-JPEG support in web UI and /convert/pdf endpoint Web UI now accepts both images and PDFs. Images go through /strip as before; PDFs go through new /convert/pdf endpoint which renders all pages via pdftoppm and returns base64 JPEGs. Also updates page title and layout. Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 6 +- server.js | 180 +++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 161 insertions(+), 25 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index bc9aef2..215bf7c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,9 +11,11 @@ Image/PDF-to-JPEG conversion microservice. Single-file Node.js/Express app (`ser ## Endpoints +- `GET /` — web UI for stripping photo metadata (drag-and-drop, uses `/strip`) - `GET /health` — health check -- `POST /convert` — image or first PDF page → JPEG -- `POST /convert/pdf` — all PDF pages → ZIP of JPEGs +- `POST /strip` — strip metadata from image, return clean JPEG + removed metadata (JSON or raw) +- `POST /convert` — image or first PDF page → JPEG (resize/quality via headers) +- `POST /convert/pdf` — all PDF pages → JSON array of base64 JPEGs ## Auth diff --git a/server.js b/server.js index abc00a6..333b342 100644 --- a/server.js +++ b/server.js @@ -251,6 +251,41 @@ async function pdfFirstPageToJpeg(input, opts) { } } +async function pdfAllPagesToJpegs(input, opts) { + const id = randomUUID(); + const pdf = `/tmp/${id}.pdf`; + const prefix = `/tmp/${id}`; + + try { + await fs.writeFile(pdf, input); + + await execFilePromise( + "pdftoppm", + ["-jpeg", "-r", String(opts.pdfDpi), pdf, prefix], + DEFAULT_REQ_TIMEOUT_PDF_MS + ); + + // pdftoppm writes -01.jpg, -02.jpg, etc. + const dir = await fs.readdir("/tmp"); + const pages = dir + .filter((f) => f.startsWith(`${id}-`) && f.endsWith(".jpg")) + .sort(); + + const results = []; + for (const page of pages) { + const pagePath = `/tmp/${page}`; + const buf = await fs.readFile(pagePath); + const jpeg = await normalizeForVision(buf, opts); + results.push(jpeg); + await safeUnlink(pagePath); + } + + return results; + } finally { + await safeUnlink(pdf); + } +} + /* ------------------------------------------------------------------ */ /* Single-flight per machine (ONLY for /convert) */ /* ------------------------------------------------------------------ */ @@ -286,14 +321,13 @@ app.get("/", (_req, res) => { -PostConvert – Strip Metadata +PostConvert