nsfwapp/backend/record_preview_scrubber.go
2026-02-23 17:00:22 +01:00

123 lines
3.4 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 main
import (
"fmt"
"math"
"net/http"
"net/url"
"strconv"
"strings"
)
const defaultScrubberCount = 18
// /api/preview-scrubber/{index}?id=... (oder ?file=...)
func recordPreviewScrubberFrame(w http.ResponseWriter, r *http.Request) {
const prefix = "/api/preview-scrubber/"
if !strings.HasPrefix(r.URL.Path, prefix) {
http.NotFound(w, r)
return
}
idxPart := strings.Trim(strings.TrimPrefix(r.URL.Path, prefix), "/")
if idxPart == "" {
http.Error(w, "missing scrubber frame index", http.StatusBadRequest)
return
}
idx, err := strconv.Atoi(idxPart)
if err != nil || idx < 0 {
http.Error(w, "invalid scrubber frame index", http.StatusBadRequest)
return
}
// id oder file muss vorhanden sein (wie bei recordPreview / recordDoneMeta)
q := r.URL.Query()
id := strings.TrimSpace(q.Get("id"))
file := strings.TrimSpace(q.Get("file"))
if id == "" && file == "" {
http.Error(w, "missing id or file", http.StatusBadRequest)
return
}
// Dauer aus Meta ermitteln (WICHTIG für gleichmäßige Verteilung)
durSec, err := lookupDurationForScrubber(r, id, file)
if err != nil || durSec <= 0 {
// Fallback: wir versuchen trotzdem was Sinnvolles
// (z. B. 60s annehmen) besser als gar kein Bild
durSec = 60
}
// Count: gleich wie im Frontend (oder dynamisch, aber dann auch im Payload liefern!)
count := defaultScrubberCount
if idx >= count {
// wenn Frontend mehr sendet als Backend erwartet -> clamp
idx = count - 1
}
if count < 1 {
count = 1
}
t := scrubberIndexToTime(idx, count, durSec)
// An bestehenden Preview-Handler delegieren via Redirect
// recordPreview unterstützt bei dir bereits ?id=...&t=...
targetQ := url.Values{}
if id != "" {
targetQ.Set("id", id)
}
if file != "" {
targetQ.Set("file", file)
}
targetQ.Set("t", fmt.Sprintf("%.3f", t))
// Cache freundlich (optional feinjustieren)
w.Header().Set("Cache-Control", "private, max-age=300")
http.Redirect(w, r, "/api/preview?"+targetQ.Encode(), http.StatusFound)
}
// Gleichmäßig über die Videolänge sampeln (Mitte des Segments)
func scrubberIndexToTime(index, count int, durationSec float64) float64 {
if count <= 1 {
return 0.1
}
if durationSec <= 0 {
return 0.1
}
// nicht exakt bei 0 / nicht exakt am Ende
maxT := math.Max(0.1, durationSec-0.1)
ratio := (float64(index) + 0.5) / float64(count)
t := ratio * maxT
if t < 0.1 {
t = 0.1
}
if t > maxT {
t = maxT
}
return t
}
// TODO: Hier deine bestehende Meta-Lookup-Logik aus recordDoneMeta wiederverwenden.
// Ziel: durationSeconds aus meta.json / job-meta lesen.
// Diese Funktion ist der einzige Teil, den du an dein Projekt anpassen musst.
func lookupDurationForScrubber(r *http.Request, id, file string) (float64, error) {
// ------------------------------------------------------------
// OPTION A (empfohlen): dieselbe interne Funktion nutzen wie recordDoneMeta
// Beispiel (PSEUDO):
//
// meta, err := loadDoneMetaByIDOrFile(id, file)
// if err != nil { return 0, err }
// if d := meta.DurationSeconds; d > 0 { return d, nil }
//
// ------------------------------------------------------------
// ------------------------------------------------------------
// OPTION B: Wenn du aktuell keine Helper-Funktion hast:
// erstmal Fehler zurückgeben und später konkret anschließen.
// ------------------------------------------------------------
return 0, fmt.Errorf("lookupDurationForScrubber not wired yet")
}