gerdur 2.3.0 → 2.5.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 +31 -0
- package/README.md +23 -3
- package/dist/package.json +2 -2
- package/dist/src/gerdur.js +97 -51
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +11 -2
- package/dist/src/lib/decrypt.d.ts +7 -2
- package/dist/src/lib/decrypt.js +15 -2
- package/dist/src/lib/download-track.js +4 -1
- package/dist/src/lib/session.d.ts +12 -0
- package/dist/src/lib/session.js +17 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.5.0 - 2026-08-31
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **`gerdur-core@^2.5.0`.** Track decryption in the download pipeline is now
|
|
8
|
+
**streamed** to a temp file (`decryptFileToFile`) instead of read fully into
|
|
9
|
+
memory twice — peak memory during decrypt drops to ~one 2048-byte stripe.
|
|
10
|
+
- Gateway failures now surface as `DeezerError` (re-exported) with `code` /
|
|
11
|
+
`keys` / `retryable`, and gateway retries are bounded (no more infinite spin
|
|
12
|
+
on a persistently failing endpoint).
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Re-exports: `streamTrackDownload`, `createDecryptStream`, `getStream`,
|
|
17
|
+
`DeezerError` + the `StreamTrackOptions` / `TrackStream` / `StreamResponse` types.
|
|
18
|
+
- **`Session.streamTrack(track, quality?, options?)`** — download a track as a
|
|
19
|
+
constant-memory stream of decrypted audio (`{stream, size, startedAt}`),
|
|
20
|
+
with `onProgress` and `resumeFrom`.
|
|
21
|
+
|
|
22
|
+
## 2.4.0 - 2026-08-31
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **`gerdur-core@^2.3.0`** — re-exports `getTrackPreview`, `downloadPreview`,
|
|
27
|
+
`formatName`, `toFormat`, `DEEZER_FORMATS` and the `DeezerFormat` /
|
|
28
|
+
`TrackPreview` types.
|
|
29
|
+
- **`--preview`** — download the 30-second clip for each track as
|
|
30
|
+
`<name>.preview.mp3` instead of the full file. Plain licence-free MP3s: no
|
|
31
|
+
`--quality`, no decryption, no tagging. Works with every source and headless.
|
|
32
|
+
- **`Session.trackPreview(track)`** and **`Session.downloadPreview(track)`**.
|
|
33
|
+
|
|
3
34
|
## 2.3.0 - 2026-08-31
|
|
4
35
|
|
|
5
36
|
### Added
|
package/README.md
CHANGED
|
@@ -142,6 +142,7 @@ 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
|
|
|
@@ -198,6 +199,18 @@ only on **track** search:
|
|
|
198
199
|
| `dur_min:` / `dur_max:` | `dur_min:200` (seconds) |
|
|
199
200
|
| `bpm_min:` / `bpm_max:` | `bpm_min:120` |
|
|
200
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
|
+
|
|
201
214
|
## Programmatic API
|
|
202
215
|
|
|
203
216
|
When installed as a dependency, `gerdur` exposes a side-effect-free API
|
|
@@ -245,13 +258,17 @@ Session methods:
|
|
|
245
258
|
- **browse** — `genres`, `chart`, `chartTracks`, `editorialSections`,
|
|
246
259
|
`artistTopTracks`, `relatedArtists`, `artistAlbums`, `artistRadio`,
|
|
247
260
|
`trackByISRC`, `albumByUPC`
|
|
248
|
-
- **user / download** — `getUser`, `getTrackBuffer`, `
|
|
249
|
-
`downloadTracks`, `downloadUrl`
|
|
261
|
+
- **user / download** — `getUser`, `getTrackBuffer`, `streamTrack`,
|
|
262
|
+
`downloadTrack`, `downloadTracks`, `downloadUrl`, `trackPreview`, `downloadPreview`
|
|
250
263
|
|
|
251
264
|
Every download call is silent; `downloadTracks` / `downloadUrl` accept
|
|
252
265
|
`concurrency` and an `onProgress` callback and return one `{path, written} | null`
|
|
253
266
|
per track.
|
|
254
267
|
|
|
268
|
+
`streamTrack(track, quality?, options?)` returns `{stream, size, startedAt,
|
|
269
|
+
isEncrypted}` — decrypted audio at constant memory, with `onProgress(got, total)`
|
|
270
|
+
and `resumeFrom` (bytes). Pipe `stream` to a file or your own tag muxer.
|
|
271
|
+
|
|
255
272
|
`searchAdvanced(filters, options?)` builds a Deezer advanced-search query from
|
|
256
273
|
`{query?, artist?, album?, track?, label?, durMin?, durMax?, bpmMin?, bpmMax?}`
|
|
257
274
|
and takes `{order?, strict?, limit?, index?, fallback?}`. Deezer's operators are
|
|
@@ -302,7 +319,10 @@ or `createSession`):
|
|
|
302
319
|
`getEditorialCharts`, `getArtistTopTracks`, `getRelatedArtists`,
|
|
303
320
|
`getArtistAlbums`, `getArtistPlaylists`, `getArtistRadioTracks`,
|
|
304
321
|
`getTrackByISRC`, `getAlbumByUPC`
|
|
305
|
-
- **Download** — `getTrackDownloadUrl`, `resolveDownloadUrls`,
|
|
322
|
+
- **Download** — `getTrackDownloadUrl`, `resolveDownloadUrls`,
|
|
323
|
+
`streamTrackDownload`, `createDecryptStream`, `getStream`, `getTrackPreview`,
|
|
324
|
+
`downloadPreview`, `formatName`, `toFormat`, `DEEZER_FORMATS`
|
|
325
|
+
- **Errors** — `DeezerError` (`code` / `keys` / `retryable`), `GeoBlocked`
|
|
306
326
|
|
|
307
327
|
### Auth & config helpers
|
|
308
328
|
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gerdur",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.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.5.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',
|
|
@@ -327,56 +366,63 @@ const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
|
327
366
|
const resolveFullPath = options.resolveFullPath ?? conf.get('playlist.resolveFullPath');
|
|
328
367
|
const savedFiles = [];
|
|
329
368
|
let m3u8 = [];
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
'128': [1],
|
|
335
|
-
'3': [3, 1],
|
|
336
|
-
'320': [3, 1],
|
|
337
|
-
'9': [9, 3, 1],
|
|
338
|
-
flac: [9, 3, 1],
|
|
339
|
-
};
|
|
340
|
-
const prefetchedUrls = new Map();
|
|
341
|
-
if (data.tracks.length > 1 && !process.env.SIMULATE) {
|
|
342
|
-
try {
|
|
343
|
-
const base = QUALITY_PREF[String(options.quality).toLowerCase()] ?? [3, 1];
|
|
344
|
-
const pref = fallbackQuality ? base : [base[0]];
|
|
345
|
-
const resolved = await (0, gerdur_core_1.resolveDownloadUrls)(data.tracks, pref);
|
|
346
|
-
resolved.forEach((r, i) => {
|
|
347
|
-
if (r) {
|
|
348
|
-
prefetchedUrls.set(data.tracks[i].SNG_ID, r);
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
catch {
|
|
353
|
-
// batch resolve failed wholesale — every track falls back to per-track resolution
|
|
354
|
-
}
|
|
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);
|
|
355
373
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
fallbackTrack,
|
|
367
|
-
fallbackQuality,
|
|
368
|
-
overwrite,
|
|
369
|
-
message: `(${index}/${data.tracks.length})`,
|
|
370
|
-
lrc,
|
|
371
|
-
prefetched: prefetchedUrls.get(track.SNG_ID) ?? null,
|
|
372
|
-
});
|
|
373
|
-
// Add to saved list
|
|
374
|
-
if (savedPath) {
|
|
375
|
-
m3u8.push((0, path_1.resolve)(process.env.SIMULATE ? savedPath : (0, true_case_path_1.trueCasePathSync)(savedPath)));
|
|
376
|
-
savedFiles.push(savedPath);
|
|
377
|
-
}
|
|
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],
|
|
378
384
|
};
|
|
379
|
-
|
|
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
|
+
}
|
|
380
426
|
// Display downloaded location
|
|
381
427
|
if (savedFiles.length > 0) {
|
|
382
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, getGenres, getChart, getChartTracks, getGenreArtists, getEditorialList, getEditorialReleases, getEditorialSelection, getEditorialCharts, getArtistTopTracks, getRelatedArtists, getArtistAlbums, getArtistPlaylists, getArtistRadioTracks, getTrackByISRC, getAlbumByUPC, 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, streamTrackDownload, createDecryptStream, getStream, addTrackTags, getRichAlbum, normalizeContributors, toLrc, GeoBlocked, DeezerError, } from 'gerdur-core';
|
|
63
63
|
export type { AddTrackTagsOptions, TaggedTrack, TrackTagModel, RichAlbum, ResolvedUrl, NormalizedContributors, } from 'gerdur-core';
|
|
64
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, StreamTrackOptions, TrackStream, StreamResponse, DeezerErrorPayload, } from 'gerdur-core';
|
package/dist/src/index.js
CHANGED
|
@@ -33,8 +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.
|
|
37
|
-
exports.GeoBlocked = exports.toLrc = void 0;
|
|
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.DeezerError = exports.GeoBlocked = exports.toLrc = exports.normalizeContributors = exports.getRichAlbum = exports.addTrackTags = exports.getStream = exports.createDecryptStream = exports.streamTrackDownload = exports.resolveDownloadUrls = exports.getTrackDownloadUrl = void 0;
|
|
38
38
|
// ─── Authentication ────────────────────────────────────────────────────────
|
|
39
39
|
const email_login_1 = require("./lib/email-login");
|
|
40
40
|
Object.defineProperty(exports, "loginWithEmail", { enumerable: true, get: function () { return email_login_1.loginWithEmail; } });
|
|
@@ -112,6 +112,11 @@ Object.defineProperty(exports, "getArtistPlaylists", { enumerable: true, get: fu
|
|
|
112
112
|
Object.defineProperty(exports, "getArtistRadioTracks", { enumerable: true, get: function () { return gerdur_core_1.getArtistRadioTracks; } });
|
|
113
113
|
Object.defineProperty(exports, "getTrackByISRC", { enumerable: true, get: function () { return gerdur_core_1.getTrackByISRC; } });
|
|
114
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; } });
|
|
115
120
|
Object.defineProperty(exports, "getUser", { enumerable: true, get: function () { return gerdur_core_1.getUser; } });
|
|
116
121
|
Object.defineProperty(exports, "getTrackInfo", { enumerable: true, get: function () { return gerdur_core_1.getTrackInfo; } });
|
|
117
122
|
Object.defineProperty(exports, "getAlbumInfo", { enumerable: true, get: function () { return gerdur_core_1.getAlbumInfo; } });
|
|
@@ -123,8 +128,12 @@ Object.defineProperty(exports, "getDiscography", { enumerable: true, get: functi
|
|
|
123
128
|
Object.defineProperty(exports, "getLyrics", { enumerable: true, get: function () { return gerdur_core_1.getLyrics; } });
|
|
124
129
|
Object.defineProperty(exports, "getTrackDownloadUrl", { enumerable: true, get: function () { return gerdur_core_1.getTrackDownloadUrl; } });
|
|
125
130
|
Object.defineProperty(exports, "resolveDownloadUrls", { enumerable: true, get: function () { return gerdur_core_1.resolveDownloadUrls; } });
|
|
131
|
+
Object.defineProperty(exports, "streamTrackDownload", { enumerable: true, get: function () { return gerdur_core_1.streamTrackDownload; } });
|
|
132
|
+
Object.defineProperty(exports, "createDecryptStream", { enumerable: true, get: function () { return gerdur_core_1.createDecryptStream; } });
|
|
133
|
+
Object.defineProperty(exports, "getStream", { enumerable: true, get: function () { return gerdur_core_1.getStream; } });
|
|
126
134
|
Object.defineProperty(exports, "addTrackTags", { enumerable: true, get: function () { return gerdur_core_1.addTrackTags; } });
|
|
127
135
|
Object.defineProperty(exports, "getRichAlbum", { enumerable: true, get: function () { return gerdur_core_1.getRichAlbum; } });
|
|
128
136
|
Object.defineProperty(exports, "normalizeContributors", { enumerable: true, get: function () { return gerdur_core_1.normalizeContributors; } });
|
|
129
137
|
Object.defineProperty(exports, "toLrc", { enumerable: true, get: function () { return gerdur_core_1.toLrc; } });
|
|
130
138
|
Object.defineProperty(exports, "GeoBlocked", { enumerable: true, get: function () { return gerdur_core_1.GeoBlocked; } });
|
|
139
|
+
Object.defineProperty(exports, "DeezerError", { enumerable: true, get: function () { return gerdur_core_1.DeezerError; } });
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
export { decryptDownload, TrackDecryptStream } from 'gerdur-core';
|
|
3
|
-
/** Decrypt a track that was streamed to a temp file. */
|
|
2
|
+
export { decryptDownload, createDecryptStream, TrackDecryptStream } from 'gerdur-core';
|
|
3
|
+
/** Decrypt a track that was streamed to a temp file (buffered — reads the file into memory). */
|
|
4
4
|
export declare const decryptDownloadFile: (tmpfile: string, trackId: string) => Buffer;
|
|
5
|
+
/**
|
|
6
|
+
* Decrypt `src` → `dest` as a stream — peak memory is ~one 2048-byte stripe,
|
|
7
|
+
* not the whole file twice. Use this over `decryptDownloadFile` for large FLACs.
|
|
8
|
+
*/
|
|
9
|
+
export declare const decryptFileToFile: (src: string, dest: string, trackId: string) => Promise<void>;
|
package/dist/src/lib/decrypt.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.decryptDownloadFile = exports.TrackDecryptStream = exports.decryptDownload = void 0;
|
|
6
|
+
exports.decryptFileToFile = exports.decryptDownloadFile = exports.TrackDecryptStream = exports.createDecryptStream = exports.decryptDownload = void 0;
|
|
4
7
|
/**
|
|
5
8
|
* Track decryption. As of gerdur-core@1.0.2 the core `decryptDownload` is a
|
|
6
9
|
* correct, dependency-free Blowfish that works on every Node (it no longer
|
|
7
10
|
* touches OpenSSL's removed `bf-cbc`), at ~290 MiB/s — so there is nothing left
|
|
8
11
|
* to wrap or fall back to here.
|
|
9
12
|
*/
|
|
13
|
+
const stream_1 = __importDefault(require("stream"));
|
|
14
|
+
const util_1 = require("util");
|
|
10
15
|
const fs_1 = require("fs");
|
|
11
16
|
const gerdur_core_1 = require("gerdur-core");
|
|
12
17
|
var gerdur_core_2 = require("gerdur-core");
|
|
13
18
|
Object.defineProperty(exports, "decryptDownload", { enumerable: true, get: function () { return gerdur_core_2.decryptDownload; } });
|
|
19
|
+
Object.defineProperty(exports, "createDecryptStream", { enumerable: true, get: function () { return gerdur_core_2.createDecryptStream; } });
|
|
14
20
|
Object.defineProperty(exports, "TrackDecryptStream", { enumerable: true, get: function () { return gerdur_core_2.TrackDecryptStream; } });
|
|
15
|
-
|
|
21
|
+
const pipeline = (0, util_1.promisify)(stream_1.default.pipeline);
|
|
22
|
+
/** Decrypt a track that was streamed to a temp file (buffered — reads the file into memory). */
|
|
16
23
|
const decryptDownloadFile = (tmpfile, trackId) => (0, gerdur_core_1.decryptDownload)((0, fs_1.readFileSync)(tmpfile), trackId);
|
|
17
24
|
exports.decryptDownloadFile = decryptDownloadFile;
|
|
25
|
+
/**
|
|
26
|
+
* Decrypt `src` → `dest` as a stream — peak memory is ~one 2048-byte stripe,
|
|
27
|
+
* not the whole file twice. Use this over `decryptDownloadFile` for large FLACs.
|
|
28
|
+
*/
|
|
29
|
+
const decryptFileToFile = (src, dest, trackId) => pipeline((0, fs_1.createReadStream)(src), (0, gerdur_core_1.createDecryptStream)(trackId), (0, fs_1.createWriteStream)(dest));
|
|
30
|
+
exports.decryptFileToFile = decryptFileToFile;
|
|
@@ -145,7 +145,10 @@ const downloadTrack = async ({ track, quality, info, coverSizes, path, totalTrac
|
|
|
145
145
|
let outFile;
|
|
146
146
|
if (trackData.isEncrypted) {
|
|
147
147
|
(0, log_update_1.default)(signale_1.default.pending('Decrypting ' + track.SNG_TITLE + ' by ' + track.ART_NAME));
|
|
148
|
-
|
|
148
|
+
const decfile = tmpfile + '.dec';
|
|
149
|
+
await (0, decrypt_1.decryptFileToFile)(tmpfile, decfile, track.SNG_ID); // streamed — ~one stripe in memory
|
|
150
|
+
outFile = (0, fs_1.readFileSync)(decfile);
|
|
151
|
+
(0, fs_1.unlinkSync)(decfile);
|
|
149
152
|
}
|
|
150
153
|
else {
|
|
151
154
|
outFile = (0, fs_1.readFileSync)(tmpfile);
|
|
@@ -3,6 +3,7 @@ import { parseInfo, searchMusic } from 'gerdur-core';
|
|
|
3
3
|
import type { AdvancedSearchOptions } from './search';
|
|
4
4
|
import type { Quality, DownloadTrackOptions, DownloadResult } from './api-download';
|
|
5
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, StreamTrackOptions, TrackStream } 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 {
|
|
@@ -85,8 +86,19 @@ export declare class Session {
|
|
|
85
86
|
trackByISRC(isrc: string): Promise<trackTypePublicApi>;
|
|
86
87
|
/** Resolve a UPC/EAN barcode to the public-API album (with its `tracks`). */
|
|
87
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>;
|
|
88
93
|
/** Return a fully tagged audio Buffer for a track (nothing written to disk). */
|
|
89
94
|
getTrackBuffer(track: trackType, quality?: Quality, options?: {}): Promise<Buffer | null>;
|
|
95
|
+
/**
|
|
96
|
+
* Download a track as a **stream** of decrypted audio — constant memory,
|
|
97
|
+
* regardless of file size or concurrency. `{stream, size, startedAt,
|
|
98
|
+
* isEncrypted}`; pipe `stream` to a file or your own tag muxer.
|
|
99
|
+
* `options.onProgress(received, total)`, `options.resumeFrom` (bytes).
|
|
100
|
+
*/
|
|
101
|
+
streamTrack(track: trackType, quality?: 1 | 3 | 9, options?: StreamTrackOptions): Promise<TrackStream>;
|
|
90
102
|
/** Download a single track to disk. */
|
|
91
103
|
downloadTrack(track: trackType, quality?: Quality, options?: DownloadTrackOptions): Promise<DownloadResult | null>;
|
|
92
104
|
/**
|
package/dist/src/lib/session.js
CHANGED
|
@@ -116,10 +116,27 @@ class Session {
|
|
|
116
116
|
albumByUPC(upc) {
|
|
117
117
|
return (0, gerdur_core_1.getAlbumByUPC)(upc);
|
|
118
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
|
+
}
|
|
119
127
|
/** Return a fully tagged audio Buffer for a track (nothing written to disk). */
|
|
120
128
|
getTrackBuffer(track, quality = '320', options = {}) {
|
|
121
129
|
return (0, api_download_1.getTrackBuffer)(track, quality, options);
|
|
122
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Download a track as a **stream** of decrypted audio — constant memory,
|
|
133
|
+
* regardless of file size or concurrency. `{stream, size, startedAt,
|
|
134
|
+
* isEncrypted}`; pipe `stream` to a file or your own tag muxer.
|
|
135
|
+
* `options.onProgress(received, total)`, `options.resumeFrom` (bytes).
|
|
136
|
+
*/
|
|
137
|
+
streamTrack(track, quality = 3, options = {}) {
|
|
138
|
+
return (0, gerdur_core_1.streamTrackDownload)(track, quality, options);
|
|
139
|
+
}
|
|
123
140
|
/** Download a single track to disk. */
|
|
124
141
|
downloadTrack(track, quality = '320', options = {}) {
|
|
125
142
|
return (0, api_download_1.downloadTrackToFile)(track, quality, options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gerdur",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.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.5.0",
|
|
52
52
|
"dot-prop": "^6.0.1",
|
|
53
53
|
"got": "^11.8.6",
|
|
54
54
|
"gradient-string": "^2.0.2",
|