Add gallery admin and video media support.
This updates gallery handling to support video playback with generated poster thumbnails, adds authenticated admin upload/delete flows, and improves dev/runtime behavior including reliable thumbnail generation and media-safe response handling. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
package gallery
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -29,7 +32,8 @@ func EnsureThumbnails(imagesDir string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return filepath.WalkDir(imagesDir, func(path string, d fs.DirEntry, err error) error {
|
||||
var failures []string
|
||||
walkErr := filepath.WalkDir(imagesDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -39,7 +43,7 @@ func EnsureThumbnails(imagesDir string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !isJPEG(d.Name()) {
|
||||
if !isGalleryMedia(d.Name()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -52,19 +56,43 @@ func EnsureThumbnails(imagesDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if isVideo(d.Name()) {
|
||||
thumbDst := filepath.Join(imagesDir, thumbDirName, filepath.FromSlash(videoPosterRel(rel)))
|
||||
if err := os.MkdirAll(filepath.Dir(thumbDst), 0o755); err != nil {
|
||||
failures = append(failures, fmt.Sprintf("%s: %v", rel, err))
|
||||
return nil
|
||||
}
|
||||
if err := writeVideoPoster(path, thumbDst, gridThumbMaxEdge); err != nil {
|
||||
failures = append(failures, fmt.Sprintf("%s: %v", rel, err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
thumbDst := filepath.Join(imagesDir, thumbDirName, filepath.FromSlash(rel))
|
||||
heroDst := filepath.Join(imagesDir, heroDirName, filepath.FromSlash(rel))
|
||||
if err := os.MkdirAll(filepath.Dir(thumbDst), 0o755); err != nil {
|
||||
return err
|
||||
failures = append(failures, fmt.Sprintf("%s: %v", rel, err))
|
||||
return nil
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(heroDst), 0o755); err != nil {
|
||||
return err
|
||||
failures = append(failures, fmt.Sprintf("%s: %v", rel, err))
|
||||
return nil
|
||||
}
|
||||
if err := writeThumb(path, thumbDst, gridThumbMaxEdge, gridThumbQuality); err != nil {
|
||||
return err
|
||||
failures = append(failures, fmt.Sprintf("%s thumb: %v", rel, err))
|
||||
}
|
||||
return writeThumb(path, heroDst, heroThumbMaxEdge, heroThumbQuality)
|
||||
if err := writeThumb(path, heroDst, heroThumbMaxEdge, heroThumbQuality); err != nil {
|
||||
failures = append(failures, fmt.Sprintf("%s hero: %v", rel, err))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return fmt.Errorf("%d file(s): %s", len(failures), strings.Join(failures, "; "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsurePriority generates thumbnails for one image before first page load.
|
||||
@@ -87,6 +115,41 @@ func EnsurePriority(imagesDir, relPath string) error {
|
||||
return writeThumb(src, thumbDst, gridThumbMaxEdge, gridThumbQuality)
|
||||
}
|
||||
|
||||
func writeVideoPoster(src, dst string, maxEdge int) error {
|
||||
srcInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dstInfo, err := os.Stat(dst); err == nil && !dstInfo.ModTime().Before(srcInfo.ModTime()) {
|
||||
return nil
|
||||
}
|
||||
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
||||
return nil
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
scale := fmt.Sprintf("scale=%d:%d:force_original_aspect_ratio=decrease", maxEdge, maxEdge)
|
||||
cmd := exec.Command("ffmpeg",
|
||||
"-hide_banner", "-loglevel", "error", "-y",
|
||||
"-i", src,
|
||||
"-vf", scale,
|
||||
"-frames:v", "1",
|
||||
"-q:v", "5",
|
||||
dst,
|
||||
)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
msg := strings.TrimSpace(stderr.String())
|
||||
if msg == "" {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("%w: %s", err, msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeThumb(src, dst string, maxEdge, quality int) error {
|
||||
srcInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user