Skip to content

binhex/seakarr

Repository files navigation

Seakarr

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.

Features

  • Dual search modesmanual mode searches for a single artist or album on demand; automatic mode 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-count candidates 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 1 subdirectories 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-path using 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-url option.

Prerequisites

Quick start

Installation using uv (recommended)

git clone https://github.com/binhex/seakarr
cd seakarr
uv venv --quiet
uv sync

Installation using pip

git clone https://github.com/binhex/seakarr
cd seakarr
python -m venv .venv
source .venv/bin/activate
pip install .

Usage

seakarr --help

Manual 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"

Options

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-key can also be supplied via the SLSKD_API_KEY environment variable instead of the command-line flag.

How it works

Seakarr processes each album target through two main phases: finding the best candidate on the network, then downloading and organising the files.

Search and candidate selection

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])
Loading

Download and organisation

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])
Loading

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.

Development

git clone https://github.com/binhex/seakarr
cd seakarr
uv venv --quiet
uv sync --extra dev

If 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.

FAQ

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 714 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:

  1. --min-upload-speed is greater than 0.
  2. 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.

About

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.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages