add: processing controls — start/hold, pause/resume, retry
Three lifecycle controls on the dashboard, sharing one /api surface and the status feed: - Start/hold gate (#11): the watcher tracks the queue but holds processing until POST /api/start. Held by default; AV1DAE_AUTOSTART=1 restores start- on-boot. NOTE: flips the previous auto-start default. - Pause/resume the active encode (#10): SIGSTOP/SIGCONT on the ffmpeg process (libsvtav1 is in-process, so one signal suspends all its threads). Resumes exactly where it left off; the tracker excludes paused time from elapsed. - Retry a failed file (#3): POST /api/retry?file=NAME moves it from failed/ back to input/, with a base-name guard against path traversal. /status now reports running + the failed list; snapshots carry a paused flag. Verified live: held queue, retry move, traversal -> 400, and a real encode suspending to process state T on pause and S on resume. Closes #3 Closes #10 Closes #11
This commit is contained in:
+31
-2
@@ -91,8 +91,29 @@ func main() {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
|
||||
// Start/hold gate: held by default so the user clicks Start; AV1DAE_AUTOSTART
|
||||
// (truthy) restores start-on-boot.
|
||||
autostart := envTruthy(os.Getenv("AV1DAE_AUTOSTART"))
|
||||
w := watcher.New(cfg.Paths.Input, 15, autostart)
|
||||
if !autostart {
|
||||
log.Info("Queue held on startup — click Start (set AV1DAE_AUTOSTART=1 to auto-start)")
|
||||
}
|
||||
|
||||
controls := server.Controls{
|
||||
Running: w.Running,
|
||||
SetRunning: w.SetRunning,
|
||||
Pause: enc.Pause,
|
||||
Resume: enc.Resume,
|
||||
RetryFailed: func(name string) error {
|
||||
if name == "" || name != filepath.Base(name) {
|
||||
return fmt.Errorf("invalid file name")
|
||||
}
|
||||
return mover.Rename(filepath.Join(cfg.Paths.Failed, name), filepath.Join(cfg.Paths.Input, name))
|
||||
},
|
||||
}
|
||||
|
||||
if addr := *cfg.HTTPAddr; addr != "" {
|
||||
srv := &http.Server{Addr: addr, Handler: server.New(tracker, log, store, cfg.Paths.Input).Handler()}
|
||||
srv := &http.Server{Addr: addr, Handler: server.New(tracker, log, store, controls, cfg.Paths.Input, cfg.Paths.Failed).Handler()}
|
||||
go func() {
|
||||
log.Info(fmt.Sprintf("Status server listening on %s", addr))
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
@@ -107,7 +128,6 @@ func main() {
|
||||
}()
|
||||
}
|
||||
|
||||
w := watcher.New(cfg.Paths.Input, 15)
|
||||
w.Start(ctx, func(ctx context.Context, inputPath string) error {
|
||||
return processFile(ctx, inputPath, cfg, enc, metaClient, log, tracker, store)
|
||||
})
|
||||
@@ -269,6 +289,15 @@ func failToFailed(path, failedDir string, log *logger.Logger) {
|
||||
}
|
||||
}
|
||||
|
||||
// envTruthy reports whether an env var is set to a truthy value.
|
||||
func envTruthy(s string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(s)) {
|
||||
case "1", "true", "yes", "on":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// toStatusStreams maps probed source streams to the display shape, keeping only
|
||||
// audio and subtitle streams (the video stream isn't shown).
|
||||
func toStatusStreams(streams []encoder.StreamMetadata) []status.Stream {
|
||||
|
||||
Reference in New Issue
Block a user