nsfwapp/backend/preview_jpeg.go
2026-02-06 10:28:46 +01:00

136 lines
3.2 KiB
Go

package main
import (
"bytes"
"fmt"
"net/http"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"time"
)
func prunePreviewCacheDir(previewDir string, maxFrames int, maxAge time.Duration) {
entries, err := os.ReadDir(previewDir)
if err != nil {
return
}
type frame struct {
path string
mt time.Time
}
now := time.Now()
var frames []frame
for _, e := range entries {
name := e.Name()
path := filepath.Join(previewDir, name)
// .part Dateien immer weg
if strings.HasSuffix(name, ".part") {
_ = os.Remove(path)
continue
}
// optional: preview.jpg neu erzeugen lassen, wenn uralt
if name == "preview.jpg" {
if info, err := e.Info(); err == nil {
if maxAge > 0 && now.Sub(info.ModTime()) > maxAge {
_ = os.Remove(path)
}
}
continue
}
// Nur t_*.jpg verwalten
if strings.HasPrefix(name, "t_") && strings.HasSuffix(name, ".jpg") {
info, err := e.Info()
if err != nil {
continue
}
// alte Frames löschen
if maxAge > 0 && now.Sub(info.ModTime()) > maxAge {
_ = os.Remove(path)
continue
}
frames = append(frames, frame{path: path, mt: info.ModTime()})
}
}
// Anzahl begrenzen: älteste zuerst löschen
if maxFrames > 0 && len(frames) > maxFrames {
sort.Slice(frames, func(i, j int) bool { return frames[i].mt.Before(frames[j].mt) })
toDelete := len(frames) - maxFrames
for i := 0; i < toDelete; i++ {
_ = os.Remove(frames[i].path)
}
}
}
func servePreviewJPEGBytes(w http.ResponseWriter, img []byte) {
w.Header().Set("Content-Type", "image/jpeg")
w.Header().Set("Cache-Control", "public, max-age=31536000")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(img)
}
func servePreviewJPEGBytesNoStore(w http.ResponseWriter, img []byte) {
w.Header().Set("Content-Type", "image/jpeg")
w.Header().Set("Cache-Control", "no-store, max-age=0")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(img)
}
func serveLivePreviewJPEGBytes(w http.ResponseWriter, img []byte) {
w.Header().Set("Content-Type", "image/jpeg")
w.Header().Set("Cache-Control", "no-store, max-age=0, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(img)
}
func servePreviewJPEGFile(w http.ResponseWriter, r *http.Request, path string) {
w.Header().Set("Content-Type", "image/jpeg")
w.Header().Set("Cache-Control", "public, max-age=31536000")
w.Header().Set("X-Content-Type-Options", "nosniff")
http.ServeFile(w, r, path)
}
func extractFirstFrameJPEG(path string) ([]byte, error) {
cmd := exec.Command(
ffmpegPath,
"-hide_banner",
"-loglevel", "error",
"-i", path,
"-frames:v", "1",
"-vf", "scale=720:-2",
"-q:v", "10",
"-f", "image2pipe",
"-vcodec", "mjpeg",
"pipe:1",
)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("ffmpeg first-frame: %w (%s)", err, strings.TrimSpace(stderr.String()))
}
return out.Bytes(), nil
}