add: live encode progress tracking and read-only status web UI
Stream ffmpeg -progress during the video encode into an in-memory tracker (internal/status) so the long-running step is no longer a black box: percent, fps, speed, and ETA are derived from the source duration and updated ~1/s. Progress prints to stdout only (no logs.db row) to avoid burying real events. Expose it over HTTP (internal/server, default :8080, set http_addr to "" to disable): GET /status returns the live snapshot, the pending input queue, and recent non-debug events; GET / serves an embedded dashboard that polls /status every second. The server shuts down on the same SIGINT/SIGTERM context as the watcher.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
// Package server exposes a read-only HTTP status endpoint for the daemon:
|
||||
// the live encode progress, the pending input queue, and recent log events.
|
||||
package server
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"videnc-vibe/internal/logger"
|
||||
"videnc-vibe/internal/status"
|
||||
"videnc-vibe/internal/watcher"
|
||||
)
|
||||
|
||||
//go:embed index.html
|
||||
var indexHTML []byte
|
||||
|
||||
type Server struct {
|
||||
tracker *status.Tracker
|
||||
log *logger.Logger
|
||||
inputDir string
|
||||
}
|
||||
|
||||
func New(tracker *status.Tracker, log *logger.Logger, inputDir string) *Server {
|
||||
return &Server{tracker: tracker, log: log, inputDir: inputDir}
|
||||
}
|
||||
|
||||
// Handler returns the mux for all status routes. Phase 3 adds "/" (the HTML
|
||||
// dashboard) to this same mux.
|
||||
func (s *Server) Handler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/status", s.handleStatus)
|
||||
mux.HandleFunc("/", s.handleIndex)
|
||||
return mux
|
||||
}
|
||||
|
||||
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = w.Write(indexHTML)
|
||||
}
|
||||
|
||||
type statusResponse struct {
|
||||
Current status.Snapshot `json:"current"`
|
||||
Queue []string `json:"queue"`
|
||||
Recent []logger.LogEntry `json:"recent"`
|
||||
}
|
||||
|
||||
func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
queue := []string{}
|
||||
for _, f := range watcher.InputFiles(s.inputDir) {
|
||||
queue = append(queue, filepath.Base(f))
|
||||
}
|
||||
|
||||
recent, err := s.log.RecentLogs(50)
|
||||
if err != nil {
|
||||
http.Error(w, "reading logs", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(statusResponse{
|
||||
Current: s.tracker.Snapshot(),
|
||||
Queue: queue,
|
||||
Recent: recent,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user