Fix HEIC conversion + flatten ZIP layout + web-ready sizing + dep bumps#16
Merged
Conversation
HEIC uploads converted to an empty ZIP with a "complete" status and no error. Two causes: 1. The Docker image lacked libheif, so libvips had no HEIF loader and every HEIC preset failed to decode. Move builder and runtime to Debian trixie (libheif 1.19 -- bookworm's 1.15 rejects iPhone HEICs with "Metadata not correctly assigned to image") and install libheif1 + libde265-0 in the runtime stage. The runtime libvips package is renamed libvips42t64 on trixie. 2. When every preset for a file failed, runJob still called Finish() and served a valid-but-empty ZIP. Fail the job loudly when it produced zero outputs so the frontend surfaces the terminal error instead.
Multi-image jobs namespaced each source into its own folder (IMG_0274/convert_jpeg.jpg), which made files awkward to handle. Write plain image outputs flat at the ZIP root as "<source>_<preset>.<ext>" (e.g. IMG_0274_convert_jpeg.jpg) -- source plus preset keeps every entry unique without folders, so the per-source namespacing is gone. Favicon packs stay grouped in a "<source>_favicon/" folder since a favicon set is a drop-in unit; PDF bundles are unchanged.
Browsers can't render HEIC/HEIF in an <img>, so the pre-upload preview showed a broken image. Detect those formats client-side and render a placeholder icon tile (with the filename and size) instead of a blob URL; the file still uploads and converts server-side. Hide the focal "Adjust crop" affordance for them since it needs a visible image, and key the preview list on a stable id now that url can be null.
The trixie migration left AVIF encoding broken: on trixie libheif's codecs are plugins and the encoders are only Recommends, so with --no-install-recommends the AV1 encoder was never installed. Every AVIF output (convert_avif, website_avif, srcset AVIF members) failed with "heifsave: Unsupported compression" and produced no file. Add libheif-plugin-aomenc to the runtime stage. Verified against a HEIC source: convert_avif now -82%, website_avif -91% vs the original.
resolveFormat had no HEIC/HEIF case, so compress_* presets (FormatAuto, "keep the source format") fell through to JPEG for iPhone photos. HEIC is an HEVC-class codec, so re-encoding to JPEG actually grew the file (measured +111% on a sample) -- which is why compressing HEIC barely helped. Map HEIF inputs to WebP, the most efficient format we can write and serve broadly. compress_* on HEIC now lands at -51% to -80%. AVIF stays available explicitly via convert_avif.
The website_* presets kept the source resolution, so a 24MP iPhone HEIC came out as a ~2MB WebP at 5712x4284 -- far too large for web publishing and a Core Web Vitals failure. Resolution, not encoding, was the issue. Add a MaxDim field to Preset and a FitsWithin() mode: a downscale-only, aspect-preserving fit-inside (vips InterestingNone + SizeDown), distinct from Width/Height crop-to-fill. Set MaxDim=2560 on website_webp/avif and jpeg/png_original. SVG keeps its dedicated density path (checked first). Measured on a real 24.5MP HEIC: website_webp 2.3MB -> 0.7MB (-69%), output 2560x1920, aspect preserved, no crop; images already under 2560px pass through un-enlarged. Frontend dims/help updated to say "Max 2560px".
Incorporates Dependabot PR #14.
Incorporates Dependabot PR #15 (filed as an esbuild advisory bump, but the actual change is the build toolchain): @sveltejs/vite-plugin-svelte ^5 -> ^7.1.2 and vite ^6 -> ^8.0.16, which drops the flagged transitive esbuild. Dev-only dependencies; no runtime/app code change. Verified: clean npm install resolves with 0 vulnerabilities, npm ci is in sync (used by the Docker frontend stage), and npm run build succeeds.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes HEIC handling end-to-end and improves web-output quality, plus folds in two pending Dependabot updates. Originally a single "HEIC converts to an empty ZIP" bug report; investigation surfaced several related issues, each fixed and verified against the production container.
HEIC pipeline
Recommends, so with--no-install-recommendsno AV1 encoder was installed — every AVIF write failed withheifsave: Unsupported compression. Addedlibheif-plugin-aomenc. (convert_avif/website_avif/srcset AVIF members now work.)compress_*(FormatAuto) had no HEIC case inresolveFormat, so it fell through to JPEG — which actually grew the file (HEIC is HEVC-class; JPEG is a downgrade). Map HEIF inputs to WebP (−51% to −80% on a sample). AVIF stays available viaconvert_avif.Web-output quality
MaxDim/FitsWithin()mode onPreset. Verified on real files:website_webp2.3 MB → 0.7 MB (−69%), output 2560×1920; sub-2560px sources pass through untouched.UI
<img>, so the pre-upload preview showed a broken image. Now shows a clean placeholder tile (filename + size); the file still uploads/converts. Focal "Adjust crop" hidden for HEIC (needs a visible image).ZIP layout
IMG_0274/convert_jpeg.jpg). Now flat at the ZIP root as<source>_<preset>.<ext>— unique without folders. Favicon packs stay grouped in a<source>_favicon/folder (a favicon set is a drop-in unit); PDF bundles unchanged.Dependencies (folds in open Dependabot PRs)
golang.org/x/sync0.20.0 → 0.21.0. Closes Bump golang.org/x/sync from 0.20.0 to 0.21.0 #14.@sveltejs/vite-plugin-svelte^5 → ^7 (drops the flagged transitive esbuild; dev-only). Closes Bump esbuild from 0.25.12 to removed in /frontend in the npm_and_yarn group across 1 directory #15.Verification
All checks below were run, not assumed:
go build ./... && go vet ./... && go test ./...pass (stub mode).npm install(0 vulnerabilities),npm ciin sync,npm run buildsucceeds on Vite 8./healthand the SPA.compress_*→ WebP −51–80%,convert_avif−82%,website_*capped to 2560px (−69%), and an undecodable input now returns an error + 404 (no empty ZIP).