feat: add WebP + AVIF output encoding (pure-Go, no cgo) - #500
feat: add WebP + AVIF output encoding (pure-Go, no cgo)#500trungnsbkvn wants to merge 13 commits into
Conversation
Upstream imageproxy decodes WebP but only encodes JPEG/PNG/GIF/TIFF/BMP (issue willnorris#114). This fork adds WebP + AVIF encoders via gen2brain/webp and gen2brain/avif (libwebp/libaom compiled to WASM, run by wazero) so the single static binary — built with CGO_ENABLED=0, native Windows — can emit modern formats for luatsumienbac.vn's self-hosted /media resizer. - data.go: register webp/avif as parseable format options - transform.go: webp/avif encode cases + contentTypeForFormat() + defaults - imageproxy.go: set explicit Content-Type for known formats (Go's http.DetectContentType has no AVIF signature -> would 403 as octet-stream) - go.mod: + gen2brain/webp, gen2brain/avif (pure-Go WASM, no cgo) Verified: CGO_ENABLED=0 build + runtime smoke test emits valid image/avif (ftypavif) and image/webp (RIFF/WEBP). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build (native + cross-compile), run as a service (NSSM on Windows, systemd on Linux with hardening), reverse proxy (IIS URL Rewrite+ARR, nginx, Caddy — all stripping /img), disk + Cloudflare caching, optional HMAC signing, and a troubleshooting table. Flags/behaviour verified against cmd/imageproxy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Did you mean to include all the docs that look to be specific to your fork? I'm guess not? There's some discussion about webp in #388, and I have an experimental |
|
My bad, Not the docs. I was working with my Astro project and found your brilliant work. |
- build.ps1: build build/imageproxy.exe (CGO_ENABLED=0, windows/amd64) + smoke test - build/install-service.ps1, uninstall-service.ps1: NSSM service (PS 5.1+ compatible) - build/run.ps1: foreground run for testing - build/config.example.env: IMAGEPROXY_* env alternative to flags - build/README.md: copy-to-server deploy checklist -> DEPLOY.md - .gitignore: ignore built *.exe and *.log (scripts are tracked) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…bwebp, WebP-only) willnorris#393 is unmerged, WebP-only, and cgo-bound to C libwebp (breaks the single-binary / native-Windows / no-deps goal). Our gen2brain WASM approach stays CGO_ENABLED=0 and adds AVIF. Trade-off: slower encode, mitigated by cache. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nssm is an external dependency (and its site can be down). Make the single binary register ITSELF with the OS service manager instead: - cmd/imageproxy/service.go (new): -service install|uninstall|start|stop|restart (Windows SCM / systemd / launchd) + -logFile (service stdout is discarded). Foreground behaviour unchanged; graceful shutdown on Stop. - cmd/imageproxy/main.go: build the http.Server then runWithService(server). - go.mod: + github.com/kardianos/service (pure Go, keeps CGO_ENABLED=0). - build/install-service.ps1 + uninstall-service.ps1: rewritten to use the binary's -service (no nssm); install adds sc.exe crash-restart + auto-start. - DEPLOY.md / build/README.md / FORK_NOTES.md: nssm -> native service. Verified: CGO_ENABLED=0 windows build; foreground health-check OK; -service control wired (lists valid actions); -logFile captures startup + errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nssm is available again, so keep it as an option alongside the native self-install (native stays the default — no dependency). - install-service.ps1: -Method native|nssm (default native). nssm branch quotes paths, sets AppExit Restart + auto-start; native branch unchanged. - uninstall-service.ps1: generic sc.exe stop+delete — removes either kind. - DEPLOY.md / build/README.md: document both methods; FORK_NOTES clarifies nssm still works as an external wrapper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…poll)
The old trailing-& background job raced the health check ('(smoke test
skipped)') and could leak the process. Now starts hidden with -PassThru,
polls /health-check up to 6s, and stops that exact process.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- install-service.ps1: add optional -NssmPath so nssm need not be on PATH. - DEPLOY.md: acquisition (choco/scoop/manual zip -> win64\nssm.exe), by-hand configuration + what each nssm setting means, graceful-stop and run-as-account notes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ssm args Reported: 'nssm.exe : Can't open service!' aborted install-service.ps1 on a fresh install. Root cause: PowerShell 7.4+ throws on a native command's non-zero exit under ErrorActionPreference='Stop', so the pre-cleanup 'nssm stop' of a not-yet-installed service was fatal. - install/uninstall/build .ps1: set $PSNativeCommandUseErrorActionPreference = $false (harmless on PS 5.1); keep explicit $LASTEXITCODE checks. - install-service.ps1: pass runtime flags to nssm as an ARRAY () instead of a hand-quoted AppParameters string — fixes spaced/& paths (e.g. 'D:\Webs\2. Youth & Partners\...'). Cleanup now Get-Service-guarded + sc.exe; verify Running at the end. - DEPLOY.md: by-hand nssm uses the array form; troubleshooting rows for the NativeCommandError and spaced paths (space-free path recommended). Verified: reproduced the throw with sc.exe on a nonexistent service and confirmed the flag prevents it; all scripts parse clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- install-service.ps1 / run.ps1 / config.example.env: cache dir -> "D:/Webs/2. Youth & Partners/media/luatsumienbac/_imgcache" (real path; spaces + & handled by the svcArgs array-splat / quoting) - add -baseURL https://luatsumienbac.vn/media/ so /img/<opts>/<file> resolves to a readable, SEO-friendly, CDN-cacheable path (no base64) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Windows PowerShell 5.1 reads UTF-8 files without a BOM as CP1252, mangling em-dashes (—) and box-drawing (─) into `â€"` and breaking string/brace parsing (e.g. install-service.ps1 line 97 threw "Unexpected token"). Replace all non-ASCII with ASCII equivalents so the scripts parse under PS 5.1 and 7+. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nssm stores AppParameters unquoted, so the spaced/& media cache path
(D:/Webs/2. Youth & Partners/...) truncated -cache at the first space and Go's
flag parser dropped every flag after it — silently killing -baseURL, so readable
URLs 400'd ("must provide absolute remote URL").
- $CacheDir -> D:/imgcache/luatsumienbac (no spaces; cache is scratch, regenerates)
- install-service.ps1: assert no svcArg contains a space on the nssm path (fail
loudly instead of installing a silently-broken service)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
okay, I finally got around to testing the webp and avif implementations, and this is pretty impressive! At least in terms of image quality compared to my previous attempts (#393). It looks to add about 9MB to the total binary size, which isn't great, but it's a 15% increase for supporting modern image formats, so that's not terrible. I still feel a little bit uncomfortable with embedding a wasm runtime in imageproxy, but it's also not the craziest approach for this kind of thing. I've been spending some time recently on breaking imageproxy into smaller includable modules, so we can make this an optional plugin somehow, so both the increased size and wasm nature are worth a shot here. One of the first things we'll need to do though is get this PR off of your so anyway, just wanted to say thanks for the contribution, and I hope to look at it more closely soon. It might have to wait for some of the module work to land, but this is definitely good motivation for me to get that going. |
|
I just wanted to follow up from my comment yesterday to say that I did get this reworked on top of the modules work. You can see it in the modules branch (this commit specifically). I even went so far as to update my personal website to that branch and add a global config option to encode all of my images as AVIF, at which point it crashed my little webhost by running out of memory. I haven't dug into it much yet, but it reminded me about one of the reasons I wanted to add module support. While I have a strong preference for no CGO in the core imageproxy build, individual modules could use CGO if needed and users could choose to link them in or not. So that could open up things like using libvips, which is significantly faster. So, for now you're welcome to try the modules branch if you'd like and see how it performs for you. I definitely need to clean up the modules API quite a bit before I'm ready to merge it into main, and I will probably hold off for a bit on merging this webp/avif implementation. I'd like to look at adding some real benchmarks for image transformation, and do some comparisons of these libraries using purego versus not, as well as some other CGO libraries (now that those are sort of an option). |
Upstream imageproxy decodes WebP but only encodes JPEG/PNG/GIF/TIFF/BMP
(issue #114). This fork adds WebP + AVIF encoders via gen2brain/webp and
gen2brain/avif (libwebp/libaom compiled to WASM, run by wazero) so the
single static binary — built with CGO_ENABLED=0, native Windows — can emit
modern formats for luatsumienbac.vn's self-hosted /media resizer.