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

208 lines
4.4 KiB
Go

package main
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
func formatBytesSI(b int64) string {
if b < 0 {
b = 0
}
const unit = 1024
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
suffix := []string{"KB", "MB", "GB", "TB", "PB"}
v := float64(b) / float64(div)
// 1 Nachkommastelle, außer sehr große ganze Zahlen
if v >= 10 {
return fmt.Sprintf("%.0f %s", v, suffix[exp])
}
return fmt.Sprintf("%.1f %s", v, suffix[exp])
}
func u64ToI64(x uint64) int64 {
if x > uint64(maxInt64) {
return maxInt64
}
return int64(x)
}
func ensureAssetsForVideo(videoPath string) error {
// Default: keine SourceURL (für Covers egal)
return ensureAssetsForVideoWithProgress(videoPath, "", nil)
}
// Optional: für Stellen, wo du die URL hast (z.B. Postwork / Jobs)
func ensureAssetsForVideoWithSource(videoPath string, sourceURL string) error {
return ensureAssetsForVideoWithProgress(videoPath, sourceURL, nil)
}
// onRatio: 0..1 (Assets-Gesamtfortschritt)
func ensureAssetsForVideoWithProgress(videoPath string, sourceURL string, onRatio func(r float64)) error {
videoPath = strings.TrimSpace(videoPath)
if videoPath == "" {
return nil
}
fi, statErr := os.Stat(videoPath)
if statErr != nil || fi.IsDir() || fi.Size() <= 0 {
return nil
}
// ✅ ID = Dateiname ohne Endung (immer OHNE "HOT " Prefix)
base := filepath.Base(videoPath)
id := strings.TrimSuffix(base, filepath.Ext(base))
id = stripHotPrefix(id)
if strings.TrimSpace(id) == "" {
return nil
}
assetDir, gerr := ensureGeneratedDir(id)
if gerr != nil || strings.TrimSpace(assetDir) == "" {
return fmt.Errorf("generated dir: %v", gerr)
}
metaPath := filepath.Join(assetDir, "meta.json")
// ---- Meta / Duration ----
durSec := 0.0
if d, ok := readVideoMetaDuration(metaPath, fi); ok {
durSec = d
} else {
dctx, cancel := context.WithTimeout(context.Background(), 6*time.Second)
d, derr := durationSecondsCached(dctx, videoPath)
cancel()
if derr == nil && d > 0 {
durSec = d
// ✅ Duration-only meta schreiben (inkl. sourceURL)
_ = writeVideoMetaDuration(metaPath, fi, durSec, sourceURL)
}
}
// ✅ Wenn Duration aus Meta kam, aber SourceURL jetzt neu vorhanden ist,
// dann Meta "anreichern" (ohne ffprobe).
if durSec > 0 && strings.TrimSpace(sourceURL) != "" {
if u, ok := readVideoMetaSourceURL(metaPath, fi); !ok || strings.TrimSpace(u) == "" {
_ = writeVideoMetaDuration(metaPath, fi, durSec, sourceURL)
}
}
// Gewichte: thumbs klein, preview groß
const (
thumbsW = 0.25
previewW = 0.75
)
progress := func(r float64) {
if onRatio == nil {
return
}
if r < 0 {
r = 0
}
if r > 1 {
r = 1
}
onRatio(r)
}
progress(0)
// ----------------
// Thumbs
// ----------------
thumbPath := filepath.Join(assetDir, "thumbs.jpg")
if tfi, err := os.Stat(thumbPath); err == nil && !tfi.IsDir() && tfi.Size() > 0 {
progress(thumbsW)
} else {
progress(0.05)
genCtx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel()
if err := thumbSem.Acquire(genCtx); err != nil {
// best-effort
progress(thumbsW)
goto PREVIEW
}
defer thumbSem.Release()
progress(0.10)
t := 0.0
if durSec > 0 {
t = durSec * 0.5
}
progress(0.15)
img, e1 := extractFrameAtTimeJPEG(videoPath, t)
if e1 != nil || len(img) == 0 {
img, e1 = extractLastFrameJPEG(videoPath)
if e1 != nil || len(img) == 0 {
img, e1 = extractFirstFrameJPEG(videoPath)
}
}
progress(0.20)
if e1 == nil && len(img) > 0 {
if err := atomicWriteFile(thumbPath, img); err != nil {
fmt.Println("⚠️ thumb write:", err)
}
}
progress(thumbsW)
}
PREVIEW:
// ----------------
// Preview
// ----------------
previewPath := filepath.Join(assetDir, "preview.mp4")
if pfi, err := os.Stat(previewPath); err == nil && !pfi.IsDir() && pfi.Size() > 0 {
progress(1)
return nil
}
genCtx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()
progress(thumbsW + 0.02)
if err := genSem.Acquire(genCtx); err != nil {
progress(1)
return nil
}
defer genSem.Release()
progress(thumbsW + 0.05)
if err := generateTeaserClipsMP4WithProgress(genCtx, videoPath, previewPath, 1.0, 18, func(r float64) {
if r < 0 {
r = 0
}
if r > 1 {
r = 1
}
progress(thumbsW + r*previewW)
}); err != nil {
fmt.Println("⚠️ preview clips:", err)
}
progress(1)
return nil
}