136 lines
3.2 KiB
Go
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
|
|
}
|