123 lines
3.4 KiB
Go
123 lines
3.4 KiB
Go
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")
|
||
}
|