package handlers import ( "context" "encoding/json" "net/http" "os" "path/filepath" "aroll/store" "aroll/transcode" "aroll/ws" ) type exportRequest struct { Filename string `json:"filename"` Segments []transcode.Segment `json:"segments"` } // ExportHandler fetches the input video from Redis, runs FFmpeg to cut the // silence, stores the output back in Redis, then cleans up the temp files. // The output key is sent to the frontend via a "done" WebSocket message. func ExportHandler(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 exportRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "bad JSON: "+err.Error(), http.StatusBadRequest) return } if len(req.Segments) == 0 { http.Error(w, "no segments", 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 input bytes to a temp file for FFmpeg inTmp, err := os.CreateTemp("", "aroll-in-*"+ext) if err != nil { broadcastError(hub, "create input temp: "+err.Error()) return } defer os.Remove(inTmp.Name()) if _, err := inTmp.Write(data); err != nil { inTmp.Close() broadcastError(hub, "write input temp: "+err.Error()) return } inTmp.Close() // Create an output temp file for FFmpeg to write into outTmp, err := os.CreateTemp("", "aroll-out-*.mp4") if err != nil { broadcastError(hub, "create output temp: "+err.Error()) return } outTmp.Close() defer os.Remove(outTmp.Name()) // Run FFmpeg — progress streamed via WebSocket if err := transcode.ExportSegments( inTmp.Name(), outTmp.Name(), req.Segments, hub.Broadcast, ); err != nil { broadcastError(hub, err.Error()) return } // Read the output bytes and store them in Redis outData, err := os.ReadFile(outTmp.Name()) if err != nil { broadcastError(hub, "read output: "+err.Error()) return } outputKey := "output-" + store.NewID() if err := st.Set(context.Background(), outputKey, outData); err != nil { broadcastError(hub, "store output: "+err.Error()) return } // Tell the frontend where to download from msg, _ := json.Marshal(map[string]string{ "type": "done", "message": outputKey, }) hub.Broadcast(msg) }() w.WriteHeader(http.StatusAccepted) } }