Automated music downloader that searches the Soulseek network for albums and tracks via the slskd API, applying intelligent quality filtering, speed checking, and library upgrade scanning.
- Dual search modes —
manualmode searches for a single artist or album on demand;automaticmode scans an existing music library directory and upgrades every album it finds. - Preferred-user fast-path — seakarr remembers reliable uploaders across runs. On subsequent searches it probes those users directly (no network search) before falling back to the full Soulseek network.
- Quality filtering — restrict results by file extension (e.g.
flac), minimum bitrate (kbps), and minimum bit depth (e.g. 24-bit). Locked/private files are excluded by default. - Speed checking — after a transfer begins, seakarr measures the real upload rate and cancels
connections that fall below
--min-upload-speed. When more than--min-filtered-user-countcandidates are available the fastest is automatically selected; below that threshold the check is skipped so you never get stuck with no candidates. - Multi-candidate fallback — if a candidate fails (timeout, error, or slow speed) seakarr moves on to the next ranked candidate automatically. If every candidate is rejected by the speed check, the fastest of them is retried without the speed floor rather than giving up entirely.
- Multi-tier search fallback — if the primary artist + album query returns no usable results, seakarr re-tries first with an artist-only query (filtered by album directory name), then with an album-only query (filtered by artist directory name), maximising the chance of finding obscure or misspelled albums.
- Disc/box-set merging — multi-disc releases stored in
Disc 1/CD 1subdirectories are transparently merged into a single candidate so they download as a complete set. - File organisation — completed downloads can be automatically moved into a structured directory tree
under
--slskd-completed-pathusing a configurable path pattern with%user%,%artist%, and%album%placeholders. - Tag stripping — optionally remove all embedded metadata tags from organised files, useful when a downstream tagger (e.g. MusicBrainz Picard, Beets) will re-tag from scratch.
- SQLite tracking — every successfully downloaded album is recorded in a local SQLite database so seakarr never re-downloads the same album, even across restarts.
- Graceful cleanup on failure — if a download is cancelled, times out, or errors, all partially transferred files are removed from the slskd completed directory automatically.
- Keyword exclusion — filter out candidates whose filenames contain unwanted words
(e.g.
vinyl,live,demo). - Concurrent processing — download and search multiple albums in parallel using
--workers. Each worker runs its own search-filter-download pipeline independently, significantly speeding up large library upgrade scans. - Push notifications — send a notification on each successful album download via any of
Apprise's 80+ services (ntfy, Discord, Slack, Telegram, …)
using the repeatable
--notify-urloption.
- Python 3.12+
- Astral uv (optional)
- A running slskd instance with an API key
git clone https://github.com/binhex/seakarr
cd seakarr
uv venv --quiet
uv syncgit clone https://github.com/binhex/seakarr
cd seakarr
python -m venv .venv
source .venv/bin/activate
pip install .seakarr --helpManual search for a single album:
seakarr \
--slskd-api-key YOUR_KEY \
--search-mode manual \
--search-artist "Pink Floyd" \
--search-title "The Wall"Automatic library upgrade scan:
seakarr \
--slskd-api-key YOUR_KEY \
--search-mode automatic \
--music-library-path /mnt/music \
--allowed-extensions flac \
--min-bitrate 320 \
--slskd-completed-path /downloads/complete \
--completed-pattern "%artist%/%album%"With push notifications (ntfy + Discord):
seakarr \
--slskd-api-key YOUR_KEY \
--search-mode automatic \
--music-library-path /mnt/music \
--notify-url "ntfy://my-topic" \
--notify-url "discord://webhook_id/token"| Option | Description | Default | Example | Type |
|---|---|---|---|---|
--slskd-url ✱ |
URL of the slskd server. | http://localhost:8983 |
http://192.168.1.10:8983 |
string |
--slskd-api-key ✱ |
slskd API key (16–255 characters). Can also be set via the SLSKD_API_KEY environment variable. |
— | abc123def456ghi7 |
string |
--slskd-url-base |
URL base path when slskd is served from a subdirectory (e.g. behind a reverse proxy). | / |
/slskd |
string |
--slskd-completed-path |
Local filesystem path where slskd saves completed downloads. Required when --completed-pattern is set. |
— | /downloads/complete |
path |
--search-mode |
manual searches for a single artist/album; automatic scans --music-library-path for albums to upgrade. |
manual |
automatic |
choice |
--search-artist ✱† |
Artist name to search for. Required when --search-mode=manual. |
— | "Pink Floyd" |
string |
--search-title |
Album or track title to search for. When omitted in manual mode, all content from the artist is returned. | — | "The Wall" |
string |
--search-user |
Restrict the search to a single Soulseek user's shared files (skips the network-wide search). | — | someuser123 |
string |
--response-limit |
Maximum number of search responses to collect from the Soulseek network. | 1000 |
2000 |
integer |
--search-timeout |
Seconds to wait for the Soulseek network search to complete. | 15 |
30 |
integer |
--browse-cache-ttl |
Cache user directory browse results for this many days. When enabled, the full directory listing fetched for a user is stored in SQLite and reused on subsequent runs within the TTL window, avoiding expensive Soulseek browse API calls. 0 disables caching. |
7 |
14 |
integer |
--search-type |
Filter candidates by track count: album keeps results with 5+ tracks, single keeps 1–4 tracks, any applies no restriction. |
any |
album |
choice |
--allowed-extensions |
Comma-separated list of permitted file extensions. | flac |
flac,mp3 |
string |
--min-bitrate |
Minimum bitrate in kbps. Files with a lower bitrate are excluded. | — | 320 |
integer |
--min-bitdepth |
Minimum bit depth in bits. Files with a lower bit depth are excluded. | — | 24 |
integer |
--include-locked |
Include locked (private) files in search results. Locked files are excluded by default. | false |
— | flag |
--exclude-words |
Comma-separated list of words to exclude from filenames (case-insensitive). | — | vinyl,live,demo |
string |
--max-queue-length |
Maximum accepted upload queue length for a peer. 0 requires a free upload slot; values above 0 allow queues up to that length. |
0 |
10 |
integer |
--max-start-time |
Maximum time to wait after a file reaches the front of the remote user's upload queue before the transfer actually starts. | 120 |
240 |
integer |
--max-queue-time |
Maximum seconds to wait from enqueue before any file starts transferring. Abandons the candidate and tries the next if no file has started at all within this window. Set to 0 to disable. |
1800 |
3600 |
integer |
--min-upload-speed |
Minimum measured transfer speed in KB/s. Peers below this threshold are cancelled and the next candidate is tried. Set to 0 to disable the speed check entirely. |
250 |
500 |
integer |
--speed-check-wait |
Seconds to wait after a transfer starts before measuring its speed. | 30 |
60 |
integer |
--min-filtered-user-count |
Minimum number of filtered candidates required before the speed check is applied. Below this threshold the check is skipped to avoid leaving no viable candidates. | 10 |
20 |
integer |
--max-retries |
Maximum per-file retry attempts on a transfer error. | 4 |
6 |
integer |
--retry-delay |
Seconds to wait between retry attempts. | 30 |
60 |
integer |
--download-timeout |
Inactivity timeout in seconds. The download is cancelled if no track has completed and no transfer is actively in-flight for this long. | 180 |
300 |
integer |
--browse-timeout |
Maximum seconds to wait when browsing a user's shared files. Users that exceed this limit are skipped for the rest of the current run. Set to 0 to disable (may hang indefinitely on slow peers). |
60 |
120 |
integer |
--max-download-time |
Hard wallclock ceiling in minutes for a single album download. Cancels the session regardless of in-progress transfer state. | 120 |
240 |
integer |
--clear-completed |
Remove all completed transfers from slskd after each album download. Warning: this is a global operation that clears all completed transfers, not only those started by seakarr. | false |
— | flag |
--completed-pattern |
Relative path pattern for organising downloaded files under --slskd-completed-path. Supports %user%, %artist%, and %album% placeholders. Requires --slskd-completed-path. |
— | %artist%/%album% |
string |
--strip-tags |
Strip all embedded metadata tags from downloaded files after they are organised. Requires --completed-pattern. |
false |
— | flag |
--music-library-path ✱‡ |
Path to an existing music library. Each subdirectory is treated as an artist, and each of its subdirectories as an album to search for and upgrade. Required when --search-mode=automatic. |
— | /mnt/music |
path |
--database-path |
Path to the SQLite database file used to track downloaded albums and preferred users. | ~/.seakarr/db/seakarr.db |
/var/lib/seakarr/seakarr.db |
path |
--log-level |
Console logging level. Choices: debug, info, success, warning, error. |
INFO |
debug |
choice |
--log-path |
Path to the log file. | ~/.seakarr/logs/seakarr.log |
/var/log/seakarr.log |
path |
--api-timeout |
HTTP request timeout in seconds for slskd API calls. Prevents the monitor loop from blocking indefinitely when slskd waits on an unresponsive remote peer. Set to 0 to disable (may hang indefinitely on slow peers). |
30 |
60 |
integer |
--notify-url |
Apprise notification URL. Repeat the flag to send to multiple services. A notification is sent after each successful album download. | — | ntfy://my-topic |
string |
--workers |
Number of concurrent search and download processing workers. Set to 1 for sequential processing. |
10 |
4 |
integer |
✱ Required. † Required only when --search-mode=manual. ‡ Required only when --search-mode=automatic.
Note:
--slskd-api-keycan also be supplied via theSLSKD_API_KEYenvironment variable instead of the command-line flag.
Seakarr processes each album target through two main phases: finding the best candidate on the network, then downloading and organising the files.
flowchart TD
A([Start]) --> B{--search-mode?}
B -- manual --> C[Build query from\n--search-artist +\n--search-title]
B -- automatic --> D[Scan --music-library-path\nfor artist/album directories]
D --> E{Already in DB?\nDownloaded or skipped?}
E -- Yes --> F([⏭ Skip album])
E -- No --> E2{Album on disk?\nautomatic mode +\n%artist%+%album% in pattern}
E2 -- Yes --> E3[Backfill DB sentinel] --> F
E2 -- No / check skipped --> G
C --> G{Next album target}
G --> H{DB preferred users\navailable?}
H -- "Yes (no search-user)" --> I[Browse preferred user\nfiles directly]
I --> J{Album found &\nquality criteria met?}
J -- No, try next --> I
J -- All failed --> K{--search-user set?}
H -- No --> K
K -- Yes --> L[Browse single user's\nshared files]
K -- No --> M[Network search\nfor artist + album]
M --> N{Any results?}
N -- No or density-rejected --> O[Fallback cascade:\nartist-only · album-only\n+ dir filtering]
N -- Yes --> P[Album density filter:\nreject wrong-album results]
O --> P
P --> Q[build_candidates:\ngroup by dir · filter by ext /\nbitrate / bitdepth / lock / queue]
L --> Q
Q --> R{Any candidates\nsurvive filtering?}
R -- No --> S([⚠️ Record skip in DB])
R -- Yes --> T([Ranked candidate list\nready for download])
flowchart TD
A([Ranked candidates]) --> B[Integrity check\non candidate files]
B -- Fail --> C[Try next candidate]
B -- Pass --> D{candidates >=\n--min-filtered-user-count?}
D -- Yes --> E[Start download · wait\n--speed-check-wait · measure rate]
D -- No --> F[Start download\nskip speed check]
E --> G{Speed >=\n--min-upload-speed?}
G -- Below threshold --> H[Cancel + record\nspeed failure]
H --> C
G -- Above threshold --> I[Continue download]
F --> I
I --> J{File error?}
J -- Yes, retries remain --> K[Re-enqueue file\nwait --retry-delay]
K --> I
J -- Yes, retries exhausted --> L[Cleanup staged files]
L --> C
J -- Timeout or cancelled --> M[Cleanup staged files]
M --> C
J -- All succeeded --> N{--completed-pattern set?}
C --> O{More candidates?}
O -- No, speed-cancelled exist --> P[Retry fastest candidate\nwithout speed floor]
P --> N
O -- No, all failed --> Q{Artist-only fallback\nnot yet tried?}
Q -- Yes --> R[Artist-only network search\nrefilter · retry from top]
R --> A
Q -- No --> Q2{Album-only fallback\nnot yet tried?}
Q2 -- Yes --> R2[Album-only network search\nrefilter · retry from top]
R2 --> A
Q2 -- No --> S([❌ Record skip in DB])
N -- No --> T[Write DB success record]
N -- Yes --> U[Move files using\n--completed-pattern path]
U --> V{--strip-tags set?}
V -- Yes --> W[Strip all embedded tags\nfrom organised files]
W --> T
V -- No --> T
T --> X[Promote user to\npreferred in DB]
X --> Y([✅ Album complete])
If a download is cancelled, times out, or errors, all partially transferred files are cleaned up from slskd's completed directory automatically before the next candidate is tried.
git clone https://github.com/binhex/seakarr
cd seakarr
uv venv --quiet
uv sync --extra devIf you wish to perform linting on all files before committing (PRs will not be accepted if they do not
pass all linting) then run pre-commit run --all-files.
Q: My album has multiple discs — will seakarr handle it?
Seakarr automatically merges multi-disc releases stored in Disc 1 / Disc 2 or CD 1 / CD 2
subdirectories into a single unified candidate so the whole set downloads together.
Q: How do I prevent seakarr from re-downloading albums I already have?
Every successfully downloaded album is recorded in the SQLite database (see --database-path). Seakarr
checks this record before each search and skips any album already marked as downloaded. In automatic
mode, if --completed-pattern contains both %artist% and %album%, seakarr also checks the
organised library on disk — useful when the database has been wiped or albums were added manually.
Albums found on disk are backfilled into the database so subsequent runs use the faster DB check.
Q: What happens when slskd returns a 500 error mid-transfer?
Per-file errors trigger an automatic retry up to --max-retries times (with --retry-delay seconds
between attempts). If retries are exhausted the candidate is abandoned, staged files are cleaned up, and
seakarr moves on to the next ranked candidate.
Q: Can I run seakarr without the speed check?
Set --min-upload-speed 0 to disable the speed check entirely. All candidates will be downloaded
regardless of their measured transfer rate.
Q: The --completed-pattern placeholders look like %%artist%% in some places — why?
Click escapes % to %% in help text. When passing the option on the command line use single percent
signs, e.g. --completed-pattern "%artist%/%album%".
Q: An album keeps getting skipped every run — how do I reset it?
Seakarr records failed or exhausted albums in its skip list. Most skips are soft and are retried on the
next run. If an album was marked permanently skipped (e.g. via a manual database edit) it will never
be retried. Use an SQLite browser to inspect and delete the relevant row from the skipped table in
the database file specified by --database-path.
Q: What does --browse-cache-ttl do and what value should I use?
When set to a non-zero value, seakarr stores the full directory listing fetched from each Soulseek user
into SQLite. Subsequent runs reuse the cached listing instead of hitting the network, which speeds up
the preferred-user fast-path considerably. A value of 7–14 days is a reasonable starting point.
Set it shorter if you want seakarr to pick up newly shared files sooner, or 0 to disable entirely.
Q: What is the preferred users list and how does it help?
After a user passes both the download and speed check, they are promoted to the preferred list. On subsequent runs seakarr browses each preferred user's library before running a full Soulseek network search. This avoids a 30-second broadcast search entirely for albums that preferred users already have, making those runs significantly faster.
Q: Why do some files get excluded even though I can see them in slskd?
Files in locked (private) directories are included in candidate scoring but filtered out before
downloading unless --include-locked is set. Files are also excluded if they fail the extension
filter (--allowed-extensions), minimum bitrate (--min-bitrate), or minimum bit depth
(--min-bitdepth) checks.
Q: A download appears stuck and never completes — what happens?
Seakarr enforces a --download-timeout (per-file idle time) and --max-download-time (total wall
clock cap). Transfers stuck in the slskd Initializing state for longer than --api-timeout seconds
are also detected and abandoned. When a timeout fires, staged files are cleaned up and seakarr moves
on to the next ranked candidate.
Q: I have set --workers to a value higher than 2, but my concurrent search still shows only 2, why is that?
Slskd has a hard coded maximum concurrent search value of 2, any worker value higher than 2 will result in queued searches please note this does not affect concurrent downloads (no limit set in Slskd).
Q: I'm specifying --min-upload-speed but I never see any speed checks being performed — why?
The speed check is only applied when two conditions are both true:
--min-upload-speedis greater than 0.- The number of candidates found for the album is at least
--min-filtered-user-count.
The second condition exists to avoid cancelling a download when there are no alternatives to fall back
to. If only one or two candidates are found and that is below --min-filtered-user-count, the speed
check is skipped and the best available candidate is downloaded unconditionally. To see speed checks
more often, lower --min-filtered-user-count.
Q: A user successfully provided a full album download but was not added to my preferred users list — why?
Preferred user promotion requires the speed check to have been applied and passed. A user is only added to the preferred list when they have demonstrated both reliability (completed the download) and speed (passed the measured transfer rate threshold). If the speed check was skipped — for example because there were not enough competing candidates (see above) — the user is not promoted regardless of the outcome. The reported speed shown in the candidate list is the value advertised by slskd and is not the same as the actual measured transfer rate used for the speed check.
Q: How do I set up push notifications?
Pass one or more --notify-url flags using any URL scheme supported by
Apprise. Each URL encodes both the service type and its
credentials. For example:
# ntfy (self-hosted or ntfy.sh)
--notify-url "ntfy://my-topic"
# Discord webhook
--notify-url "discord://webhook_id/token"
# Slack
--notify-url "slack://tokenA/tokenB/tokenC/channel"
# Multiple services at once
--notify-url "ntfy://my-topic" --notify-url "discord://webhook_id/token"A notification titled "Seakarr — Download Complete" with body "Artist / Album" is sent after each album finishes downloading. Invalid or unrecognised URLs are logged as warnings (only the scheme is logged — credentials are never written to logs) and the remaining services are still notified.