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") }