gerdur 2.11.0 → 2.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.11.1 - 2026-08-31
4
+
5
+ ### Docs
6
+
7
+ - **README rewritten** for npm — a task-oriented CLI guide (common tasks, the
8
+ full options table, every source prefix, search, previews, `--enrich`, output
9
+ templates, config file, headless use) followed by the programmatic API
10
+ (sessions, primitives, re-exported query functions, auth/config helpers,
11
+ multi-account). Added a table of contents, shields badges, a single-line
12
+ description; dropped the internal-project references and the stale team footer.
13
+ - `package.json` `description` rewritten and `keywords` added for npm search.
14
+
15
+ No code changes.
16
+
3
17
  ## 2.11.0 - 2026-08-31
4
18
 
5
19
  ### Fixed
package/README.md CHANGED
@@ -1,257 +1,325 @@
1
1
  # gerdur
2
2
 
3
- <img src="https://raw.githubusercontent.com/soulwax/gerdur/main/.github/assets/logo.png" alt="gerdur logo" width="200" height="200" style="display: block; margin-left: auto; margin-right: auto;" />
3
+ > Command-line music downloader for Deezer Spotify and Tidal links resolved via
4
+ > ISRC/UPC matching — with automatic MP3/FLAC tagging, synced lyrics, and a
5
+ > side-effect-free programmatic API.
4
6
 
5
- > A component of the darkfloor streaming stack.
6
- > Source: <https://github.com/soulwax/gerdur>
7
+ [![npm](https://img.shields.io/npm/v/gerdur.svg)](https://www.npmjs.com/package/gerdur)
8
+ [![npm downloads](https://img.shields.io/npm/dm/gerdur.svg)](https://www.npmjs.com/package/gerdur)
9
+ [![node](https://img.shields.io/node/v/gerdur.svg)](https://www.npmjs.com/package/gerdur)
7
10
 
8
- ![Screenshot](https://raw.githubusercontent.com/soulwax/gerdur/main/.github/assets/screenshot.png)
9
-
10
- **gerdur is a program for downloading music from streaming music services. Remember that the artists and studios put a lot of work into making music — purchase the original music to support them.**
11
-
12
- ## Why *gerdur*?
13
-
14
- **Gerðr** is a jötunn — in the old poems, *the fairest of all beings*, the glow of her arms lighting sea and sky. Freyr sees her from Odin's watchtower, falls ill with longing, and sends his servant Skírnir riding through a wall of fire to win her back. Her name is *garðr*: the enclosure, the walled yard, the tended garden — the same root as English *garden* and *garth*, and the *-grad* of Slavic city-names.
15
-
16
- A track behind a service is Gerðr — walled off, worth the crossing. **gerdur** is what rides through the fire and brings it home into your own *garðr*: decrypted, tagged, and yours to keep.
11
+ <img src="https://raw.githubusercontent.com/soulwax/gerdur/main/.github/assets/logo.png" alt="gerdur" width="160" height="160" />
17
12
 
18
- ## Features
19
-
20
- - *Supports downloading tracks, albums, artists, and playlists*
21
- - *Allows music quality selection (**128 kbps**, **320 kbps** and **FLAC**)*
22
- - *Auto tagging **MP3** & **FLAC** (including album cover and lyrics)*
23
- - *Support downloading from both links and via searching*
13
+ ![Screenshot](https://raw.githubusercontent.com/soulwax/gerdur/main/.github/assets/screenshot.png)
24
14
 
25
- ## Supported Sites
15
+ > Artists and studios put real work into this music. Use `gerdur` for personal
16
+ > and archival copies of things you're entitled to, and **buy the music you
17
+ > love** so the people who made it keep making it.
26
18
 
27
- ### *Deezer*, *Spotify* and *Tidal*
19
+ ---
28
20
 
29
- - *Tracks*
30
- - *Albums*
31
- - *Audiobook*
32
- - *Playlists*
33
- - *Artists*
21
+ ## Contents
22
+
23
+ - [Install](#install)
24
+ - [First run — your `arl`](#first-run--your-arl)
25
+ - [CLI](#cli)
26
+ - [Common tasks](#common-tasks)
27
+ - [Options](#options)
28
+ - [Sources](#sources)
29
+ - [Search](#search)
30
+ - [Previews](#previews)
31
+ - [Better cover art (`--enrich`)](#better-cover-art---enrich)
32
+ - [Output templates](#output-templates)
33
+ - [Config file](#config-file)
34
+ - [Headless / scripting](#headless--scripting)
35
+ - [Programmatic API](#programmatic-api)
36
+ - [High-level: sessions](#high-level-sessions)
37
+ - [Low-level: primitives](#low-level-primitives)
38
+ - [Re-exported query functions](#re-exported-query-functions)
39
+ - [Auth &amp; config helpers](#auth--config-helpers)
40
+ - [Multiple accounts](#multiple-accounts)
41
+ - [Supported services](#supported-services)
42
+ - [The name](#the-name)
43
+ - [Legal](#legal)
34
44
 
35
- > Note that Spotify and Tidal tracks will be sourced from Deezer using ISRC matching and UPC for albums. Also Spotify artist tracks are limited upto 10 items.
45
+ ---
36
46
 
37
47
  ## Install
38
48
 
39
- ### *Method 1*
49
+ **As a global CLI** (needs [Node](https://nodejs.org) 18+):
40
50
 
41
- First install `nodejs` following [this tutorial](https://nodejs.org/en/download/package-manager/)
42
-
43
- Once `nodejs` installation is complete run this command.
44
-
45
- npm i -g gerdur # may require sudo on linux and macOS
46
-
47
- or if you are using yarn
48
-
49
- yarn global add gerdur
50
-
51
- or pnpm
52
-
53
- pnpm add -g gerdur
54
-
55
- Now you can run using command `gerdur` to start.
56
-
57
- ### First run — getting your `arl`
51
+ ```bash
52
+ npm i -g gerdur # sudo may be required on Linux/macOS
53
+ ```
58
54
 
59
- `gerdur` logs in to Deezer using an `arl` cookie. On the **first interactive run** (just
60
- running `gerdur` with no `--headless`/scripting flags), if you have not configured an `arl`
61
- yet, `gerdur` walks you through it: it prints where to find the cookie and prompts you to
62
- paste it, then saves it for you. It will also offer to re-enter the `arl` if login fails
63
- because the cookie has expired.
55
+ ```bash
56
+ yarn global add gerdur
57
+ ```
64
58
 
65
- To get your `arl` manually:
59
+ ```bash
60
+ pnpm add -g gerdur
61
+ ```
66
62
 
67
- 1. Open [https://www.deezer.com](https://www.deezer.com) in your browser and log in.
68
- 2. Open DevTools (`F12`) → **Application** → **Cookies** → `deezer.com`.
69
- 3. Copy the value of the `arl` cookie (a long hex string).
63
+ Run `gerdur` with no arguments for the interactive prompt.
70
64
 
71
- You can set or update it any time without the prompt:
65
+ **As a prebuilt binary** download for your platform from
66
+ [Releases](https://github.com/soulwax/gerdur/releases). On Windows double-click;
67
+ on Linux/macOS `chmod +x gerdur && ./gerdur`.
72
68
 
73
- gerdur --set-arl <your_arl>
69
+ **As a library** — `npm i gerdur` and import it. Importing is side-effect-free
70
+ (no banner, no arg parsing, nothing logged). See [Programmatic API](#programmatic-api).
74
71
 
75
- Or run the guided setup at any time:
72
+ ## First run your `arl`
76
73
 
77
- gerdur setup # same as: gerdur --setup
74
+ `gerdur` logs in to Deezer with an **`arl` cookie**. On the first interactive run,
75
+ if none is configured, it walks you through getting one and saves it.
78
76
 
79
- #### Log in with email & password
77
+ **Guided setup** (any time):
80
78
 
81
- `gerdur` can fetch your `arl` automatically from your Deezer email and password, so you
82
- never have to open DevTools. The guided setup offers it as the first option:
79
+ ```bash
80
+ gerdur setup # same as: gerdur --setup
81
+ ```
83
82
 
84
- gerdur setup
83
+ **Paste an `arl` directly:**
85
84
 
86
- Only the fetched `arl` is stored; your password is never written to disk unless you
87
- explicitly opt in when prompted. For headless use, supply `GERDUR_EMAIL` and
88
- `GERDUR_PASSWORD` env vars and `gerdur` will log in automatically when no `arl` is set:
85
+ ```bash
86
+ gerdur --set-arl <your_arl>
87
+ ```
89
88
 
90
- GERDUR_EMAIL=you@example.com GERDUR_PASSWORD=secret gerdur -q 320 -u <url>
89
+ To copy it manually: open <https://www.deezer.com> logged in DevTools (`F12`)
90
+ Application → Cookies → `deezer.com` → copy the `arl` value (192 hex chars).
91
91
 
92
- > Login uses Deezer's mobile OAuth flow. If it ever fails (bad credentials or a change
93
- > on Deezer's side), `gerdur` falls back to asking you to paste an `arl`.
92
+ **Log in with email &amp; password** the guided setup can fetch the `arl` for you
93
+ via Deezer's mobile OAuth flow, so you never open DevTools. Only the resulting
94
+ `arl` is stored; your password is never written to disk unless you explicitly opt
95
+ in when prompted. Deezer bot-protects scripted logins, so this is best-effort and
96
+ falls back to pasting an `arl`.
94
97
 
95
- ### Zero-config usage (env var & global config)
98
+ **Zero-config / CI** — set `GERDUR_ARL` (or `GERDUR_EMAIL` + `GERDUR_PASSWORD`)
99
+ and skip the config file entirely. `GERDUR_ARL` takes precedence over the config
100
+ file and is never written to disk:
96
101
 
97
- `gerdur` works from **any directory** without a per-folder config file:
102
+ ```bash
103
+ GERDUR_ARL=<your_arl> gerdur -d -q 320 -u https://deezer.com/album/302127
104
+ ```
98
105
 
99
- - Set the `GERDUR_ARL` environment variable and `gerdur` will use it (it takes precedence over
100
- the config file, and is never written to disk). Handy for CI, containers, and one-offs:
106
+ When you save an `arl` and there's no `gerdur.config.json` in the current
107
+ directory, it goes to a **global** path
108
+ (`$XDG_CONFIG_HOME/gerdur/gerdur.config.json`, or `~/.config/gerdur/…`) and is
109
+ reused everywhere.
101
110
 
102
- GERDUR_ARL=<your_arl> gerdur -q 320 -u <url>
111
+ ---
103
112
 
104
- - When you save an `arl` (via `setup` or `--set-arl`) and there is no `gerdur.config.json` in
105
- the current directory, `gerdur` stores it in a **global** location
106
- (`$XDG_CONFIG_HOME/gerdur/gerdur.config.json`, or `~/.config/gerdur/gerdur.config.json`) so it is
107
- reused everywhere. A `gerdur.config.json` in the current directory still takes precedence.
113
+ ## CLI
108
114
 
109
- > Scripted, headless (`-d`), and non-interactive (piped) runs never prompt — they behave
110
- > exactly as before and simply use the configured `arl` (or `GERDUR_ARL`).
115
+ ### Common tasks
111
116
 
112
- ### *Method 2*
117
+ ```bash
118
+ # Interactive — paste a URL or type a search term, then pick what to download
119
+ gerdur
113
120
 
114
- Download pre-built binary from [here](https://github.com/soulwax/gerdur/releases) and then double click on Windows to run. For Linux and macOS user, first open your choice of terminal and the execute with `./gerdur`
121
+ # A single track / album / playlist / artist URL
122
+ gerdur -u https://deezer.com/album/302127 -q flac
115
123
 
116
- ## CLI Parameters
124
+ # A Spotify or Tidal link (matched to Deezer by ISRC / UPC)
125
+ gerdur -u https://open.spotify.com/track/0DiWol3AO6WpXZgp0goxAV -q 320
117
126
 
118
- All options are optional. You can suppress prompts via providing `quality` and `url` if you are using scripts. You can also use config file. Read the docs [here](https://github.com/soulwax/gerdur/blob/main/docs/config.md) for more info.
127
+ # Many URLs from a file, 8 at a time
128
+ gerdur -i links.txt -c 8 -q 320
119
129
 
120
- | Parameter | Short | Supported values | Description |
121
- | :-------------------- | :-------------: | :---------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------: |
122
- | `--quality` | `-q` | 128/320/flac | The quality of the files to download |
123
- | `--output` | `-o` | Output file format according to `saveLayout`. See more [here](https://github.com/soulwax/gerdur/blob/main/docs/config.md) | The path to download the files to |
124
- | `--url` | `-u or nothing` | album/artist/playlist/track url | Downloads from url |
125
- | `--input-file` | `-i` | Downloads all urls listed in text file. Example: `gerdur -i links.txt` | Downloads from url |
126
- | `--concurrency` | `-c` 1 to 50 | Downloads X songs at a time | |
127
- | `--set-arl` | `-a` | `arl` string | Set arl cookies |
128
- | `--setup` | `-s` | *Nothing* | Run guided setup to enter your `arl` cookie. Also available as `gerdur setup`. |
129
- | `--overwrite` | `-w` | *Nothing* | Re-download and overwrite existing files (default is to skip them). |
130
- | `--experimental-login`| *None* | *Nothing* | Enable experimental email/password login to fetch an `arl` (often blocked by Deezer; falls back to arl paste). |
131
- | `--headless` | `-d` | *Nothing* | Run in headless mode. You must provide both `--url` and `--quality` if you run in headless mode. This mode is meant for scripting automation. |
132
- | `--resolve-full-path` | `-rfp` | *Nothing* | Create playlist file with absolute path location |
133
- | `--create-playlist` | `-cp` | *Nothing* | Create playlist for albums and artists |
134
- | `--config-file` | `-conf` | Config location. Example: `gerdur -conf my-gerdur.config.json` | Specify custom location to config file |
135
- | `--update` | `-U` | *Nothing* | Download new update (binary only) |
136
- | `--help` | `-h` | *Nothing* | Shows the CLI help |
137
- | `--search` | *None* | free-text query | Search tracks and pick what to download. Combine with the filters below. |
138
- | `--artist` | *None* | artist name | Search filter — restrict results to this artist |
139
- | `--album` | *None* | album title | Search filter — restrict results to this album |
140
- | `--track` | *None* | track title | Search filter — restrict results to this title |
141
- | `--label` | *None* | record label | Search filter — restrict results to this label |
142
- | `--bpm-min` / `--bpm-max` | *None* | number | Search filter — tempo range (beats per minute) |
143
- | `--dur-min` / `--dur-max` | *None* | number (seconds) | Search filter — track duration range |
144
- | `--search-limit` | *None* | number (default 50) | How many search results to fetch |
145
- | `--preview` | *None* | *Nothing* | Download the 30-second preview clips (`.preview.mp3`) instead of full tracks — no `--quality` needed |
146
- | `--enrich` | *None* | *Nothing* | Embed a higher-res front cover from the Cover Art Archive (looked up by ISRC via MusicBrainz); falls back to Deezer's cover |
130
+ # Search and pick
131
+ gerdur --search "get lucky" --artist "Daft Punk"
147
132
 
148
- ## Search
133
+ # Grab an exact recording by barcode, no prompts
134
+ gerdur -d -q flac -u isrc:USUM71311296
149
135
 
150
- ### From the interactive prompt
136
+ # Your Deezer Flow
137
+ gerdur -u flow
151
138
 
152
- When `gerdur` asks for a URL, a search term, or a prefixed query:
139
+ # 30-second previews instead of full tracks (no arl needed)
140
+ gerdur --preview -u https://deezer.com/album/302127
141
+ ```
153
142
 
154
- | Input | Does |
143
+ ### Options
144
+
145
+ All options are optional; the interactive prompt fills in the rest. Provide
146
+ `--quality` **and** `--url` to skip all prompts (see [Headless](#headless--scripting)).
147
+
148
+ | Option | Short | Values | Description |
149
+ | :--- | :--- | :--- | :--- |
150
+ | `--quality` | `-q` | `128` \| `320` \| `flac` | Download quality (falls back to a lower tier if unlicensed) |
151
+ | `--url` | `-u` | URL or [source](#sources) | What to download (positional also works: `gerdur <url>`) |
152
+ | `--output` | `-o` | template | Filename/path template — see [Output templates](#output-templates) |
153
+ | `--input-file` | `-i` | path | Download every URL / source listed in a text file (one per line) |
154
+ | `--concurrency` | `-c` | `1`–`50` | Parallel downloads for albums / artists / playlists (default 4) |
155
+ | `--overwrite` | `-w` | — | Re-download files that already exist (default: skip) |
156
+ | `--set-arl` | `-a` | `arl` string | Save your `arl` and exit |
157
+ | `--setup` | `-s` | — | Run guided setup (also `gerdur setup`) |
158
+ | `--headless` | `-d` | — | Never prompt — requires `--url` and `--quality`. For scripts/CI |
159
+ | `--config-file` | `-conf` | path | Use a config file at a custom location |
160
+ | `--create-playlist` | `-cp` | — | Also write an `.m3u8` for albums and artists |
161
+ | `--resolve-full-path` | `-rfp` | — | Use absolute paths inside generated playlist files |
162
+ | `--search` | | free text | Track search; combine with the filters below |
163
+ | `--artist` / `--album` / `--track` / `--label` | | text | Search filters |
164
+ | `--bpm-min` / `--bpm-max` | | number | Tempo range (BPM) |
165
+ | `--dur-min` / `--dur-max` | | seconds | Duration range |
166
+ | `--search-limit` | | number | How many results to fetch (default 50) |
167
+ | `--preview` | | — | Download 30-second `.preview.mp3` clips — no `--quality`, no `arl` |
168
+ | `--enrich` | | — | Embed a higher-res cover from the Cover Art Archive (by ISRC) |
169
+ | `--update` | `-U` | — | Self-update (prebuilt binary only) |
170
+ | `--help` | `-h` | — | Full help |
171
+
172
+ ### Sources
173
+
174
+ Anywhere a URL is accepted (`-u`, the interactive prompt, or a line in
175
+ `--input-file`) you can also use a prefixed source:
176
+
177
+ | Input | Downloads |
155
178
  | :--- | :--- |
156
- | `Harder Better Faster Stronger` | plain track search pick tracks to download |
157
- | `artist:daft punk` | artist search pick an artist, then its discography |
158
- | `album:discovery` | album search pick an album |
159
- | `playlist:deep focus` | playlist search pick a playlist |
160
- | `search:artist:"daft punk" bpm_min:120` | advanced track search (see operators below) |
161
- | `isrc:GBDUW0000059` | download the exact track for an ISRC |
162
- | `upc:0724384960650` | download the album for a UPC / EAN barcode |
163
- | `flow` | pick tracks from your Deezer **Flow** |
164
- | `flow:2064440442` | pick tracks from another user's Flow |
165
- | `radio:38305` | pick tracks from a radio's current playlist |
166
- | `chart` / `chart:132` | pick tracks from this week's chart (optionally a genre) |
167
- | `artist-top:27` | pick from an artist's most popular tracks |
168
- | `episode:294961882` | download a single podcast episode (plain MP3, to `Podcasts/`) |
169
-
170
- `isrc:` / `upc:` / `flow` / `radio:` also work in `--headless` mode (`-u flow`, …).
171
-
172
- ### From flags (works headless)
173
-
174
- The `--search` / `--artist` / `--album` / `--track` / `--label` / `--bpm-min` /
175
- `--bpm-max` / `--dur-min` / `--dur-max` flags are composed into one Deezer
176
- advanced-search query, the matches are listed, and — interactively — you pick
177
- which to download. In `--headless` mode every match (up to `--search-limit`) is
178
- downloaded.
179
+ | `https://deezer.com/...` | Deezer track / album / audiobook / playlist / artist (incl. `page.link` short links) |
180
+ | `https://open.spotify.com/...` · `spotify:...` | Spotify track / album / playlist / artist, matched to Deezer |
181
+ | `https://tidal.com/...` | Tidal track / album / playlist / artist, matched to Deezer |
182
+ | `https://youtu.be/...` | a single YouTube track, matched to Deezer |
183
+ | `Harder Better Faster Stronger` | plain track search pick what to download |
184
+ | `artist:daft punk` | artist search pick an artist → its discography |
185
+ | `album:discovery` | album search pick an album |
186
+ | `playlist:deep focus` | playlist search pick a playlist |
187
+ | `search:artist:"daft punk" bpm_min:120` | [advanced](#search) track search |
188
+ | `isrc:USUM71311296` | the exact track for an ISRC |
189
+ | `upc:0724384960650` | the album for a UPC / EAN barcode |
190
+ | `flow` · `flow:<userId>` | your Deezer Flow, or another user's |
191
+ | `radio:<id>` | a radio's current playlist |
192
+ | `chart` · `chart:<genreId>` | this week's chart (optionally a genre) |
193
+ | `artist-top:<artistId>` | an artist's most popular tracks |
194
+ | `episode:<episodeId>` | a single podcast episode (plain MP3, saved to `Podcasts/`) |
195
+
196
+ All of these work in `--headless` mode too (`gerdur -d -q 320 -u flow`).
197
+
198
+ ### Search
199
+
200
+ **From flags** (works headless) `--search` / `--artist` / `--album` /
201
+ `--track` / `--label` / `--bpm-min` / `--bpm-max` / `--dur-min` / `--dur-max`
202
+ are composed into one Deezer advanced-search query. Interactively you tick which
203
+ matches to download; headless grabs every match up to `--search-limit`.
179
204
 
180
205
  ```bash
181
- # interactive: search, then tick the tracks you want
182
- gerdur --artist "Daft Punk" --track "Around the World"
183
-
184
- # headless: grab the first 5 matches as FLAC
206
+ gerdur --artist "Daft Punk" --track "Around the World" # interactive
185
207
  gerdur -d -q flac --search "get lucky" --artist "Daft Punk" --search-limit 5
186
-
187
- # tempo / duration windows
188
208
  gerdur --artist "Justice" --bpm-min 120 --bpm-max 130 --dur-min 180
189
209
  ```
190
210
 
191
- Search hits are resolved to full tracks (via `getTrackInfo`) before downloading,
192
- so quality fallback, tagging and `.lrc` sidecars all work as normal.
211
+ **From the prompt** type `search:<query>` for the same advanced search, or a
212
+ plain phrase for a normal one. Matches are resolved to full tracks before
213
+ downloading, so quality fallback, tagging and `.lrc` sidecars all work.
193
214
 
194
- ### Advanced query operators
195
-
196
- Usable inside `--search`, after `search:`, or via `buildAdvancedQuery` in code.
197
- Deezer applies them as ranking hints (not hard filters), and they bite reliably
198
- only on **track** search:
215
+ **Advanced operators** — usable in `--search`, after `search:`, or via
216
+ `buildAdvancedQuery` in code. Deezer treats them as ranking hints (not hard
217
+ filters) and honours them reliably only on **track** search; an empty result is
218
+ retried as plain text.
199
219
 
200
220
  | Operator | Example |
201
221
  | :--- | :--- |
202
- | `artist:` | `artist:"daft punk"` |
203
- | `album:` | `album:"discovery"` |
204
- | `track:` | `track:"one more time"` |
205
- | `label:` | `label:"Virgin"` |
206
- | `dur_min:` / `dur_max:` | `dur_min:200` (seconds) |
207
- | `bpm_min:` / `bpm_max:` | `bpm_min:120` |
222
+ | `artist:` `album:` `track:` `label:` | `artist:"daft punk"` |
223
+ | `dur_min:` `dur_max:` | `dur_min:200` (seconds) |
224
+ | `bpm_min:` `bpm_max:` | `bpm_min:120` |
208
225
 
209
- ## Previews
226
+ ### Previews
210
227
 
211
228
  `--preview` writes the 30-second clip for each track as `<name>.preview.mp3`
212
- instead of downloading the full file. The clips are plain, licence-free MP3s
213
- no `--quality`, no decryption, no tagging. Works with every source (URL, search,
214
- `isrc:` / `upc:`) and in `--headless` mode.
229
+ instead of the full file plain, licence-free MP3s: no `--quality`, no
230
+ decryption, no tagging. Works with every source and headless.
215
231
 
216
232
  ```bash
217
233
  gerdur --preview -u https://deezer.com/album/302127 # 14 clips
218
234
  gerdur -d --preview --artist "Justice" --search-limit 10 # audition a search
219
235
  ```
220
236
 
221
- ## Better cover art (`--enrich`)
237
+ ### Better cover art (`--enrich`)
222
238
 
223
- `--enrich` looks each track's ISRC up on MusicBrainz, finds its release-group,
224
- and embeds the front cover from the [Cover Art Archive](https://coverartarchive.org)
225
- instead of Deezer's (which is capped at 1800 px). No match, or the services are
226
- down? It silently keeps Deezer's cover.
239
+ `--enrich` looks each track's ISRC up on MusicBrainz, walks its release-groups
240
+ canonical-first, and embeds the front cover from the
241
+ [Cover Art Archive](https://coverartarchive.org) instead of Deezer's (which caps
242
+ at 1800 px). No match or the services are down? It silently keeps Deezer's cover.
227
243
 
228
244
  ```bash
229
245
  gerdur --enrich -q flac -u https://deezer.com/album/302127
230
246
  ```
231
247
 
232
- The enrichment functions (`lookupRecordingByISRC`, `getBestCoverArtUrl`, …) are
233
- also re-exported for programmatic use. Read-only, off by default, and
234
- `gerdur-core` never wires them into tagging on its own.
248
+ ### Output templates
249
+
250
+ `--output` (or `saveLayout` in the config) is a path template. `{TOKEN}`s are
251
+ filled from the track/album; the correct extension is appended.
252
+
253
+ ```bash
254
+ gerdur -u <url> -o "Music/{ALB_TITLE}/{TRACK_NUMBER} {SNG_TITLE}"
255
+ gerdur -u <url> -o "{ART_NAME} - {SNG_TITLE}"
256
+ ```
257
+
258
+ | Token | Example |
259
+ | :--- | :--- |
260
+ | `{ALB_TITLE}` | `Discovery` |
261
+ | `{ART_NAME}` | `Daft Punk` |
262
+ | `{SNG_TITLE}` | `Harder, Better, Faster, Stronger` |
263
+ | `{TRACK_NUMBER}` | `04 - …` (forces a track-number prefix) |
264
+ | `{NO_TRACK_NUMBER}` | drop the track-number prefix |
265
+ | `{TITLE}` | playlist title (playlist downloads only) |
266
+
267
+ Full details, per-type layouts and every config key: **[docs/config.md](https://github.com/soulwax/gerdur/blob/main/docs/config.md)**.
268
+
269
+ ### Config file
270
+
271
+ `gerdur.config.json` — resolution order: `--config-file` →
272
+ `./gerdur.config.json` → global `~/.config/gerdur/gerdur.config.json`.
273
+
274
+ ```jsonc
275
+ {
276
+ "concurrency": 4,
277
+ "saveLayout": {
278
+ "track": "Music/{ALB_TITLE}/{SNG_TITLE}",
279
+ "album": "Music/{ALB_TITLE}/{SNG_TITLE}",
280
+ "artist": "Music/{ALB_TITLE}/{SNG_TITLE}",
281
+ "playlist": "Playlist/{TITLE}/{SNG_TITLE}"
282
+ },
283
+ "trackNumber": true,
284
+ "fallbackTrack": true,
285
+ "fallbackQuality": true,
286
+ "overwrite": false,
287
+ "coverSize": {"128": 500, "320": 500, "flac": 1000},
288
+ "cookies": {"arl": "xxx…"}
289
+ }
290
+ ```
291
+
292
+ ### Headless / scripting
293
+
294
+ Pass `--headless` (`-d`) with `--url` and `--quality` and `gerdur` never prompts —
295
+ it uses the configured `arl` (or `GERDUR_ARL`) and downloads. Piped / non-TTY
296
+ runs behave the same way.
297
+
298
+ ```bash
299
+ GERDUR_ARL=$ARL gerdur -d -q flac -u https://deezer.com/playlist/908622995 \
300
+ -o "Music/{ALB_TITLE}/{TRACK_NUMBER} {SNG_TITLE}" -c 8
301
+ ```
302
+
303
+ ---
235
304
 
236
305
  ## Programmatic API
237
306
 
238
- When installed as a dependency, `gerdur` exposes a side-effect-free API
239
- (importing it does **not** print the banner or parse CLI args, and nothing logs to the
240
- console — progress is delivered via callbacks). CommonJS and ESM both work.
307
+ `import`/`require` `gerdur` and it stays silent no banner, no `process.exit`,
308
+ no console output; progress arrives through callbacks. CommonJS and ESM both work.
241
309
 
242
310
  > **Full reference:** [docs/api.md](https://github.com/soulwax/gerdur/blob/main/docs/api.md) ·
243
311
  > **Runnable examples:** [examples/](https://github.com/soulwax/gerdur/tree/main/examples)
244
312
 
245
313
  ### High-level: sessions
246
314
 
247
- The quickest path from credentials to files. `createSession` logs in (with an `arl` or
248
- email/password), verifies the session, and returns helpers for querying and downloading:
315
+ `createSession` logs in (arl or email/password), verifies, and returns query +
316
+ download helpers:
249
317
 
250
318
  ```ts
251
319
  import {createSession} from 'gerdur';
252
320
 
253
321
  const session = await createSession({email: 'you@example.com', password: 'secret'});
254
- // ...or: createSession({arl: '...'})
322
+ // ...or: await createSession({arl: ''})
255
323
 
256
324
  // Resolve any Deezer / Spotify / Tidal URL and download it:
257
325
  await session.downloadUrl('https://deezer.com/album/302127', 'flac', {
@@ -261,56 +329,41 @@ await session.downloadUrl('https://deezer.com/album/302127', 'flac', {
261
329
  console.log(`${index + 1}/${total} ${track.SNG_TITLE} -> ${result?.path ?? 'unavailable'}`),
262
330
  });
263
331
 
264
- // Or work with the pieces:
265
- const {tracks} = await session.parseUrl('https://deezer.com/track/3135556');
266
- const found = await session.search('daft punk', ['TRACK'], 10);
332
+ // ...or work with the pieces:
333
+ const {tracks, linkinfo} = await session.parseUrl('https://deezer.com/track/3135556');
267
334
  const results = await session.downloadTracks(tracks, '320', {output: '{ART_NAME} - {SNG_TITLE}'});
268
335
 
269
- // Structured search against the public REST API (isrc / preview / bpm-aware):
270
- const hits = await session.searchAdvanced(
336
+ // Search:
337
+ const hits = await session.search('daft punk', ['TRACK'], 10);
338
+ const advanced = await session.searchAdvanced(
271
339
  {query: 'one more time', artist: 'daft punk', durMin: 200},
272
340
  {limit: 25, order: 'RANKING'},
273
341
  );
274
- const suggestions = await session.suggest('daft'); // autocomplete
342
+ const suggestions = await session.suggest('daf'); // autocomplete
343
+
344
+ // Get a tagged Buffer without touching disk:
345
+ const mp3 = await session.getTrackBuffer(tracks[0], '320');
275
346
  ```
276
347
 
277
- Session methods:
278
-
279
- - **resolve / search** `parseUrl`, `search`, `searchAdvanced`, `suggest`
280
- - **browse** `genres`, `chart`, `chartTracks`, `editorialSections`,
281
- `artistTopTracks`, `relatedArtists`, `artistAlbums`, `artistRadio`,
282
- `trackByISRC`, `albumByUPC`
283
- - **flow / library** — `flow`, `favoriteTracks`, `favoriteAlbums`,
284
- `favoriteArtists`, `playlists`, `userRadios`, `radios`, `radioTracks`
285
- (the `userId` arg defaults to the logged-in user)
286
- - **user / download** — `getUser`, `getTrackBuffer`, `streamTrack`,
287
- `downloadTrack`, `downloadTracks`, `downloadUrl`, `trackPreview`, `downloadPreview`
288
-
289
- Every download call is silent; `downloadTracks` / `downloadUrl` accept
290
- `concurrency` and an `onProgress` callback and return one `{path, written} | null`
291
- per track.
292
-
293
- `streamTrack(track, quality?, options?)` returns `{stream, size, startedAt,
294
- isEncrypted}` — decrypted audio at constant memory, with `onProgress(got, total)`
295
- and `resumeFrom` (bytes). Pipe `stream` to a file or your own tag muxer.
296
-
297
- `searchAdvanced(filters, options?)` builds a Deezer advanced-search query from
298
- `{query?, artist?, album?, track?, label?, durMin?, durMax?, bpmMin?, bpmMax?}`
299
- and takes `{order?, strict?, limit?, index?, fallback?}`. Deezer's operators are
300
- unreliable, so an empty result is retried as a plain free-text query unless
301
- `fallback: false`. It returns public-API track objects — fetch a hit's gw track
302
- with `getTrackInfo(id)` before downloading it.
348
+ | Group | Methods |
349
+ | :--- | :--- |
350
+ | resolve / search | `parseUrl`, `search`, `searchAdvanced`, `suggest` |
351
+ | browse | `genres`, `chart`, `chartTracks`, `editorialSections`, `artistTopTracks`, `relatedArtists`, `artistAlbums`, `artistRadio`, `trackByISRC`, `albumByUPC` |
352
+ | flow / library | `flow`, `favoriteTracks`, `favoriteAlbums`, `favoriteArtists`, `playlists`, `userRadios`, `radios`, `radioTracks` — the `userId` arg defaults to the logged-in user |
353
+ | download | `getTrackBuffer`, `streamTrack`, `downloadTrack`, `downloadTracks`, `downloadUrl`, `trackPreview`, `downloadPreview` |
354
+ | account | `getUser` |
303
355
 
304
- ```ts
305
- // Browse: this week's electro chart, download the top 5
306
- const {tracks} = await session.chart(106, 5); // 106 = "Dance" genre
307
- const full = await Promise.all(tracks.data.map((t) => getTrackInfo(String(t.id))));
308
- await session.downloadTracks(full, 'flac');
309
-
310
- // Find & grab an exact recording by barcode
311
- const t = await session.trackByISRC('GBDUW0000059');
312
- await session.downloadTrack(await getTrackInfo(String(t.id)), '320');
313
- ```
356
+ `downloadTracks` / `downloadUrl` take `concurrency` and `onProgress`, and return
357
+ one `{path, written, lrcPath?} | null` per track.
358
+
359
+ `streamTrack(track, quality?, opts?)` `{stream, size, startedAt, isEncrypted}` —
360
+ decrypted audio at constant memory, with `onProgress(received, total)` and
361
+ `resumeFrom` (bytes). Pipe `stream` to a file or your own tag muxer.
362
+
363
+ `searchAdvanced(filters, opts?)` returns **public-API** track objects — fetch a
364
+ hit's gw track with `getTrackInfo(String(hit.id))` before downloading it. Deezer's
365
+ operators are unreliable, so an empty result is retried as free text unless you
366
+ pass `{fallback: false}`.
314
367
 
315
368
  ### Low-level: primitives
316
369
 
@@ -322,44 +375,32 @@ await initDeezerApi(arl);
322
375
 
323
376
  const track = await getTrackInfo('3135556');
324
377
 
325
- // Get the tagged audio as a Buffer without touching the disk:
326
- const mp3 = await getTrackBuffer(track, '320');
327
-
328
- // ...or write it straight to a file:
329
- const {path, written} = (await downloadTrackToFile(track, 'flac', {output: '{ART_NAME} - {SNG_TITLE}'}))!;
378
+ const mp3 = await getTrackBuffer(track, '320'); // tagged Buffer, no disk I/O
379
+ const {path, written} = (await downloadTrackToFile(track, 'flac', {
380
+ output: '{ART_NAME} - {SNG_TITLE}',
381
+ }))!; // ...or write it
330
382
  ```
331
383
 
384
+ `getTaggedTrack(track, quality, opts?)` is like `getTrackBuffer` but returns
385
+ `{buffer, model}` — `model.lyricsSynced` is an LRC document for a `.lrc` sidecar.
386
+
332
387
  ### Re-exported query functions
333
388
 
334
- So you don't need `gerdur-core` as a second dependency (call after `initDeezerApi`
335
- or `createSession`):
336
-
337
- - **Query** `parseInfo`, `getUser`, `getTrackInfo`, `getAlbumInfo`,
338
- `getAlbumTracks`, `getPlaylistInfo`, `getPlaylistTracks`, `getArtistInfo`,
339
- `getDiscography`, `getLyrics`
340
- - **Search** `searchMusic`, `searchFacets`, `searchPublicApi`, `searchTracks`,
341
- `searchAlbums`, `searchArtists`, `searchPlaylists`, `buildAdvancedQuery`, `suggest`
342
- - **Browse** `getGenres`, `getChart`, `getChartTracks`, `getGenreArtists`,
343
- `getEditorialList`, `getEditorialReleases`, `getEditorialSelection`,
344
- `getEditorialCharts`, `getArtistTopTracks`, `getRelatedArtists`,
345
- `getArtistAlbums`, `getArtistPlaylists`, `getArtistRadioTracks`,
346
- `getTrackByISRC`, `getAlbumByUPC`
347
- - **Flow / library** — `getUserFlow`, `getUserFavoriteTracks`,
348
- `getUserFavoriteAlbums`, `getUserFavoriteArtists`, `getUserPlaylists`,
349
- `getUserRadios`, `getUserChartTracks`, `getRadios`, `getRadioTracks`,
350
- `getRadioGenres`, `getEpisode`, `getShowEpisodes`
351
- - **Download** — `getTrackDownloadUrl`, `resolveDownloadUrls`,
352
- `refreshTrackTokens`, `streamTrackDownload`, `downloadTrackBuffer`,
353
- `createDecryptStream`, `getStream`, `getTrackPreview`, `downloadPreview`,
354
- `formatName`, `toFormat`, `DEEZER_FORMATS`
355
- - **Errors** — `DeezerError` (`code` / `keys` / `retryable`), `GeoBlocked`
356
- - **Low-level sessions** — `createCoreSession(arl)` / `CoreSession` /
357
- `defaultSession` from `gerdur-core`: an isolated client (its own `arl`, cache,
358
- `license_token`) with `getTrackInfo` / `searchMusic` / `getTrackBuffer` / … for
359
- talking to Deezer directly or from multiple accounts. gerdur's own
360
- `createSession` / `Session` (above) is the higher-level download orchestrator.
361
-
362
- ### Auth & config helpers
389
+ So you don't need `gerdur-core` as a second dependency (call after
390
+ `initDeezerApi` or `createSession`):
391
+
392
+ | Group | Functions |
393
+ | :--- | :--- |
394
+ | query | `parseInfo`, `getUser`, `getTrackInfo`, `getAlbumInfo`, `getAlbumTracks`, `getPlaylistInfo`, `getPlaylistTracks`, `getArtistInfo`, `getDiscography`, `getLyrics` |
395
+ | search | `searchMusic`, `searchFacets`, `searchPublicApi`, `searchTracks`, `searchAlbums`, `searchArtists`, `searchPlaylists`, `buildAdvancedQuery`, `suggest` |
396
+ | browse | `getGenres`, `getChart`, `getChartTracks`, `getGenreArtists`, `getEditorialList`, `getEditorialReleases`, `getEditorialSelection`, `getEditorialCharts`, `getArtistTopTracks`, `getRelatedArtists`, `getArtistAlbums`, `getArtistPlaylists`, `getArtistRadioTracks`, `getTrackByISRC`, `getAlbumByUPC` |
397
+ | flow / library | `getUserFlow`, `getUserFavoriteTracks`, `getUserFavoriteAlbums`, `getUserFavoriteArtists`, `getUserPlaylists`, `getUserRadios`, `getUserChartTracks`, `getRadios`, `getRadioTracks`, `getRadioGenres`, `getEpisode`, `getShowEpisodes` |
398
+ | download | `getTrackDownloadUrl`, `resolveDownloadUrls`, `refreshTrackTokens`, `streamTrackDownload`, `downloadTrackBuffer`, `createDecryptStream`, `getStream`, `getTrackPreview`, `downloadPreview`, `formatName`, `toFormat`, `DEEZER_FORMATS` |
399
+ | enrichment | `configureMusicBrainz`, `lookupRecordingByISRC`, `getMusicBrainzRecording`, `getMusicBrainzRelease`, `getCoverArt`, `getBestCoverArtUrl`, `getRecordingCoverArt`, `getCoverArtByISRC`, `PoliteJsonClient` |
400
+ | tagging | `addTrackTags`, `getRichAlbum`, `normalizeContributors`, `toLrc` |
401
+ | errors | `DeezerError` (`code` / `keys` / `retryable`), `GeoBlocked` |
402
+
403
+ ### Auth &amp; config helpers
363
404
 
364
405
  ```ts
365
406
  import {loginWithEmail, LoginError, Config, globalConfigPath} from 'gerdur';
@@ -368,17 +409,60 @@ const result = await loginWithEmail('you@example.com', 'password');
368
409
  if (result.ok) console.log(result.arl);
369
410
  else console.error(result.reason, result.message); // 'wrong-credentials' | 'no-arl' | 'network' | 'unknown'
370
411
 
371
- const conf = new Config(); // same config file the CLI uses
372
- conf.set('cookies.arl', result.ok ? result.arl : '');
412
+ const conf = new Config(); // the same config the CLI uses
413
+ conf.set('cookies.arl', 'xxx…');
373
414
  ```
374
415
 
375
- Passwords passed to these functions are used only to authenticate and are never written
376
- to disk or logged; only the resulting `arl` is returned or stored.
416
+ Passwords passed to these functions authenticate only never written to disk or
417
+ logged; only the resulting `arl` is returned or stored.
418
+
419
+ ### Multiple accounts
377
420
 
378
- ### Disclaimer
421
+ gerdur's `Session` uses one process-wide login. For **concurrent** accounts, use
422
+ `gerdur-core`'s low-level session (re-exported), each fully isolated:
423
+
424
+ ```ts
425
+ import {createCoreSession} from 'gerdur';
379
426
 
380
- > I am not responsible in any way for the usage of others.
427
+ const a = await createCoreSession(arlOne);
428
+ const b = await createCoreSession(arlTwo);
429
+ const track = await a.getTrackInfo('3135556');
430
+ const audio = await a.getTrackBuffer(track, 9); // resolved + decrypted as account a
431
+ ```
381
432
 
382
433
  ---
383
434
 
384
- > Made with :heart: by the Bluesix Team. If you want to contribute, please read the [contributing guidelines](.github/CONTRIBUTING.md) first.
435
+ ## Supported services
436
+
437
+ | Service | Track | Album | Playlist | Artist | Notes |
438
+ | :--- | :-: | :-: | :-: | :-: | :--- |
439
+ | **Deezer** | ✅ | ✅ | ✅ | ✅ | audiobooks and `page.link` short links too |
440
+ | **Spotify** | ✅ | ✅ | ✅ | ✅ | matched to Deezer by ISRC / UPC; artist capped at ~10 tracks |
441
+ | **Tidal** | ✅ | ✅ | ✅ | ✅ | matched to Deezer by ISRC / UPC |
442
+ | **YouTube** | ✅ | | | | single tracks, matched to Deezer |
443
+
444
+ Quality is `128` / `320` kbps MP3 or FLAC, subject to your Deezer plan and the
445
+ track's licensing; `gerdur` falls back to a lower tier (and to a same-artist
446
+ alternate track) rather than failing.
447
+
448
+ The engine — API clients, URL resolution, decryption and tagging — is
449
+ [`gerdur-core`](https://www.npmjs.com/package/gerdur-core), published separately.
450
+
451
+ ## The name
452
+
453
+ **Gerðr** is the jötunn Freyr sends Skírnir riding through a wall of fire to
454
+ fetch. Her name is *garðr* — "the enclosure, the walled garden" (English
455
+ *garden*, *garth*). A track behind a service is Gerðr; `gerdur` rides through the
456
+ fire and brings it home into your own *garðr*: decrypted, tagged, and yours to
457
+ keep.
458
+
459
+ ## Legal
460
+
461
+ For personal and archival use with content you are entitled to access. You are
462
+ responsible for complying with the terms of service of any provider and with
463
+ copyright law in your jurisdiction. The authors accept no liability for how the
464
+ software is used. Buy the music you love.
465
+
466
+ See [LICENSE](LICENSE) · [Contributing](.github/CONTRIBUTING.md) ·
467
+ [Code of Conduct](.github/CODE_OF_CONDUCT.md) ·
468
+ [Issues](https://github.com/soulwax/gerdur/issues)
package/dist/package.json CHANGED
@@ -1,7 +1,22 @@
1
1
  {
2
2
  "name": "gerdur",
3
- "version": "2.11.0",
4
- "description": "The crucial streaming module for dabox systems.",
3
+ "version": "2.11.1",
4
+ "description": "Command-line music downloader for Deezer (Spotify/Tidal links resolved via ISRC matching) with automatic MP3/FLAC tagging, synced lyrics and a side-effect-free programmatic API.",
5
+ "keywords": [
6
+ "deezer",
7
+ "deezer-downloader",
8
+ "music-downloader",
9
+ "spotify",
10
+ "tidal",
11
+ "cli",
12
+ "flac",
13
+ "mp3",
14
+ "id3",
15
+ "lyrics",
16
+ "lrc",
17
+ "isrc",
18
+ "metadata"
19
+ ],
5
20
  "author": "Christian",
6
21
  "license": "SEE LICENSE IN LICENSE",
7
22
  "scripts": {
package/package.json CHANGED
@@ -1,7 +1,22 @@
1
1
  {
2
2
  "name": "gerdur",
3
- "version": "2.11.0",
4
- "description": "The crucial streaming module for dabox systems.",
3
+ "version": "2.11.1",
4
+ "description": "Command-line music downloader for Deezer (Spotify/Tidal links resolved via ISRC matching) with automatic MP3/FLAC tagging, synced lyrics and a side-effect-free programmatic API.",
5
+ "keywords": [
6
+ "deezer",
7
+ "deezer-downloader",
8
+ "music-downloader",
9
+ "spotify",
10
+ "tidal",
11
+ "cli",
12
+ "flac",
13
+ "mp3",
14
+ "id3",
15
+ "lyrics",
16
+ "lrc",
17
+ "isrc",
18
+ "metadata"
19
+ ],
5
20
  "author": "Christian",
6
21
  "license": "SEE LICENSE IN LICENSE",
7
22
  "scripts": {