gerdur 2.2.0 → 2.4.0
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 +29 -0
- package/README.md +53 -10
- package/dist/package.json +2 -2
- package/dist/src/gerdur.js +117 -52
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +22 -1
- package/dist/src/lib/session.d.ts +26 -1
- package/dist/src/lib/session.js +49 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.4.0 - 2026-08-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`gerdur-core@^2.3.0`** — re-exports `getTrackPreview`, `downloadPreview`,
|
|
8
|
+
`formatName`, `toFormat`, `DEEZER_FORMATS` and the `DeezerFormat` /
|
|
9
|
+
`TrackPreview` types.
|
|
10
|
+
- **`--preview`** — download the 30-second clip for each track as
|
|
11
|
+
`<name>.preview.mp3` instead of the full file. Plain licence-free MP3s: no
|
|
12
|
+
`--quality`, no decryption, no tagging. Works with every source and headless.
|
|
13
|
+
- **`Session.trackPreview(track)`** and **`Session.downloadPreview(track)`**.
|
|
14
|
+
|
|
15
|
+
## 2.3.0 - 2026-08-31
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`gerdur-core@^2.2.0`** — the browse / discovery surface is re-exported:
|
|
20
|
+
`getGenres`, `getChart`, `getChartTracks`, `getGenreArtists`,
|
|
21
|
+
`getEditorialList` / `getEditorialReleases` / `getEditorialSelection` /
|
|
22
|
+
`getEditorialCharts`, `getArtistTopTracks`, `getRelatedArtists`,
|
|
23
|
+
`getArtistAlbums`, `getArtistPlaylists`, `getArtistRadioTracks`,
|
|
24
|
+
`getTrackByISRC`, `getAlbumByUPC`, plus their types.
|
|
25
|
+
- **`Session` browse methods**: `genres`, `chart`, `chartTracks`,
|
|
26
|
+
`editorialSections`, `artistTopTracks`, `relatedArtists`, `artistAlbums`,
|
|
27
|
+
`artistRadio`, `trackByISRC`, `albumByUPC`.
|
|
28
|
+
- **CLI `isrc:` / `upc:` prefixes** — `gerdur -u isrc:GBDUW0000059` downloads the
|
|
29
|
+
exact track for an ISRC; `gerdur -u upc:0724384960650` downloads the album for
|
|
30
|
+
a barcode. Both work in `--headless` mode and from the interactive prompt.
|
|
31
|
+
|
|
3
32
|
## 2.2.0 - 2026-08-31
|
|
4
33
|
|
|
5
34
|
### Added
|
package/README.md
CHANGED
|
@@ -142,12 +142,13 @@ All options are optional. You can suppress prompts via providing `quality` and `
|
|
|
142
142
|
| `--bpm-min` / `--bpm-max` | *None* | number | Search filter — tempo range (beats per minute) |
|
|
143
143
|
| `--dur-min` / `--dur-max` | *None* | number (seconds) | Search filter — track duration range |
|
|
144
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 |
|
|
145
146
|
|
|
146
147
|
## Search
|
|
147
148
|
|
|
148
149
|
### From the interactive prompt
|
|
149
150
|
|
|
150
|
-
When `gerdur` asks for
|
|
151
|
+
When `gerdur` asks for a URL, a search term, or a prefixed query:
|
|
151
152
|
|
|
152
153
|
| Input | Does |
|
|
153
154
|
| :--- | :--- |
|
|
@@ -156,6 +157,10 @@ When `gerdur` asks for *"a URL, a search term, or `search:<advanced query>`"*:
|
|
|
156
157
|
| `album:discovery` | album search — pick an album |
|
|
157
158
|
| `playlist:deep focus` | playlist search — pick a playlist |
|
|
158
159
|
| `search:artist:"daft punk" bpm_min:120` | advanced track search (see operators below) |
|
|
160
|
+
| `isrc:GBDUW0000059` | download the exact track for an ISRC |
|
|
161
|
+
| `upc:0724384960650` | download the album for a UPC / EAN barcode |
|
|
162
|
+
|
|
163
|
+
`isrc:` and `upc:` also work in `--headless` mode (`-u isrc:…` / `-u upc:…`).
|
|
159
164
|
|
|
160
165
|
### From flags (works headless)
|
|
161
166
|
|
|
@@ -194,6 +199,18 @@ only on **track** search:
|
|
|
194
199
|
| `dur_min:` / `dur_max:` | `dur_min:200` (seconds) |
|
|
195
200
|
| `bpm_min:` / `bpm_max:` | `bpm_min:120` |
|
|
196
201
|
|
|
202
|
+
## Previews
|
|
203
|
+
|
|
204
|
+
`--preview` writes the 30-second clip for each track as `<name>.preview.mp3`
|
|
205
|
+
instead of downloading the full file. The clips are plain, licence-free MP3s —
|
|
206
|
+
no `--quality`, no decryption, no tagging. Works with every source (URL, search,
|
|
207
|
+
`isrc:` / `upc:`) and in `--headless` mode.
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
gerdur --preview -u https://deezer.com/album/302127 # 14 clips
|
|
211
|
+
gerdur -d --preview --artist "Justice" --search-limit 10 # audition a search
|
|
212
|
+
```
|
|
213
|
+
|
|
197
214
|
## Programmatic API
|
|
198
215
|
|
|
199
216
|
When installed as a dependency, `gerdur` exposes a side-effect-free API
|
|
@@ -235,10 +252,18 @@ const hits = await session.searchAdvanced(
|
|
|
235
252
|
const suggestions = await session.suggest('daft'); // autocomplete
|
|
236
253
|
```
|
|
237
254
|
|
|
238
|
-
Session methods:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
255
|
+
Session methods:
|
|
256
|
+
|
|
257
|
+
- **resolve / search** — `parseUrl`, `search`, `searchAdvanced`, `suggest`
|
|
258
|
+
- **browse** — `genres`, `chart`, `chartTracks`, `editorialSections`,
|
|
259
|
+
`artistTopTracks`, `relatedArtists`, `artistAlbums`, `artistRadio`,
|
|
260
|
+
`trackByISRC`, `albumByUPC`
|
|
261
|
+
- **user / download** — `getUser`, `getTrackBuffer`, `downloadTrack`,
|
|
262
|
+
`downloadTracks`, `downloadUrl`, `trackPreview`, `downloadPreview`
|
|
263
|
+
|
|
264
|
+
Every download call is silent; `downloadTracks` / `downloadUrl` accept
|
|
265
|
+
`concurrency` and an `onProgress` callback and return one `{path, written} | null`
|
|
266
|
+
per track.
|
|
242
267
|
|
|
243
268
|
`searchAdvanced(filters, options?)` builds a Deezer advanced-search query from
|
|
244
269
|
`{query?, artist?, album?, track?, label?, durMin?, durMax?, bpmMin?, bpmMax?}`
|
|
@@ -247,6 +272,17 @@ unreliable, so an empty result is retried as a plain free-text query unless
|
|
|
247
272
|
`fallback: false`. It returns public-API track objects — fetch a hit's gw track
|
|
248
273
|
with `getTrackInfo(id)` before downloading it.
|
|
249
274
|
|
|
275
|
+
```ts
|
|
276
|
+
// Browse: this week's electro chart, download the top 5
|
|
277
|
+
const {tracks} = await session.chart(106, 5); // 106 = "Dance" genre
|
|
278
|
+
const full = await Promise.all(tracks.data.map((t) => getTrackInfo(String(t.id))));
|
|
279
|
+
await session.downloadTracks(full, 'flac');
|
|
280
|
+
|
|
281
|
+
// Find & grab an exact recording by barcode
|
|
282
|
+
const t = await session.trackByISRC('GBDUW0000059');
|
|
283
|
+
await session.downloadTrack(await getTrackInfo(String(t.id)), '320');
|
|
284
|
+
```
|
|
285
|
+
|
|
250
286
|
### Low-level: primitives
|
|
251
287
|
|
|
252
288
|
```ts
|
|
@@ -269,11 +305,18 @@ const {path, written} = (await downloadTrackToFile(track, 'flac', {output: '{ART
|
|
|
269
305
|
So you don't need `gerdur-core` as a second dependency (call after `initDeezerApi`
|
|
270
306
|
or `createSession`):
|
|
271
307
|
|
|
272
|
-
`parseInfo`, `
|
|
273
|
-
`
|
|
274
|
-
`
|
|
275
|
-
`
|
|
276
|
-
`
|
|
308
|
+
- **Query** — `parseInfo`, `getUser`, `getTrackInfo`, `getAlbumInfo`,
|
|
309
|
+
`getAlbumTracks`, `getPlaylistInfo`, `getPlaylistTracks`, `getArtistInfo`,
|
|
310
|
+
`getDiscography`, `getLyrics`
|
|
311
|
+
- **Search** — `searchMusic`, `searchPublicApi`, `searchTracks`, `searchAlbums`,
|
|
312
|
+
`searchArtists`, `searchPlaylists`, `buildAdvancedQuery`, `suggest`
|
|
313
|
+
- **Browse** — `getGenres`, `getChart`, `getChartTracks`, `getGenreArtists`,
|
|
314
|
+
`getEditorialList`, `getEditorialReleases`, `getEditorialSelection`,
|
|
315
|
+
`getEditorialCharts`, `getArtistTopTracks`, `getRelatedArtists`,
|
|
316
|
+
`getArtistAlbums`, `getArtistPlaylists`, `getArtistRadioTracks`,
|
|
317
|
+
`getTrackByISRC`, `getAlbumByUPC`
|
|
318
|
+
- **Download** — `getTrackDownloadUrl`, `resolveDownloadUrls`, `getTrackPreview`,
|
|
319
|
+
`downloadPreview`, `formatName`, `toFormat`, `DEEZER_FORMATS`, `GeoBlocked`
|
|
277
320
|
|
|
278
321
|
### Auth & config helpers
|
|
279
322
|
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gerdur",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "The crucial streaming module for dabox systems.",
|
|
5
5
|
"author": "Christian",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"adm-zip": "^0.5.16",
|
|
49
49
|
"chalk": "^4.1.2",
|
|
50
50
|
"commander": "^9.5.0",
|
|
51
|
-
"gerdur-core": "^2.
|
|
51
|
+
"gerdur-core": "^2.3.0",
|
|
52
52
|
"dot-prop": "^6.0.1",
|
|
53
53
|
"got": "^11.8.6",
|
|
54
54
|
"gradient-string": "^2.0.2",
|
package/dist/src/gerdur.js
CHANGED
|
@@ -57,7 +57,8 @@ const cmd = new commander_1.Command()
|
|
|
57
57
|
.option('--bpm-max <n>', 'Search filter: maximum BPM')
|
|
58
58
|
.option('--dur-min <seconds>', 'Search filter: minimum duration')
|
|
59
59
|
.option('--dur-max <seconds>', 'Search filter: maximum duration')
|
|
60
|
-
.option('--search-limit <n>', 'Max search results to fetch', '50')
|
|
60
|
+
.option('--search-limit <n>', 'Max search results to fetch', '50')
|
|
61
|
+
.option('--preview', 'Download the 30-second preview clips instead of full tracks', false);
|
|
61
62
|
if (process.pkg) {
|
|
62
63
|
cmd.option('-U, --update', 'Update this program to latest version');
|
|
63
64
|
}
|
|
@@ -79,7 +80,7 @@ const toFiniteNumber = (value) => {
|
|
|
79
80
|
};
|
|
80
81
|
const advancedFilters = (0, search_1.advancedFiltersFromFlags)(options);
|
|
81
82
|
let advancedFiltersConsumed = false;
|
|
82
|
-
if (options.headless && !options.quality) {
|
|
83
|
+
if (options.headless && !options.quality && !options.preview) {
|
|
83
84
|
console.error(signale_1.default.error('Missing parameters --quality'));
|
|
84
85
|
console.error(signale_1.default.note('Quality must be provided with headless mode'));
|
|
85
86
|
process.exit(1);
|
|
@@ -152,10 +153,48 @@ const resolveAdvancedSearch = async (filters) => {
|
|
|
152
153
|
}
|
|
153
154
|
return { info: { type: 'track', id: query }, linktype: 'track', linkinfo: {}, tracks };
|
|
154
155
|
};
|
|
156
|
+
/**
|
|
157
|
+
* `--preview`: fetch the 30-second clip for each selected track and write it as
|
|
158
|
+
* `<saveLayout path>.preview.mp3`. Bypasses the get_url / decrypt / tag pipeline
|
|
159
|
+
* entirely — previews are plain, licence-free MP3s.
|
|
160
|
+
*/
|
|
161
|
+
const downloadPreviews = async (tracks, info, template, totalTracks, trackNumber, overwrite) => {
|
|
162
|
+
const saved = [];
|
|
163
|
+
await queue.addAll(tracks.map((track, index) => async () => {
|
|
164
|
+
const rel = (0, util_1.saveLayout)({
|
|
165
|
+
track,
|
|
166
|
+
album: info,
|
|
167
|
+
path: template,
|
|
168
|
+
trackNumber,
|
|
169
|
+
minimumIntegerDigits: totalTracks >= 100 ? 3 : 2,
|
|
170
|
+
}) + '.preview.mp3';
|
|
171
|
+
(0, log_update_1.default)(signale_1.default.pending(`(${index + 1}/${tracks.length}) ${track.ART_NAME} - ${track.SNG_TITLE}`));
|
|
172
|
+
if (!overwrite && (0, fs_1.existsSync)(rel)) {
|
|
173
|
+
saved.push(rel);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const clip = await (0, gerdur_core_1.downloadPreview)(track);
|
|
178
|
+
if (!clip) {
|
|
179
|
+
(0, log_update_1.default)(signale_1.default.warn(`No preview for ${track.SNG_TITLE}`));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(rel), { recursive: true });
|
|
183
|
+
(0, fs_1.writeFileSync)(rel, clip);
|
|
184
|
+
saved.push(rel);
|
|
185
|
+
(0, log_update_1.default)(signale_1.default.success(`${track.ART_NAME} - ${track.SNG_TITLE} (preview)`));
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
(0, log_update_1.default)(signale_1.default.error(`${track.SNG_TITLE}: ${err.message}`));
|
|
189
|
+
}
|
|
190
|
+
}));
|
|
191
|
+
log_update_1.default.done();
|
|
192
|
+
return saved;
|
|
193
|
+
};
|
|
155
194
|
const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
156
195
|
try {
|
|
157
196
|
url = url ?? '';
|
|
158
|
-
if (!options.quality) {
|
|
197
|
+
if (!options.quality && !options.preview) {
|
|
159
198
|
const { musicQuality } = await (0, prompts_1.default)([
|
|
160
199
|
{
|
|
161
200
|
type: 'select',
|
|
@@ -189,7 +228,7 @@ const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
|
189
228
|
{
|
|
190
229
|
type: 'text',
|
|
191
230
|
name: 'query',
|
|
192
|
-
message: 'Enter a URL, a search term, or `search
|
|
231
|
+
message: 'Enter a URL, a search term, or a `search:` / `isrc:` / `upc:` query:',
|
|
193
232
|
validate: (value) => (value ? true : false),
|
|
194
233
|
},
|
|
195
234
|
], { onCancel });
|
|
@@ -202,6 +241,25 @@ const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
|
202
241
|
throw new Error('No tracks matched that search.');
|
|
203
242
|
}
|
|
204
243
|
}
|
|
244
|
+
// `isrc:` / `upc:` — resolve a barcode to a Deezer track / album (works headless).
|
|
245
|
+
if (!searchData && url && url.startsWith('isrc:')) {
|
|
246
|
+
const code = url.slice('isrc:'.length).trim();
|
|
247
|
+
const found = await (0, gerdur_core_1.getTrackByISRC)(code).catch(() => null);
|
|
248
|
+
if (!found) {
|
|
249
|
+
throw new Error(`No Deezer track for ISRC ${code}`);
|
|
250
|
+
}
|
|
251
|
+
const track = await (0, gerdur_core_1.getTrackInfo)(String(found.id));
|
|
252
|
+
searchData = { info: { type: 'track', id: code }, linktype: 'track', linkinfo: {}, tracks: [stampVersion(track)] };
|
|
253
|
+
}
|
|
254
|
+
if (!searchData && url && url.startsWith('upc:')) {
|
|
255
|
+
const code = url.slice('upc:'.length).trim();
|
|
256
|
+
const found = await (0, gerdur_core_1.getAlbumByUPC)(code).catch(() => null);
|
|
257
|
+
if (!found) {
|
|
258
|
+
throw new Error(`No Deezer album for UPC ${code}`);
|
|
259
|
+
}
|
|
260
|
+
console.log(signale_1.default.info(`UPC ${code} → ${found.title} (${found.nb_tracks} tracks)`));
|
|
261
|
+
url = `https://www.deezer.com/album/${found.id}`;
|
|
262
|
+
}
|
|
205
263
|
if (!searchData && !url.match(urlRegex)) {
|
|
206
264
|
if (options.headless) {
|
|
207
265
|
throw new Error('Please provide a valid URL. Unknown URL: ' + url);
|
|
@@ -308,56 +366,63 @@ const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
|
308
366
|
const resolveFullPath = options.resolveFullPath ?? conf.get('playlist.resolveFullPath');
|
|
309
367
|
const savedFiles = [];
|
|
310
368
|
let m3u8 = [];
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
'128': [1],
|
|
316
|
-
'3': [3, 1],
|
|
317
|
-
'320': [3, 1],
|
|
318
|
-
'9': [9, 3, 1],
|
|
319
|
-
flac: [9, 3, 1],
|
|
320
|
-
};
|
|
321
|
-
const prefetchedUrls = new Map();
|
|
322
|
-
if (data.tracks.length > 1 && !process.env.SIMULATE) {
|
|
323
|
-
try {
|
|
324
|
-
const base = QUALITY_PREF[String(options.quality).toLowerCase()] ?? [3, 1];
|
|
325
|
-
const pref = fallbackQuality ? base : [base[0]];
|
|
326
|
-
const resolved = await (0, gerdur_core_1.resolveDownloadUrls)(data.tracks, pref);
|
|
327
|
-
resolved.forEach((r, i) => {
|
|
328
|
-
if (r) {
|
|
329
|
-
prefetchedUrls.set(data.tracks[i].SNG_ID, r);
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
catch {
|
|
334
|
-
// batch resolve failed wholesale — every track falls back to per-track resolution
|
|
335
|
-
}
|
|
369
|
+
const layoutTemplate = options.output ? options.output : saveLayout[data.linktype];
|
|
370
|
+
if (options.preview) {
|
|
371
|
+
const clips = await downloadPreviews(data.tracks, data.linkinfo, layoutTemplate, data.tracks.length, trackNumber, overwrite);
|
|
372
|
+
savedFiles.push(...clips);
|
|
336
373
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
fallbackTrack,
|
|
348
|
-
fallbackQuality,
|
|
349
|
-
overwrite,
|
|
350
|
-
message: `(${index}/${data.tracks.length})`,
|
|
351
|
-
lrc,
|
|
352
|
-
prefetched: prefetchedUrls.get(track.SNG_ID) ?? null,
|
|
353
|
-
});
|
|
354
|
-
// Add to saved list
|
|
355
|
-
if (savedPath) {
|
|
356
|
-
m3u8.push((0, path_1.resolve)(process.env.SIMULATE ? savedPath : (0, true_case_path_1.trueCasePathSync)(savedPath)));
|
|
357
|
-
savedFiles.push(savedPath);
|
|
358
|
-
}
|
|
374
|
+
else {
|
|
375
|
+
// One batched get_url for the whole selection instead of a request per track.
|
|
376
|
+
// Deezer returns the best licensed format, so pass an ordered preference list.
|
|
377
|
+
const QUALITY_PREF = {
|
|
378
|
+
'1': [1],
|
|
379
|
+
'128': [1],
|
|
380
|
+
'3': [3, 1],
|
|
381
|
+
'320': [3, 1],
|
|
382
|
+
'9': [9, 3, 1],
|
|
383
|
+
flac: [9, 3, 1],
|
|
359
384
|
};
|
|
360
|
-
|
|
385
|
+
const prefetchedUrls = new Map();
|
|
386
|
+
if (data.tracks.length > 1 && !process.env.SIMULATE) {
|
|
387
|
+
try {
|
|
388
|
+
const base = QUALITY_PREF[String(options.quality).toLowerCase()] ?? [3, 1];
|
|
389
|
+
const pref = fallbackQuality ? base : [base[0]];
|
|
390
|
+
const resolved = await (0, gerdur_core_1.resolveDownloadUrls)(data.tracks, pref);
|
|
391
|
+
resolved.forEach((r, i) => {
|
|
392
|
+
if (r) {
|
|
393
|
+
prefetchedUrls.set(data.tracks[i].SNG_ID, r);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
// batch resolve failed wholesale — every track falls back to per-track resolution
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
await queue.addAll(data.tracks.map((track, index) => {
|
|
402
|
+
return async () => {
|
|
403
|
+
const savedPath = await (0, download_track_1.default)({
|
|
404
|
+
track,
|
|
405
|
+
quality: options.quality,
|
|
406
|
+
info: data.linkinfo,
|
|
407
|
+
coverSizes,
|
|
408
|
+
path: layoutTemplate,
|
|
409
|
+
totalTracks: data ? data.tracks.length : 10,
|
|
410
|
+
trackNumber,
|
|
411
|
+
fallbackTrack,
|
|
412
|
+
fallbackQuality,
|
|
413
|
+
overwrite,
|
|
414
|
+
message: `(${index}/${data.tracks.length})`,
|
|
415
|
+
lrc,
|
|
416
|
+
prefetched: prefetchedUrls.get(track.SNG_ID) ?? null,
|
|
417
|
+
});
|
|
418
|
+
// Add to saved list
|
|
419
|
+
if (savedPath) {
|
|
420
|
+
m3u8.push((0, path_1.resolve)(process.env.SIMULATE ? savedPath : (0, true_case_path_1.trueCasePathSync)(savedPath)));
|
|
421
|
+
savedFiles.push(savedPath);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}));
|
|
425
|
+
}
|
|
361
426
|
// Display downloaded location
|
|
362
427
|
if (savedFiles.length > 0) {
|
|
363
428
|
const savedIn = new Set(savedFiles.map((l) => (0, path_1.dirname)(l)));
|
package/dist/src/index.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ export type { SessionOptions, DownloadTracksOptions, SearchType } from './lib/se
|
|
|
59
59
|
export { getTrackBuffer, getTaggedTrack, downloadTrackToFile } from './lib/api-download';
|
|
60
60
|
export type { Quality, GetTrackBufferOptions, DownloadTrackOptions, DownloadResult } from './lib/api-download';
|
|
61
61
|
export { default as Config, globalConfigPath, resolveConfigFile } from './lib/config';
|
|
62
|
-
export { initDeezerApi, parseInfo, searchMusic, searchPublicApi, searchTracks, searchAlbums, searchArtists, searchPlaylists, buildAdvancedQuery, suggest, getUser, getTrackInfo, getAlbumInfo, getAlbumTracks, getPlaylistInfo, getPlaylistTracks, getArtistInfo, getDiscography, getLyrics, getTrackDownloadUrl, resolveDownloadUrls, addTrackTags, getRichAlbum, normalizeContributors, toLrc, GeoBlocked, } from 'gerdur-core';
|
|
62
|
+
export { initDeezerApi, parseInfo, searchMusic, searchPublicApi, searchTracks, searchAlbums, searchArtists, searchPlaylists, buildAdvancedQuery, suggest, getGenres, getChart, getChartTracks, getGenreArtists, getEditorialList, getEditorialReleases, getEditorialSelection, getEditorialCharts, getArtistTopTracks, getRelatedArtists, getArtistAlbums, getArtistPlaylists, getArtistRadioTracks, getTrackByISRC, getAlbumByUPC, getTrackPreview, downloadPreview, formatName, toFormat, DEEZER_FORMATS, getUser, getTrackInfo, getAlbumInfo, getAlbumTracks, getPlaylistInfo, getPlaylistTracks, getArtistInfo, getDiscography, getLyrics, getTrackDownloadUrl, resolveDownloadUrls, addTrackTags, getRichAlbum, normalizeContributors, toLrc, GeoBlocked, } from 'gerdur-core';
|
|
63
63
|
export type { AddTrackTagsOptions, TaggedTrack, TrackTagModel, RichAlbum, ResolvedUrl, NormalizedContributors, } from 'gerdur-core';
|
|
64
|
-
export type { advancedSearchFilters, searchOrder, searchEntity, publicApiSearchOptions, publicApiSearchResponse, searchResultTrack, searchResultAlbum, searchResultArtist, searchResultPlaylist, suggestResult, } from 'gerdur-core/types';
|
|
64
|
+
export type { advancedSearchFilters, searchOrder, searchEntity, publicApiSearchOptions, publicApiSearchResponse, searchResultTrack, searchResultAlbum, searchResultArtist, searchResultPlaylist, suggestResult, publicApiList, chartType, chartTrack, chartAlbum, chartArtist, chartPlaylist, chartPodcast, genreType, editorialType, artistAlbumResult, } from 'gerdur-core/types';
|
|
65
|
+
export type { Quality as MediaFormat, DeezerFormat, TrackPreview } from 'gerdur-core';
|
package/dist/src/index.js
CHANGED
|
@@ -33,7 +33,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
33
33
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
34
34
|
};
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.
|
|
36
|
+
exports.getLyrics = exports.getDiscography = exports.getArtistInfo = exports.getPlaylistTracks = exports.getPlaylistInfo = exports.getAlbumTracks = exports.getAlbumInfo = exports.getTrackInfo = exports.getUser = exports.DEEZER_FORMATS = exports.toFormat = exports.formatName = exports.downloadPreview = exports.getTrackPreview = exports.getAlbumByUPC = exports.getTrackByISRC = exports.getArtistRadioTracks = exports.getArtistPlaylists = exports.getArtistAlbums = exports.getRelatedArtists = exports.getArtistTopTracks = exports.getEditorialCharts = exports.getEditorialSelection = exports.getEditorialReleases = exports.getEditorialList = exports.getGenreArtists = exports.getChartTracks = exports.getChart = exports.getGenres = exports.suggest = exports.buildAdvancedQuery = exports.searchPlaylists = exports.searchArtists = exports.searchAlbums = exports.searchTracks = exports.searchPublicApi = exports.searchMusic = exports.parseInfo = exports.initDeezerApi = exports.resolveConfigFile = exports.globalConfigPath = exports.Config = exports.downloadTrackToFile = exports.getTaggedTrack = exports.getTrackBuffer = exports.Session = exports.createSession = exports.getArl = exports.LoginError = exports.loginWithEmail = void 0;
|
|
37
|
+
exports.GeoBlocked = exports.toLrc = exports.normalizeContributors = exports.getRichAlbum = exports.addTrackTags = exports.resolveDownloadUrls = exports.getTrackDownloadUrl = void 0;
|
|
37
38
|
// ─── Authentication ────────────────────────────────────────────────────────
|
|
38
39
|
const email_login_1 = require("./lib/email-login");
|
|
39
40
|
Object.defineProperty(exports, "loginWithEmail", { enumerable: true, get: function () { return email_login_1.loginWithEmail; } });
|
|
@@ -96,6 +97,26 @@ Object.defineProperty(exports, "searchArtists", { enumerable: true, get: functio
|
|
|
96
97
|
Object.defineProperty(exports, "searchPlaylists", { enumerable: true, get: function () { return gerdur_core_1.searchPlaylists; } });
|
|
97
98
|
Object.defineProperty(exports, "buildAdvancedQuery", { enumerable: true, get: function () { return gerdur_core_1.buildAdvancedQuery; } });
|
|
98
99
|
Object.defineProperty(exports, "suggest", { enumerable: true, get: function () { return gerdur_core_1.suggest; } });
|
|
100
|
+
Object.defineProperty(exports, "getGenres", { enumerable: true, get: function () { return gerdur_core_1.getGenres; } });
|
|
101
|
+
Object.defineProperty(exports, "getChart", { enumerable: true, get: function () { return gerdur_core_1.getChart; } });
|
|
102
|
+
Object.defineProperty(exports, "getChartTracks", { enumerable: true, get: function () { return gerdur_core_1.getChartTracks; } });
|
|
103
|
+
Object.defineProperty(exports, "getGenreArtists", { enumerable: true, get: function () { return gerdur_core_1.getGenreArtists; } });
|
|
104
|
+
Object.defineProperty(exports, "getEditorialList", { enumerable: true, get: function () { return gerdur_core_1.getEditorialList; } });
|
|
105
|
+
Object.defineProperty(exports, "getEditorialReleases", { enumerable: true, get: function () { return gerdur_core_1.getEditorialReleases; } });
|
|
106
|
+
Object.defineProperty(exports, "getEditorialSelection", { enumerable: true, get: function () { return gerdur_core_1.getEditorialSelection; } });
|
|
107
|
+
Object.defineProperty(exports, "getEditorialCharts", { enumerable: true, get: function () { return gerdur_core_1.getEditorialCharts; } });
|
|
108
|
+
Object.defineProperty(exports, "getArtistTopTracks", { enumerable: true, get: function () { return gerdur_core_1.getArtistTopTracks; } });
|
|
109
|
+
Object.defineProperty(exports, "getRelatedArtists", { enumerable: true, get: function () { return gerdur_core_1.getRelatedArtists; } });
|
|
110
|
+
Object.defineProperty(exports, "getArtistAlbums", { enumerable: true, get: function () { return gerdur_core_1.getArtistAlbums; } });
|
|
111
|
+
Object.defineProperty(exports, "getArtistPlaylists", { enumerable: true, get: function () { return gerdur_core_1.getArtistPlaylists; } });
|
|
112
|
+
Object.defineProperty(exports, "getArtistRadioTracks", { enumerable: true, get: function () { return gerdur_core_1.getArtistRadioTracks; } });
|
|
113
|
+
Object.defineProperty(exports, "getTrackByISRC", { enumerable: true, get: function () { return gerdur_core_1.getTrackByISRC; } });
|
|
114
|
+
Object.defineProperty(exports, "getAlbumByUPC", { enumerable: true, get: function () { return gerdur_core_1.getAlbumByUPC; } });
|
|
115
|
+
Object.defineProperty(exports, "getTrackPreview", { enumerable: true, get: function () { return gerdur_core_1.getTrackPreview; } });
|
|
116
|
+
Object.defineProperty(exports, "downloadPreview", { enumerable: true, get: function () { return gerdur_core_1.downloadPreview; } });
|
|
117
|
+
Object.defineProperty(exports, "formatName", { enumerable: true, get: function () { return gerdur_core_1.formatName; } });
|
|
118
|
+
Object.defineProperty(exports, "toFormat", { enumerable: true, get: function () { return gerdur_core_1.toFormat; } });
|
|
119
|
+
Object.defineProperty(exports, "DEEZER_FORMATS", { enumerable: true, get: function () { return gerdur_core_1.DEEZER_FORMATS; } });
|
|
99
120
|
Object.defineProperty(exports, "getUser", { enumerable: true, get: function () { return gerdur_core_1.getUser; } });
|
|
100
121
|
Object.defineProperty(exports, "getTrackInfo", { enumerable: true, get: function () { return gerdur_core_1.getTrackInfo; } });
|
|
101
122
|
Object.defineProperty(exports, "getAlbumInfo", { enumerable: true, get: function () { return gerdur_core_1.getAlbumInfo; } });
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { parseInfo, searchMusic } from 'gerdur-core';
|
|
3
3
|
import type { AdvancedSearchOptions } from './search';
|
|
4
4
|
import type { Quality, DownloadTrackOptions, DownloadResult } from './api-download';
|
|
5
|
-
import type { trackType, userType, advancedSearchFilters, publicApiSearchResponse, searchResultTrack, suggestResult } from 'gerdur-core/types';
|
|
5
|
+
import type { trackType, userType, advancedSearchFilters, publicApiSearchResponse, searchResultTrack, suggestResult, chartType, genreType, editorialType, artistAlbumResult, searchResultArtist, publicApiList, trackTypePublicApi, albumTypePublicApi } from 'gerdur-core/types';
|
|
6
|
+
import type { TrackPreview } from 'gerdur-core';
|
|
6
7
|
/** Search result categories accepted by {@link Session.search}. */
|
|
7
8
|
export type SearchType = 'ALBUM' | 'ARTIST' | 'TRACK' | 'PLAYLIST' | 'RADIO' | 'SHOW' | 'USER' | 'LIVESTREAM' | 'CHANNEL';
|
|
8
9
|
export interface SessionOptions {
|
|
@@ -65,6 +66,30 @@ export declare class Session {
|
|
|
65
66
|
searchAdvanced(filters: advancedSearchFilters, options?: AdvancedSearchOptions): Promise<publicApiSearchResponse<searchResultTrack>>;
|
|
66
67
|
/** `deezer.suggest` autocomplete — for "as you type" UIs. `nb` caps items per type. */
|
|
67
68
|
suggest(query: string, nb?: number): Promise<suggestResult>;
|
|
69
|
+
/** Deezer's genre list (`id` `0` = "All"). */
|
|
70
|
+
genres(): Promise<publicApiList<genreType>>;
|
|
71
|
+
/** The five ranked lists for a genre (`0` = all): `{tracks, albums, artists, playlists, podcasts}`. */
|
|
72
|
+
chart(genreId?: number | string, limit?: number): Promise<chartType>;
|
|
73
|
+
/** Just the track chart for a genre — a ready-to-download list. */
|
|
74
|
+
chartTracks(genreId?: number | string, limit?: number, index?: number): Promise<publicApiList<searchResultTrack>>;
|
|
75
|
+
/** Deezer's editorial sections. */
|
|
76
|
+
editorialSections(): Promise<publicApiList<editorialType>>;
|
|
77
|
+
/** An artist's most popular tracks (public-API shape). */
|
|
78
|
+
artistTopTracks(artistId: number | string, limit?: number): Promise<publicApiList<searchResultTrack>>;
|
|
79
|
+
/** Artists Deezer considers related / similar. */
|
|
80
|
+
relatedArtists(artistId: number | string, limit?: number): Promise<publicApiList<searchResultArtist>>;
|
|
81
|
+
/** An artist's discography (public-API album shape). */
|
|
82
|
+
artistAlbums(artistId: number | string, limit?: number, index?: number): Promise<publicApiList<artistAlbumResult>>;
|
|
83
|
+
/** A ready-made radio (track list) seeded from an artist. */
|
|
84
|
+
artistRadio(artistId: number | string): Promise<publicApiList<searchResultTrack>>;
|
|
85
|
+
/** Resolve an ISRC to the public-API track. Pass `.id` to `getTrackInfo` to download. */
|
|
86
|
+
trackByISRC(isrc: string): Promise<trackTypePublicApi>;
|
|
87
|
+
/** Resolve a UPC/EAN barcode to the public-API album (with its `tracks`). */
|
|
88
|
+
albumByUPC(upc: string): Promise<albumTypePublicApi>;
|
|
89
|
+
/** The 30-second preview clip URL for a track (plain MP3 — no licence, no decryption). */
|
|
90
|
+
trackPreview(track: trackType | string | number): Promise<TrackPreview | null>;
|
|
91
|
+
/** Fetch a track's 30-second preview clip as a `Buffer` (plain MP3). */
|
|
92
|
+
downloadPreview(track: trackType | string | number): Promise<Buffer | null>;
|
|
68
93
|
/** Return a fully tagged audio Buffer for a track (nothing written to disk). */
|
|
69
94
|
getTrackBuffer(track: trackType, quality?: Quality, options?: {}): Promise<Buffer | null>;
|
|
70
95
|
/** Download a single track to disk. */
|
package/dist/src/lib/session.js
CHANGED
|
@@ -75,6 +75,55 @@ class Session {
|
|
|
75
75
|
suggest(query, nb) {
|
|
76
76
|
return (0, gerdur_core_1.suggest)(query, nb);
|
|
77
77
|
}
|
|
78
|
+
// ─── Browse & discovery (public REST) ──────────────────────────────────────
|
|
79
|
+
/** Deezer's genre list (`id` `0` = "All"). */
|
|
80
|
+
genres() {
|
|
81
|
+
return (0, gerdur_core_1.getGenres)();
|
|
82
|
+
}
|
|
83
|
+
/** The five ranked lists for a genre (`0` = all): `{tracks, albums, artists, playlists, podcasts}`. */
|
|
84
|
+
chart(genreId = 0, limit = 10) {
|
|
85
|
+
return (0, gerdur_core_1.getChart)(genreId, limit);
|
|
86
|
+
}
|
|
87
|
+
/** Just the track chart for a genre — a ready-to-download list. */
|
|
88
|
+
chartTracks(genreId = 0, limit = 100, index = 0) {
|
|
89
|
+
return (0, gerdur_core_1.getChartTracks)(genreId, limit, index);
|
|
90
|
+
}
|
|
91
|
+
/** Deezer's editorial sections. */
|
|
92
|
+
editorialSections() {
|
|
93
|
+
return (0, gerdur_core_1.getEditorialList)();
|
|
94
|
+
}
|
|
95
|
+
/** An artist's most popular tracks (public-API shape). */
|
|
96
|
+
artistTopTracks(artistId, limit = 50) {
|
|
97
|
+
return (0, gerdur_core_1.getArtistTopTracks)(artistId, limit);
|
|
98
|
+
}
|
|
99
|
+
/** Artists Deezer considers related / similar. */
|
|
100
|
+
relatedArtists(artistId, limit = 20) {
|
|
101
|
+
return (0, gerdur_core_1.getRelatedArtists)(artistId, limit);
|
|
102
|
+
}
|
|
103
|
+
/** An artist's discography (public-API album shape). */
|
|
104
|
+
artistAlbums(artistId, limit = 50, index = 0) {
|
|
105
|
+
return (0, gerdur_core_1.getArtistAlbums)(artistId, limit, index);
|
|
106
|
+
}
|
|
107
|
+
/** A ready-made radio (track list) seeded from an artist. */
|
|
108
|
+
artistRadio(artistId) {
|
|
109
|
+
return (0, gerdur_core_1.getArtistRadioTracks)(artistId);
|
|
110
|
+
}
|
|
111
|
+
/** Resolve an ISRC to the public-API track. Pass `.id` to `getTrackInfo` to download. */
|
|
112
|
+
trackByISRC(isrc) {
|
|
113
|
+
return (0, gerdur_core_1.getTrackByISRC)(isrc);
|
|
114
|
+
}
|
|
115
|
+
/** Resolve a UPC/EAN barcode to the public-API album (with its `tracks`). */
|
|
116
|
+
albumByUPC(upc) {
|
|
117
|
+
return (0, gerdur_core_1.getAlbumByUPC)(upc);
|
|
118
|
+
}
|
|
119
|
+
/** The 30-second preview clip URL for a track (plain MP3 — no licence, no decryption). */
|
|
120
|
+
trackPreview(track) {
|
|
121
|
+
return (0, gerdur_core_1.getTrackPreview)(track);
|
|
122
|
+
}
|
|
123
|
+
/** Fetch a track's 30-second preview clip as a `Buffer` (plain MP3). */
|
|
124
|
+
downloadPreview(track) {
|
|
125
|
+
return (0, gerdur_core_1.downloadPreview)(track);
|
|
126
|
+
}
|
|
78
127
|
/** Return a fully tagged audio Buffer for a track (nothing written to disk). */
|
|
79
128
|
getTrackBuffer(track, quality = '320', options = {}) {
|
|
80
129
|
return (0, api_download_1.getTrackBuffer)(track, quality, options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gerdur",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "The crucial streaming module for dabox systems.",
|
|
5
5
|
"author": "Christian",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"adm-zip": "^0.5.16",
|
|
49
49
|
"chalk": "^4.1.2",
|
|
50
50
|
"commander": "^9.5.0",
|
|
51
|
-
"gerdur-core": "^2.
|
|
51
|
+
"gerdur-core": "^2.3.0",
|
|
52
52
|
"dot-prop": "^6.0.1",
|
|
53
53
|
"got": "^11.8.6",
|
|
54
54
|
"gradient-string": "^2.0.2",
|