package handlers import ( "context" "encoding/json" "net/http" "os" "path/filepath" "aroll/store" "aroll/transcode" "aroll/ws" ) type analyzeRequest struct { Filename string `json:"filename"` NoiseDb float64 `json:"noiseDb"` MinSilence float64 `json:"minSilence"` Padding float64 `json:"padding"` } // AnalyzeHandler fetches the video bytes from Redis, writes them to a short-lived // temp file for FFmpeg, runs silence detection, then deletes the temp file. func AnalyzeHandler(st *store.Store, hub *ws.Hub) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } var req analyzeRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "bad JSON: "+err.Error(), http.StatusBadRequest) return } data, err := st.Get(context.Background(), req.Filename) if err != nil { http.Error(w, "file not found in store (may have expired)", http.StatusNotFound) return } ext := filepath.Ext(req.Filename) go func() { // Write to a temp file — FFmpeg needs a file path, not a byte slice. // This file exists only for the duration of the FFmpeg scan. tmp, err := os.CreateTemp("", "aroll-analyze-*"+ext) if err != nil { broadcastError(hub, "create temp: "+err.Error()) return } defer os.Remove(tmp.Name()) if _, err := tmp.Write(data); err != nil { tmp.Close() broadcastError(hub, "write temp: "+err.Error()) return } tmp.Close() _, err = transcode.DetectSpeechSegments( tmp.Name(), req.NoiseDb, req.MinSilence, req.Padding, hub.Broadcast, ) if err != nil { broadcastError(hub, err.Error()) } }() w.WriteHeader(http.StatusAccepted) } } func broadcastError(hub *ws.Hub, msg string) { data, _ := json.Marshal(map[string]string{"type": "error", "message": msg}) hub.Broadcast(data) }