- attach.VariantKey: a thumbnail's copy in another format next to it - attach/webp (separate package, so only binaries that import it link the encoder): Encode at quality 80 and Variant, which writes the WebP copy of a thumbnail once and leaves no file on a failed encode - github.com/gen2brain/webp v0.6.4, decided in .planning/notes/2026-10-28-webp-gen2brain.md - lagoon README and docs/database/attachments.md
107 lines
3.5 KiB
Go
107 lines
3.5 KiB
Go
// Package webp encodes WebP images and writes WebP variants of attachment
|
|
// thumbnails next to them, through github.com/gen2brain/webp (no cgo: the
|
|
// system libwebp through purego when it is installed, else libwebp
|
|
// transpiled to Go). It is a separate package so that only binaries that
|
|
// import it link the encoder.
|
|
package webp
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"io"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/lagoon/attach"
|
|
encoder "github.com/gen2brain/webp"
|
|
"gocloud.dev/blob"
|
|
"gocloud.dev/gcerrors"
|
|
)
|
|
|
|
// Quality is the encoder quality of Encode, on libwebp's 0 to 100 scale.
|
|
const Quality = 80
|
|
|
|
// Format is the extension of a WebP variant (attach.VariantKey).
|
|
const Format = "webp"
|
|
|
|
// ContentType is the content type a variant is stored with.
|
|
const ContentType = "image/webp"
|
|
|
|
// maxThumbBytes caps the thumbnail Variant reads, like attach caps an
|
|
// original.
|
|
const maxThumbBytes = 32 << 20
|
|
|
|
// Encode writes img to w as a lossy WebP at Quality.
|
|
func Encode(w io.Writer, img image.Image) error {
|
|
if img == nil {
|
|
return errors.New("webp: image is nil")
|
|
}
|
|
return encoder.Encode(w, img, encoder.Options{Quality: Quality})
|
|
}
|
|
|
|
// encodeImage is Encode; tests replace it to fail an encode.
|
|
var encodeImage = Encode
|
|
|
|
// Variant writes the WebP variant of f's thumbnail at w by h in mode (see
|
|
// attach.File.ThumbKey, which makes the thumbnail first when it is missing)
|
|
// next to that thumbnail, under attach.VariantKey(thumbKey, "webp"), and
|
|
// returns the variant's key. A variant that already exists is returned as
|
|
// it is, so a second call writes nothing. The thumbnail is encoded in
|
|
// memory before anything is written, and the key is deleted when the write
|
|
// or its close fails, so a failed encode leaves no partial file. Errors of
|
|
// ThumbKey are returned as they are.
|
|
func Variant(ctx context.Context, bucket *blob.Bucket, f *attach.File, w, h int, mode string) (string, error) {
|
|
thumbKey, err := f.ThumbKey(ctx, bucket, w, h, mode)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
key := attach.VariantKey(thumbKey, Format)
|
|
if key == "" {
|
|
return "", fmt.Errorf("webp: no variant key for %q", thumbKey)
|
|
}
|
|
exists, err := bucket.Exists(ctx, key)
|
|
if err != nil {
|
|
return "", fmt.Errorf("webp: variant exists: %w", err)
|
|
}
|
|
if exists {
|
|
return key, nil
|
|
}
|
|
r, err := bucket.NewReader(ctx, thumbKey, nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("webp: read thumbnail: %w", err)
|
|
}
|
|
raw, err := io.ReadAll(io.LimitReader(r, maxThumbBytes))
|
|
closeErr := r.Close()
|
|
if err != nil {
|
|
return "", fmt.Errorf("webp: read thumbnail: %w", err)
|
|
}
|
|
if closeErr != nil {
|
|
return "", fmt.Errorf("webp: read thumbnail: %w", closeErr)
|
|
}
|
|
img, _, err := image.Decode(bytes.NewReader(raw))
|
|
if err != nil {
|
|
return "", fmt.Errorf("webp: decode thumbnail %q: %w", thumbKey, err)
|
|
}
|
|
var buf bytes.Buffer
|
|
if err := encodeImage(&buf, img); err != nil {
|
|
return "", fmt.Errorf("webp: encode %q: %w", thumbKey, err)
|
|
}
|
|
wr, err := bucket.NewWriter(ctx, key, &blob.WriterOptions{ContentType: ContentType})
|
|
if err != nil {
|
|
return "", fmt.Errorf("webp: variant writer: %w", err)
|
|
}
|
|
_, writeErr := wr.Write(buf.Bytes())
|
|
closeErr = wr.Close()
|
|
if writeErr != nil || closeErr != nil {
|
|
if delErr := bucket.Delete(ctx, key); delErr != nil && gcerrors.Code(delErr) != gcerrors.NotFound {
|
|
return "", fmt.Errorf("webp: delete partial variant %q: %w", key, delErr)
|
|
}
|
|
if writeErr != nil {
|
|
return "", fmt.Errorf("webp: write variant: %w", writeErr)
|
|
}
|
|
return "", fmt.Errorf("webp: write variant: %w", closeErr)
|
|
}
|
|
return key, nil
|
|
}
|