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 +14 -0
- package/README.md +340 -256
- package/dist/package.json +17 -2
- package/package.json +17 -2
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
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/gerdur)
|
|
8
|
+
[](https://www.npmjs.com/package/gerdur)
|
|
9
|
+
[](https://www.npmjs.com/package/gerdur)
|
|
7
10
|
|
|
8
|
-
|
|
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
|
-
|
|
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
|
+

|
|
24
14
|
|
|
25
|
-
|
|
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
|
-
|
|
19
|
+
---
|
|
28
20
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
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 & 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
|
-
|
|
45
|
+
---
|
|
36
46
|
|
|
37
47
|
## Install
|
|
38
48
|
|
|
39
|
-
|
|
49
|
+
**As a global CLI** (needs [Node](https://nodejs.org) 18+):
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
59
|
+
```bash
|
|
60
|
+
pnpm add -g gerdur
|
|
61
|
+
```
|
|
66
62
|
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
72
|
+
## First run — your `arl`
|
|
76
73
|
|
|
77
|
-
|
|
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
|
-
|
|
77
|
+
**Guided setup** (any time):
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
```bash
|
|
80
|
+
gerdur setup # same as: gerdur --setup
|
|
81
|
+
```
|
|
83
82
|
|
|
84
|
-
|
|
83
|
+
**Paste an `arl` directly:**
|
|
85
84
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
```bash
|
|
86
|
+
gerdur --set-arl <your_arl>
|
|
87
|
+
```
|
|
89
88
|
|
|
90
|
-
|
|
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
|
-
|
|
93
|
-
|
|
92
|
+
**Log in with email & 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
|
-
|
|
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
|
-
|
|
102
|
+
```bash
|
|
103
|
+
GERDUR_ARL=<your_arl> gerdur -d -q 320 -u https://deezer.com/album/302127
|
|
104
|
+
```
|
|
98
105
|
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
111
|
+
---
|
|
103
112
|
|
|
104
|
-
|
|
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
|
-
|
|
110
|
-
> exactly as before and simply use the configured `arl` (or `GERDUR_ARL`).
|
|
115
|
+
### Common tasks
|
|
111
116
|
|
|
112
|
-
|
|
117
|
+
```bash
|
|
118
|
+
# Interactive — paste a URL or type a search term, then pick what to download
|
|
119
|
+
gerdur
|
|
113
120
|
|
|
114
|
-
|
|
121
|
+
# A single track / album / playlist / artist URL
|
|
122
|
+
gerdur -u https://deezer.com/album/302127 -q flac
|
|
115
123
|
|
|
116
|
-
|
|
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
|
-
|
|
127
|
+
# Many URLs from a file, 8 at a time
|
|
128
|
+
gerdur -i links.txt -c 8 -q 320
|
|
119
129
|
|
|
120
|
-
|
|
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
|
-
|
|
133
|
+
# Grab an exact recording by barcode, no prompts
|
|
134
|
+
gerdur -d -q flac -u isrc:USUM71311296
|
|
149
135
|
|
|
150
|
-
|
|
136
|
+
# Your Deezer Flow
|
|
137
|
+
gerdur -u flow
|
|
151
138
|
|
|
152
|
-
|
|
139
|
+
# 30-second previews instead of full tracks (no arl needed)
|
|
140
|
+
gerdur --preview -u https://deezer.com/album/302127
|
|
141
|
+
```
|
|
153
142
|
|
|
154
|
-
|
|
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
|
-
| `
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
| `
|
|
204
|
-
| `
|
|
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
|
-
|
|
226
|
+
### Previews
|
|
210
227
|
|
|
211
228
|
`--preview` writes the 30-second clip for each track as `<name>.preview.mp3`
|
|
212
|
-
instead of
|
|
213
|
-
|
|
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
|
-
|
|
237
|
+
### Better cover art (`--enrich`)
|
|
222
238
|
|
|
223
|
-
`--enrich` looks each track's ISRC up on MusicBrainz,
|
|
224
|
-
and embeds the front cover from the
|
|
225
|
-
instead of Deezer's (which
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
`
|
|
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
|
-
|
|
239
|
-
|
|
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
|
-
|
|
248
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
270
|
-
const hits = await session.
|
|
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('
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
// ...or write it
|
|
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
|
|
335
|
-
or `createSession`):
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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 & 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();
|
|
372
|
-
conf.set('cookies.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
|
|
376
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
4
|
-
"description": "
|
|
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.
|
|
4
|
-
"description": "
|
|
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": {
|