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#3Closes#10Closes#11
Rename the Go module, the cmd/ entrypoint dir, the binary, the default config
dir (~/.config/av1dae/), the Docker image/compose service, and all docs and
the dashboard wordmark. No behavioral change — import paths and identifiers
only.
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.
The watcher only globbed *.mkv, silently ignoring every other source
container. Expand the glob to a list of common input extensions
(mkv, mp4, m4v, mov, avi, ts, m2ts, mts, mpg, mpeg, vob, webm, wmv,
flv). Downstream is unaffected: workdir naming uses filepath.Ext, the
encoder always emits .mkv output, and the metadata regexes are not
end-anchored.
Thread context.Context from main through watcher, encoder, and metadata
clients so a Ctrl-C during a multi-hour encode immediately kills the
ffmpeg/ffprobe/opusenc children instead of waiting for them to finish.
- main: signal.NotifyContext replaces the manual sigChan + done goroutine
- watcher.Start: takes ctx, exits on ctx.Done(); processFn signature is
now func(context.Context, string) error
- encoder: Transcode and every helper (extractAudio, encodeOpus,
encodeVideo, GetMediaInfo, GetStreamLanguages, calculateZscaleWidth)
take ctx; every exec.Command becomes exec.CommandContext so the child
is SIGKILL'd on cancel
- metadata: FetchMovieMetadata, FetchSeriesMetadata, fetchTVMazeJSON
take ctx and use http.NewRequestWithContext
Mover stays ctx-free intentionally: a rename is fast enough that
mid-cancel cleanup is the next-restart's problem. processFile's
deferred RemoveAll(workDir) and failToFailed still run after cancel,
so partial output dies in the work dir and the source moves to failed/.
Two related hardening fixes for the input folder poller (bug #22):
1. Partial-write protection. A freshly-dropped .mkv now has to present
the same (mtime, size) on two consecutive ticks before processFn is
called. A file mid-rsync that grows or has a moving mtime is skipped
until it settles. The per-file (mtime, size) state lives in
Watcher.seen and is rebuilt from the current glob each tick so the
map cannot grow unboundedly.
2. Failure quarantine. When processFn returns an error, the file's
(failedAt, mtime) is recorded in Watcher.failed and subsequent ticks
skip it until either the backoff elapses or its mtime changes (user
replaced or touched it). Previously a permanently-broken input -- e.g.
a video-only mkv that trips the "no audio streams found" path added
in the multi-audio fix -- would be retried and logged every 15 s
forever.
Backoff is 5 minutes: comfortably longer than the 10-30 s polling
interval clamp so we are not effectively retrying every tick, but
short enough that an operator fixing the underlying problem by
replacing the file sees it picked up promptly on the next stable scan.