Two follow-ups from live debugging of "work/min never populates" and
"stopped never reached".
1) jobs/min + downloads/min are now computed in the BACKEND on a fixed
cadence (_rate_loop, EWMA) and reported ready-to-show. The rates were
derived client-side from poll deltas with a dt<30s guard — but a
backgrounded/unfocused browser tab throttles its timers to ~1/min, so
every delta exceeded 30s and the guard blanked the rates forever. A
server-side rate is independent of how often the tab polls. Frontend just
displays s.jobs_per_min / s.downloads_per_min. VERSION → .9.
2) ffmpeg video sampling is now killable on Stop. A downloader stuck in a
slow/reconnecting decode (observed: 47s, 230s for one video) couldn't see
the stop signal until ffmpeg returned, so Stop detached still-running
threads and work kept flowing long after — "stopped" that wasn't really
stopped. sample_frames_from_url now runs ffmpeg via Popen and polls a
`should_stop` callback every 0.5s, terminating (then killing) the process
at once on Stop or the per-video timeout. A stop-killed job is handed back
(transient), not failed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>