Job Dashboard¶
The dashboard is a live view of every download, not a page you reload. Job state is pushed to the browser over Server-Sent Events.
Live updates¶
Two SSE endpoints exist:
| Endpoint | Scope |
|---|---|
GET /events | Every job — powers the dashboard list |
GET /api/jobs/{job_id}/events | One job — powers the job detail page |
| Property | Value |
|---|---|
| Keep-alive interval | 5 s |
| Per-client queue | 32 events |
| Max concurrent connections | 200 |
| Rate limit | 30/minute per client |
The event broker is process-local
Subscribers only receive events published by the same Python process. This is why WORKERS must stay at 1; with a second Gunicorn worker, clients would subscribe to a process that never sees their job. See Resources & Workers.
If a client's queue overflows — a browser tab suspended in the background, for instance — the connection is dropped rather than allowed to grow unbounded. The page reconnects and re-reads the current state from GET /api/jobs.
Job statuses¶
| Status | Terminal | Meaning |
|---|---|---|
queued | Waiting for a worker slot | |
processing | Metadata extraction and format selection | |
downloading | yt-dlp is fetching | |
transcoding | ffmpeg is converting | |
analysis | BPM detection running; the file is already downloadable | |
analysis_done | Finished, with analysis | |
done | Finished, no analysis | |
error | Failed — the message is shown and a retry is offered | |
cancelled | Cancelled by the user |
done and analysis_done both count as completed. analysis additionally counts as downloadable, so an audio file can be fetched while its tempo analysis is still running.
A completed video whose file is VP9 or AV1 (see Video Codecs & Output Format) carries a Limited playback marker in the job list and the details dialog — it will not open on Safari, iOS, or most TVs.
Job actions¶
| Action | Availability |
|---|---|
| Download | Any downloadable status (automatic when Start Download after Processing is on) |
| Open job page | Any job |
| Cancel | While in flight |
| Retry | After error |
| Trim | Audio jobs, downloadable — Audio Trimming |
| Stems | Audio jobs, Lalal.ai connected — Stem Separation |
| Share | Completed jobs — Share Links |
There is no per-job delete action. Jobs are removed either by retention sweeping expired jobs — row, files and share links together, so they leave this list as well — or by Settings → System → Remove all jobs, which clears every job at once (see Bulk cleanup below).
Hiding jobs from the history¶
Settings → General → Retention → Enable Job History decides whether a new job is listed in the dashboard. A job submitted while it is off still runs and is counted in the statistics, but is left out of the list. The choice is stored per job at submission, so flipping the setting later never changes jobs that already exist. The Show Job History toggle on the dashboard is unrelated: it only collapses the list in your browser. See Application Settings.
Starting the download automatically¶
Settings → General → Retention → Start Download after Processing is on by default. Once a newly submitted job becomes downloadable, the browser starts the file download on its own — no extra click on Download needed. The job still appears in the job history exactly as before; only the extra click is skipped. Turning it off restores the previous behavior: the job waits in the list until you download it yourself. Like Enable Job History, this only applies to jobs submitted after the change. See Application Settings.
Phones and tablets¶
On touch devices up to iPad Pro 12.9" in landscape, the job list switches from a table to compact cards.
A Current job card above the list keeps the download you just started visible even while Show Job History is off. It holds one job at a time, is cleared by a page reload, and hands the job back to the list once the screen is wide enough for the table.
Track length¶
duration_seconds is filled twice. At submit time it comes from the source's own metadata, so a job shows its length while it is still queued or downloading. When the download finishes, ffprobe measures the actual file and replaces the value — a probe that comes back empty leaves the source value in place rather than blanking it.
It is rendered as M:SS, or H:MM:SS from an hour up, in the job list (desktop media column and mobile card), the detail dialog, and on the job page. Jobs whose length is unknown show an en dash.
The job page¶
GET /job/{job_id} renders a detail view with:
- The player (in-browser playback of the downloaded file)
- The waveform for audio jobs, with drag-to-select trimming
- Detected BPM and beat confidence, when analysis has run
- Job metadata: source URL, platform, type, quality, duration, file size, timestamps
Statistics¶
GET /api/stats returns aggregate counters for the dashboard — totals by status, bytes downloaded, and the like. The result is cached briefly so a busy dashboard does not re-aggregate the database on every event.
GET /api/stats/bpm-clusters buckets every detected tempo into 5-BPM groups, sorted by count, for the tempo distribution chart.
Settings → System → Reset statistics (POST /api/stats/reset) stamps a reset marker rather than deleting rows, so counters restart without losing job history.
Bulk cleanup¶
Settings → System → Remove all jobs (POST /api/jobs/remove-all, 2/minute) deletes every job row and its on-disk artifacts in one step.
There is no undo
Removing all jobs deletes the downloaded media as well. Any share links pointing at those jobs stop working immediately.
For automatic cleanup by age, use retention instead — see Storage & Retention.
Recovery after a restart¶
Jobs that were processing, downloading, or transcoding when the process stopped cannot be resumed — the subprocess is gone. At startup they are marked cancelled with the message "Cancelled because the application restarted during processing", so they end up in a terminal state you can retry from instead of being stuck in flight forever.