Beatport downloader (FLAC, AAC) — a fork of unspok3n/beatportdl reworked:
- pure go drops the dependencies on CGO, zig, Make, etc
- now just a standard go package: run
go install,go build, etc - behaves like a normal unix command line tool (see 'Behavior')
- config file now follows convention instead of forcibly being next to the binary
- full auto completion for zsh/bash
- many bug fixes and structural improvements
beetroot-dl https://www.beatport.com/track/strobe/1696999Behaviour
upstream beatportdl |
beetroot-dl |
|
|---|---|---|
| after a download | drops into an interactive prompt loop | exits |
| interactive loop | always on | opt-in with -i |
--help |
none | full usage screen |
| flag parsing | after config load + login | before anything else |
| exit status | always 0 |
0 ok, 1 failure, 2 usage, 130 interrupted |
| downloaded paths | never reported | printed to stdout, one per line |
| progress/errors/prompts | mixed into stdout | all on stderr |
| config format | YAML | TOML |
| config location | next to the binary | ~/.config/beetroot-dl.toml |
XDG_CONFIG_HOME |
Linux only | all platforms |
| missing config, no tty | blocks forever on a prompt | fails with an explanation |
| fatal error | "Press enter to exit" (blocks) | message on stderr, exits |
The point of most of it: a run is now scriptable. stdout carries only results, so the output pipes; the exit status says whether it worked; and nothing waits on a human unless you asked for a prompt.
# cue every track you just bought, without guessing what was written where
beetroot-dl "$URL" | while read -r f; do process "$f"; done || echo "something failed"Fixes
--helpworks on a fresh machine. Flags were parsed afterSetup(), so asking for help on a box with no config would first interrogate you for a username and password.- Piped/cron runs no longer hang. With no config and no terminal, the first-run prompt would block forever; it now explains what to run instead.
- Panics in download workers are no longer swallowed. They printed to
stdout and were discarded, so a crashed download still exited
0. They are now reported on stderr and counted. - Results survive Ctrl-D. Quitting a prompt called
os.Exitdeep inside the input reader, so paths already downloaded were never printed. Logging in/Refreshing tokenno longer pollute stdout.
Additions
-v/--verbose— report each step (auth, resolve, fetch metadata, target directory, write, tag) on stderr.-q/--quiet— suppress progress bars.--config PATH— use a specific config file.--completions bash|zsh— print a shell completion script (install instructions under Setup).--version.
Breaking
- The binary is
beetroot-dl, notbeatportdl. - Config is TOML at
~/.config/beetroot-dl.toml; port an existingbeatportdl-config.ymlby hand (key: value→key = "value"). - Upstream's
-qmeant "quit after finishing" — that is the default now, and-qmeans--quiet.
Requires an active Beatport or Beatsource streaming plan.
- Scriptable by default: downloads what you asked for and exits; stdout carries only the downloaded file paths, so output pipes cleanly.
- Real exit codes (
0ok,1failure,2usage,130interrupted). --help,--version,-v,-q,--config— and they work before any config or login.- TOML config at
~/.config/beetroot-dl.tomlinstead of YAML next to the binary;XDG_CONFIG_HOMErespected on all platforms. - Never blocks on a prompt in a pipe or cron job.
- Pure Go — no CGO, so
go install github.com/monomadic/beetroot-dl@latestworks on a machine with nothing but Go, and cross-compiling needs no C toolchain (upstream requires taglib headers and zig to build).
(Short for now — this list grows as TASKS.md gets worked through.)
-
Download or build beetroot-dl.
Compiled binaries for Windows, macOS (amd64, arm64) and Linux (amd64, arm64) are available on the Releases page.
Don't forget to set the execute permission on unix systems, e.g., chmod +x beetroot-dl-darwin-arm64 -
Run beetroot-dl with
-i(e.g../beetroot-dl-darwin-arm64 -i), then specify the:- Beatport username
- Beatport password
- Downloads directory
- Audio quality
-
OPTIONAL: Install shell completions — the binary carries its own scripts:
# zsh — write into any directory on your $fpath, then restart the shell beetroot-dl --completions zsh > "${fpath[1]}/_beetroot-dl" # bash beetroot-dl --completions bash > ~/.local/share/bash-completion/completions/beetroot-dl
-
OPTIONAL: Customize the config file at
~/.config/beetroot-dl.toml(or$XDG_CONFIG_HOME/beetroot-dl.toml), created on first run. It is TOML, so values arekey = "value". You can put the following options into it:
| Option | Default Value | Type | Description |
|---|---|---|---|
username |
String | Beatport username | |
password |
String | Beatport password | |
quality |
lossless | String | Download quality (medium-hls, medium, high, lossless) |
show_progress |
true | Boolean | Enable progress bars |
write_error_log |
false | Boolean | Write errors to error.log |
max_download_workers |
15 | Integer | Concurrent download jobs limit |
max_global_workers |
15 | Integer | Concurrent global jobs limit |
downloads_directory |
String | Location for the downloads directory | |
sort_by_context |
false | Boolean | Create a directory for each release, playlist, chart, label, or artist |
sort_by_label |
false | Boolean | Use label names as parent directories for releases (requires sort_by_context) |
force_release_directories |
false | Boolean | Create release directories inside chart and playlist folders (requires sort_by_context) |
track_exists |
update | String | Behavior when track file already exists |
track_number_padding |
2 | Integer | Track number padding for filenames and tag mappings (when using track_number_with_padding or release_track_count_with_padding)Set to 0 for dynamic padding based on track count |
cover_size |
1400x1400 | String | Cover art size for keep_cover and track metadata (if fix_tags is enabled) [max: 1400x1400] |
keep_cover |
false | Boolean | Download cover art file (cover.jpg) to the context directory (requires sort_by_context) |
fix_tags |
true | Boolean | Enable tag writing capabilities |
tag_mappings |
Listed below | String Map | Custom tag mappings |
track_file_template |
{number}. {artists} - {name} ({mix_name}) | String | Track filename template |
release_directory_template |
[{catalog_number}] {artists} - {name} | String | Release directory template |
playlist_directory_template |
{name} [{created_date}] | String | Playlist directory template |
chart_directory_template |
{name} [{published_date}] | String | Chart directory template |
label_directory_template |
{name} [{updated_date}] | String | Label directory template |
artist_directory_template |
{name} | String | Artist directory template |
whitespace_character |
String | Whitespace character for track filenames and release directories | |
artists_limit |
3 | Integer | Maximum number of artists allowed before replacing with artists_short_form (affects directories, filenames, and search results) |
artists_short_form |
VA | String | Custom string to represent "Various Artists" |
key_system |
standard-short | String | Music key system used in filenames and tags |
proxy |
String | Proxy URL |
If the Beatport credentials are correct, you should also see the file beetroot-dl-credentials.json appear beside the config.
If you accidentally entered an incorrect password and got an error, you can always manually edit the config file
Download quality options, per Beatport/Beatsource subscription type:
| Option | Description | Requires at least | Notes |
|---|---|---|---|
medium-hls |
128 kbps AAC through /stream endpoint (IMPORTANT: requires ffmpeg) |
Essential / Beatsource | Same as medium on Advanced but uses a slightly slower download method |
medium |
128 kbps AAC | Advanced / Beatsource Pro+ | |
high |
256 kbps AAC | Professional / Beatsource Pro+ | |
lossless |
44.1 kHz FLAC | Professional / Beatsource Pro+ |
Available track_exists options:
errorLog error and skipskipSkip silentlyoverwriteRe-downloadupdateUpdate tags
Available template keywords for filenames and directories (*_template):
- Track:
id,name,mix_name,slug,artists,remixers,number,length,key,bpm,genre,subgenre,genre_with_subgenre,subgenre_or_genre,isrc,label - Release:
id,name,slug,artists,remixers,date,year,track_count,bpm_range,catalog_number,upc,label - Playlist:
id,name,first_genre,track_count,bpm_range,length,created_date,updated_date - Chart:
id,name,slug,first_genre,track_count,creator,created_date,published_date,updated_date - Artist:
id,name,slug - Label:
id,name,slug,created_date,updated_date
Default tag_mappings config:
tag_mappings:
flac:
track_name: "TITLE"
track_artists: "ARTIST"
track_number: "TRACKNUMBER"
track_subgenre_or_genre: "GENRE"
track_key: "KEY"
track_bpm: "BPM"
track_isrc: "ISRC"
release_name: "ALBUM"
release_artists: "ALBUMARTIST"
release_date: "DATE"
release_track_count: "TOTALTRACKS"
release_catalog_number: "CATALOGNUMBER"
release_label: "LABEL"
m4a:
track_name: "TITLE"
track_artists: "ARTIST"
track_number: "TRACKNUMBER"
track_genre: "GENRE"
track_key: "KEY"
track_bpm: "BPM"
track_isrc: "ISRC"
release_name: "ALBUM"
release_artists: "ALBUMARTIST"
release_date: "DATE"
release_track_count: "TOTALTRACKS"
release_catalog_number: "CATALOGNUMBER"
release_label: "LABEL"As you can see, each key here represents a predefined value from either a release or a track that you can use to customize what is written to which tags. When you add an entry in the mappings for any format (for e.g., flac), only the tags that you specify will be written.
All tags by default are converted to uppercase, but since some M4A players might not recognize it, you can write the tag in lowercase and add the _raw suffix to bypass the conversion. (This applies to M4A tags only)
For e.g., Traktor doesn't recognize the track key tag in uppercase, so you have to add:
tag_mappings:
m4a:
track_key: "initialkey_raw"Available tag_mappings keys: track_id,track_url,track_name,track_artists,track_artists_limited,track_remixers,track_remixers_limited,track_number,track_number_with_padding,track_number_with_total,track_genre,track_subgenre,track_genre_with_subgenre,track_subgenre_or_genre,track_key,track_bpm,track_isrc,release_id,release_url,release_name,release_artists,release_artists_limited,release_remixers,release_remixers_limited,release_date,release_year,release_track_count,release_track_count_with_padding,release_catalog_number,release_upc,release_label,release_label_url
Available key_system options:
| System | Example |
|---|---|
standard |
Eb Minor, F Major |
standard-short |
Ebm, F |
openkey |
7m, 12d |
camelot |
2A, 7B |
Proxy URL format example: http://username:password@127.0.0.1:8080
Give it a URL; it downloads and exits.
beetroot-dl https://www.beatport.com/track/strobe/1696999Multiple URLs, or text files of URLs (one per line), both work:
beetroot-dl https://www.beatport.com/track/strobe/1696999 https://www.beatport.com/track/move-for-me/591753
beetroot-dl file.txt file2.txtThe path of each downloaded track goes to stdout, one per line, so a run composes with other tools:
beetroot-dl https://www.beatport.com/track/strobe/1696999 | while read -r f; do
echo "got: $f"
doneProgress bars, errors and -v step reporting all go to stderr, and the exit
status says whether it worked — so beetroot-dl ... || echo failed is
meaningful.
For the interactive prompt loop (search queries, label/artist filters), pass
-i:
beetroot-dl -i
Enter url or search query:Search returns beatport results; include an @beatsource tag in the query to
search beatsource instead. A search query needs -i, since picking a result
requires a prompt.
Run beetroot-dl --help for the full flag list.
URL types that are currently supported: Tracks, Releases, Playlists, Charts, Labels, Artists
Pure Go — no C toolchain, headers, system libraries, Makefile, or environment variables needed. Tagging uses TagLib compiled to WebAssembly (go.senan.xyz/taglib), embedded in the binary and run in-process.
Install straight from source:
go install github.com/monomadic/beetroot-dl@latestOr from a checkout — a bare go install, or:
go buildCross-compiling is standard Go — GOOS=... GOARCH=... go build, nothing
else. --version reports the module version (or git tag/commit) stamped by
the Go toolchain itself; no build flags required.
ffmpeg is required at runtime only for the medium-hls quality setting.