Files
Aether/backend/internal/thumbnails/thumbnails.go
T
HoneyandClaude Opus 5 31a027c93a feat(aether): initial commit – self-hosted Cloud, Phasen 1–7
Go-Backend (chi, pgx, golang-migrate) + React/TS-Frontend als PWA.
Enthalten: Auth inkl. OIDC, Dateien mit Versionen und Papierkorb,
Freigaben (Links + intern), WebDAV, Gruppenordner, Admin/Theming,
Volltextsuche mit OCR, eigener PDF-Betrachter und WOPI-Office
(Collabora/OnlyOffice).

Nicht im Repo: deploy/.env (Sitzungsschlüssel, DB- und Admin-Passwort)
sowie alle Daten- und Build-Verzeichnisse – siehe .gitignore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 09:25:36 +02:00

96 lines
2.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package thumbnails
import (
"bytes"
"context"
"fmt"
"image"
_ "image/gif"
"image/jpeg"
_ "image/png"
_ "golang.org/x/image/webp" // SupportedMIME nennt WebP – ohne den Dekodierer entstand dafür nie ein Vorschaubild
"io"
"strings"
"golang.org/x/image/draw"
"aether/internal/storage"
)
const (
maxWidth = 256
maxHeight = 256
)
// SupportedMIME returns true if we can generate a thumbnail for this MIME type.
func SupportedMIME(mime string) bool {
m := strings.ToLower(mime)
return strings.HasPrefix(m, "image/jpeg") ||
strings.HasPrefix(m, "image/png") ||
strings.HasPrefix(m, "image/gif") ||
strings.HasPrefix(m, "image/webp")
}
// ThumbnailKey returns the storage key for a file's thumbnail.
func ThumbnailKey(fileID string) string {
return "thumbs/" + fileID + ".jpg"
}
// Generate reads an image from src, creates a thumbnail, and stores it.
// Returns nil if generation succeeds, error otherwise.
func Generate(ctx context.Context, st storage.Storage, fileID, mimeType string, src io.Reader) error {
if !SupportedMIME(mimeType) {
return fmt.Errorf("unsupported MIME type: %s", mimeType)
}
img, _, err := image.Decode(src)
if err != nil {
return fmt.Errorf("decode image: %w", err)
}
thumb := resize(img, maxWidth, maxHeight)
var buf bytes.Buffer
if err := jpeg.Encode(&buf, thumb, &jpeg.Options{Quality: 80}); err != nil {
return fmt.Errorf("encode thumbnail: %w", err)
}
key := ThumbnailKey(fileID)
if _, _, err := st.Put(ctx, key, &buf); err != nil {
return fmt.Errorf("store thumbnail: %w", err)
}
return nil
}
func resize(src image.Image, maxW, maxH int) image.Image {
bounds := src.Bounds()
w, h := bounds.Dx(), bounds.Dy()
if w == 0 || h == 0 {
return src
}
scaleW := float64(maxW) / float64(w)
scaleH := float64(maxH) / float64(h)
scale := scaleW
if scaleH < scale {
scale = scaleH
}
if scale >= 1 {
return src // already smaller than max
}
newW := int(float64(w) * scale)
newH := int(float64(h) * scale)
if newW < 1 {
newW = 1
}
if newH < 1 {
newH = 1
}
dst := image.NewRGBA(image.Rect(0, 0, newW, newH))
draw.BiLinear.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
return dst
}