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:
@@ -5,12 +5,15 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"av1dae/internal/logger"
|
||||
@@ -26,6 +29,59 @@ type Encoder struct {
|
||||
opusencPath string
|
||||
log *logger.Logger
|
||||
tracker *status.Tracker
|
||||
|
||||
procMu sync.Mutex
|
||||
cur *os.Process // the in-flight video encode, for pause/resume
|
||||
}
|
||||
|
||||
// Pause freezes the active video encode in place via SIGSTOP. libsvtav1 runs in
|
||||
// the ffmpeg process (no forked children), so one signal suspends all its
|
||||
// threads. No-op if nothing is encoding. The process keeps its memory and
|
||||
// partial output and resumes exactly where it left off.
|
||||
func (e *Encoder) Pause() error {
|
||||
e.procMu.Lock()
|
||||
defer e.procMu.Unlock()
|
||||
if e.cur == nil {
|
||||
return nil
|
||||
}
|
||||
if err := e.cur.Signal(syscall.SIGSTOP); err != nil {
|
||||
return err
|
||||
}
|
||||
if e.tracker != nil {
|
||||
e.tracker.SetPaused(true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resume thaws a paused encode via SIGCONT. No-op if nothing is encoding.
|
||||
func (e *Encoder) Resume() error {
|
||||
e.procMu.Lock()
|
||||
defer e.procMu.Unlock()
|
||||
if e.cur == nil {
|
||||
return nil
|
||||
}
|
||||
if err := e.cur.Signal(syscall.SIGCONT); err != nil {
|
||||
return err
|
||||
}
|
||||
if e.tracker != nil {
|
||||
e.tracker.SetPaused(false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Encoder) setProc(p *os.Process) {
|
||||
e.procMu.Lock()
|
||||
e.cur = p
|
||||
e.procMu.Unlock()
|
||||
}
|
||||
|
||||
func (e *Encoder) clearProc() {
|
||||
e.procMu.Lock()
|
||||
e.cur = nil
|
||||
e.procMu.Unlock()
|
||||
if e.tracker != nil {
|
||||
e.tracker.SetPaused(false)
|
||||
}
|
||||
}
|
||||
|
||||
type StreamInfo struct {
|
||||
@@ -94,6 +150,9 @@ func (e *Encoder) runCmdProgress(ctx context.Context, label, file, name string,
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Publish the process so Pause/Resume can signal it; clear on exit.
|
||||
e.setProc(cmd.Process)
|
||||
defer e.clearProc()
|
||||
|
||||
// Reads stdout to EOF (when ffmpeg exits), so Wait below is safe afterwards.
|
||||
var lastLog time.Time
|
||||
|
||||
Reference in New Issue
Block a user