gerdur 2.13.0 → 2.15.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 +2 -0
- package/dist/package.json +2 -2
- package/dist/src/gerdur.js +51 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +10 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.15.0 - 2026-08-31
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **`gerdur-core@^2.19.0`** — tagging gets substantially faster on any network
|
|
8
|
+
where Musixmatch is blocked (most datacentres, many home connections). The
|
|
9
|
+
lyrics fallback now latches off after three consecutive transport failures
|
|
10
|
+
instead of retrying it for every track forever: measured **2880 ms → 653 ms
|
|
11
|
+
per 14-track album, 77% faster**, once the latch trips. Nothing to configure;
|
|
12
|
+
`--fast` still skips the fallback outright from the first track.
|
|
13
|
+
- Also picks up `gerdur-core@2.18.0`'s library write operations
|
|
14
|
+
(`addFavoriteTracks`, `followPlaylist`, `createPlaylist`, …), re-exported for
|
|
15
|
+
programmatic use.
|
|
16
|
+
|
|
17
|
+
## 2.14.0 - 2026-08-31
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **`mix:<trackId>`** — download a "more like this" mix seeded from a track.
|
|
22
|
+
Backed by `gerdur-core@2.17.0`'s `getTrackMix`, which returns tracks with
|
|
23
|
+
their `TRACK_TOKEN`s already attached, so unlike the public radio sources
|
|
24
|
+
there is no per-track lookup before downloading.
|
|
25
|
+
- **`library`** (also `favorites` / `favourites`) — pick from the logged-in
|
|
26
|
+
account's own loved tracks, **private library included**. The public profile
|
|
27
|
+
endpoints can't see these.
|
|
28
|
+
- **`gerdur-core@^2.17.0`** — re-exports the whole private-library surface:
|
|
29
|
+
`getMyPlaylists`, `getMyFavorite{Tracks,TrackIds,Albums,Artists,Playlists,Radios,Shows}`,
|
|
30
|
+
`getTrackMix`.
|
|
31
|
+
|
|
3
32
|
## 2.13.0 - 2026-08-31
|
|
4
33
|
|
|
5
34
|
### Added
|
package/README.md
CHANGED
|
@@ -192,6 +192,8 @@ Anywhere a URL is accepted (`-u`, the interactive prompt, or a line in
|
|
|
192
192
|
| `radio:<id>` | a radio's current playlist |
|
|
193
193
|
| `chart` · `chart:<genreId>` | this week's chart (optionally a genre) |
|
|
194
194
|
| `artist-top:<artistId>` | an artist's most popular tracks |
|
|
195
|
+
| `mix:<trackId>` | a "more like this" mix seeded from a track |
|
|
196
|
+
| `library` · `favorites` | your own loved tracks (private library) |
|
|
195
197
|
| `episode:<episodeId>` | a single podcast episode (plain MP3, saved to `Podcasts/`) |
|
|
196
198
|
|
|
197
199
|
All of these work in `--headless` mode too (`gerdur -d -q 320 -u flow`).
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gerdur",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.0",
|
|
4
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
5
|
"keywords": [
|
|
6
6
|
"deezer",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"adm-zip": "^0.5.16",
|
|
64
64
|
"chalk": "^4.1.2",
|
|
65
65
|
"commander": "^9.5.0",
|
|
66
|
-
"gerdur-core": "^2.
|
|
66
|
+
"gerdur-core": "^2.19.0",
|
|
67
67
|
"dot-prop": "^6.0.1",
|
|
68
68
|
"got": "^11.8.6",
|
|
69
69
|
"gradient-string": "^2.0.2",
|
package/dist/src/gerdur.js
CHANGED
|
@@ -148,6 +148,36 @@ const pickAndHydrate = async (results, id) => {
|
|
|
148
148
|
}
|
|
149
149
|
return { info: { type: 'track', id }, linktype: 'track', linkinfo: {}, tracks };
|
|
150
150
|
};
|
|
151
|
+
/**
|
|
152
|
+
* Like `pickAndHydrate`, but for gateway tracks — the library and mix endpoints
|
|
153
|
+
* already return full tracks with `TRACK_TOKEN`s, so there is nothing to fetch.
|
|
154
|
+
*/
|
|
155
|
+
const pickGwTracks = async (results, id) => {
|
|
156
|
+
if (!results.length) {
|
|
157
|
+
console.log(signale_1.default.warn('Nothing to download.'));
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
let picks = results;
|
|
161
|
+
if (!options.headless) {
|
|
162
|
+
const choice = await (0, prompts_1.default)([
|
|
163
|
+
{
|
|
164
|
+
type: 'multiselect',
|
|
165
|
+
name: 'items',
|
|
166
|
+
message: `Select tracks to download. ${results.length} available.`,
|
|
167
|
+
choices: results.map((r) => ({
|
|
168
|
+
title: `${r.SNG_TITLE} — ${r.ART_NAME}`,
|
|
169
|
+
value: r,
|
|
170
|
+
description: `Album: ${r.ALB_TITLE ?? 'Unknown'} · ${(0, util_1.formatSecondsReadable)(Number(r.DURATION) || 0)}`,
|
|
171
|
+
})),
|
|
172
|
+
},
|
|
173
|
+
], { onCancel });
|
|
174
|
+
picks = choice.items ?? [];
|
|
175
|
+
}
|
|
176
|
+
if (!picks.length) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
return { info: { type: 'track', id }, linktype: 'track', linkinfo: {}, tracks: picks.map(stampVersion) };
|
|
180
|
+
};
|
|
151
181
|
const resolveAdvancedSearch = async (filters) => {
|
|
152
182
|
const limit = toFiniteNumber(options.searchLimit) ?? 50;
|
|
153
183
|
const { data: results, query, usedFallback } = await (0, search_1.searchAdvancedTracks)(filters, { limit });
|
|
@@ -235,7 +265,7 @@ const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
|
235
265
|
{
|
|
236
266
|
type: 'text',
|
|
237
267
|
name: 'query',
|
|
238
|
-
message: 'Enter a URL, a search term, or a prefix (search: isrc: upc: flow radio: chart artist-top: episode:):',
|
|
268
|
+
message: 'Enter a URL, a search term, or a prefix (search: isrc: upc: flow radio: chart artist-top: mix: library episode:):',
|
|
239
269
|
validate: (value) => (value ? true : false),
|
|
240
270
|
},
|
|
241
271
|
], { onCancel });
|
|
@@ -307,6 +337,26 @@ const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
|
307
337
|
throw new Error(`Artist ${aid} returned no top tracks.`);
|
|
308
338
|
}
|
|
309
339
|
}
|
|
340
|
+
// `mix:<trackId>` — a "more like this" mix. Tokens come attached, so unlike
|
|
341
|
+
// the public radio sources there is no per-track lookup before downloading.
|
|
342
|
+
if (!searchData && url && url.startsWith('mix:')) {
|
|
343
|
+
const sid = url.slice('mix:'.length).trim();
|
|
344
|
+
console.log(signale_1.default.info(`Building a mix from track ${sid}…`));
|
|
345
|
+
const { data } = await (0, gerdur_core_1.getTrackMix)(sid, toFiniteNumber(options.searchLimit) ?? 40);
|
|
346
|
+
searchData = await pickGwTracks(data, `mix:${sid}`);
|
|
347
|
+
if (!searchData) {
|
|
348
|
+
throw new Error(`Track ${sid} returned no mix.`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// `library` — the logged-in account's loved tracks, private ones included.
|
|
352
|
+
if (!searchData && url && (url === 'library' || url === 'favorites' || url === 'favourites')) {
|
|
353
|
+
console.log(signale_1.default.info('Fetching your loved tracks…'));
|
|
354
|
+
const { data, total } = await (0, gerdur_core_1.getMyFavoriteTracks)(undefined, toFiniteNumber(options.searchLimit) ?? 100);
|
|
355
|
+
if (!data.length) {
|
|
356
|
+
throw new Error(`Your library has no loved tracks (total ${total}).`);
|
|
357
|
+
}
|
|
358
|
+
searchData = await pickGwTracks(data, 'library');
|
|
359
|
+
}
|
|
310
360
|
// `episode:<id>` — a podcast episode (a plain MP3: no licence, decryption or tagging).
|
|
311
361
|
if (!searchData && url && url.startsWith('episode:')) {
|
|
312
362
|
const eid = url.slice('episode:'.length).trim();
|
package/dist/src/index.d.ts
CHANGED
|
@@ -63,7 +63,7 @@ export type { MBRecording, MBRelease, MBArtistCredit, CoverArt, CoverArtImage }
|
|
|
63
63
|
export { getTrackBuffer, getTaggedTrack, downloadTrackToFile } from './lib/api-download';
|
|
64
64
|
export type { Quality, GetTrackBufferOptions, DownloadTrackOptions, DownloadResult } from './lib/api-download';
|
|
65
65
|
export { default as Config, globalConfigPath, resolveConfigFile } from './lib/config';
|
|
66
|
-
export { initDeezerApi, parseInfo, searchMusic, searchFacets, searchPublicApi, searchTracks, searchAlbums, searchArtists, searchPlaylists, buildAdvancedQuery, suggest, getGenres, getChart, getChartTracks, getGenreArtists, getEditorialList, getEditorialReleases, getEditorialSelection, getEditorialCharts, getArtistTopTracks, getRelatedArtists, getArtistAlbums, getArtistPlaylists, getArtistRadioTracks, getUserFlow, getUserFavoriteTracks, getUserFavoriteAlbums, getUserFavoriteArtists, getUserPlaylists, getUserRadios, getUserChartTracks, getRadios, getRadioTracks, getRadioGenres, getEpisode, getShowEpisodes, refreshTrackTokens, getTrackByISRC, getAlbumByUPC, getTrackPreview, downloadPreview, formatName, toFormat, DEEZER_FORMATS, getUser, getTrackInfo, getAlbumInfo, getAlbumTracks, getPlaylistInfo, getPlaylistTracks, getArtistInfo, getDiscography, getLyrics, getTrackDownloadUrl, resolveDownloadUrls, streamTrackDownload, downloadTrackBuffer, createDecryptStream, getStream, addTrackTags, getRichAlbum, normalizeContributors, toLrc, GeoBlocked, DeezerError, } from 'gerdur-core';
|
|
66
|
+
export { initDeezerApi, parseInfo, searchMusic, searchFacets, searchPublicApi, searchTracks, searchAlbums, searchArtists, searchPlaylists, buildAdvancedQuery, suggest, getGenres, getChart, getChartTracks, getGenreArtists, getEditorialList, getEditorialReleases, getEditorialSelection, getEditorialCharts, getArtistTopTracks, getRelatedArtists, getArtistAlbums, getArtistPlaylists, getArtistRadioTracks, getUserFlow, getUserFavoriteTracks, getUserFavoriteAlbums, getUserFavoriteArtists, getUserPlaylists, getUserRadios, getUserChartTracks, getRadios, getRadioTracks, getRadioGenres, getEpisode, getShowEpisodes, getMyPlaylists, getMyFavoriteTracks, getMyFavoriteTrackIds, getMyFavoriteAlbums, getMyFavoriteArtists, getMyFavoritePlaylists, getMyFavoriteRadios, getMyFavoriteShows, getTrackMix, refreshTrackTokens, getTrackByISRC, getAlbumByUPC, getTrackPreview, downloadPreview, formatName, toFormat, DEEZER_FORMATS, getUser, getTrackInfo, getAlbumInfo, getAlbumTracks, getPlaylistInfo, getPlaylistTracks, getArtistInfo, getDiscography, getLyrics, getTrackDownloadUrl, resolveDownloadUrls, streamTrackDownload, downloadTrackBuffer, createDecryptStream, getStream, addTrackTags, getRichAlbum, normalizeContributors, toLrc, GeoBlocked, DeezerError, } from 'gerdur-core';
|
|
67
67
|
export type { AddTrackTagsOptions, TaggedTrack, TrackTagModel, RichAlbum, ResolvedUrl, NormalizedContributors, } from 'gerdur-core';
|
|
68
68
|
export type { advancedSearchFilters, searchOrder, searchEntity, publicApiSearchOptions, publicApiSearchResponse, searchResultTrack, searchResultAlbum, searchResultArtist, searchResultPlaylist, suggestResult, publicApiList, chartType, chartTrack, chartAlbum, chartArtist, chartPlaylist, chartPodcast, genreType, editorialType, artistAlbumResult, userFavoriteTrack, userFavoriteAlbum, userFavoriteArtist, userPlaylistResult, radioResult, radioGenre, } from 'gerdur-core/types';
|
|
69
69
|
export type { Quality as MediaFormat, DeezerFormat, TrackPreview, StreamTrackOptions, TrackStream, StreamResponse, DeezerErrorPayload, } from 'gerdur-core';
|
package/dist/src/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
34
34
|
};
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.getUserFavoriteAlbums = exports.getUserFavoriteTracks = exports.getUserFlow = 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.searchFacets = exports.searchMusic = exports.parseInfo = exports.initDeezerApi = exports.resolveConfigFile = exports.globalConfigPath = exports.Config = exports.downloadTrackToFile = exports.getTaggedTrack = exports.getTrackBuffer = exports.PoliteJsonClient = exports.getCoverArtByISRC = exports.getRecordingCoverArt = exports.getBestCoverArtUrl = exports.getCoverArt = exports.getMusicBrainzRelease = exports.getMusicBrainzRecording = exports.lookupRecordingByISRC = exports.configureMusicBrainz = exports.defaultSession = exports.CoreSession = exports.createCoreSession = 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.downloadTrackBuffer = exports.streamTrackDownload = exports.resolveDownloadUrls = exports.getTrackDownloadUrl = 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.refreshTrackTokens = exports.getShowEpisodes = exports.getEpisode = exports.getRadioGenres = exports.getRadioTracks = exports.getRadios = exports.getUserChartTracks = exports.getUserRadios = exports.getUserPlaylists = exports.getUserFavoriteArtists = void 0;
|
|
37
|
+
exports.DeezerError = exports.GeoBlocked = exports.toLrc = exports.normalizeContributors = exports.getRichAlbum = exports.addTrackTags = exports.getStream = exports.createDecryptStream = exports.downloadTrackBuffer = exports.streamTrackDownload = exports.resolveDownloadUrls = exports.getTrackDownloadUrl = 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.refreshTrackTokens = exports.getTrackMix = exports.getMyFavoriteShows = exports.getMyFavoriteRadios = exports.getMyFavoritePlaylists = exports.getMyFavoriteArtists = exports.getMyFavoriteAlbums = exports.getMyFavoriteTrackIds = exports.getMyFavoriteTracks = exports.getMyPlaylists = exports.getShowEpisodes = exports.getEpisode = exports.getRadioGenres = exports.getRadioTracks = exports.getRadios = exports.getUserChartTracks = exports.getUserRadios = exports.getUserPlaylists = exports.getUserFavoriteArtists = 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; } });
|
|
@@ -142,6 +142,15 @@ Object.defineProperty(exports, "getRadioTracks", { enumerable: true, get: functi
|
|
|
142
142
|
Object.defineProperty(exports, "getRadioGenres", { enumerable: true, get: function () { return gerdur_core_3.getRadioGenres; } });
|
|
143
143
|
Object.defineProperty(exports, "getEpisode", { enumerable: true, get: function () { return gerdur_core_3.getEpisode; } });
|
|
144
144
|
Object.defineProperty(exports, "getShowEpisodes", { enumerable: true, get: function () { return gerdur_core_3.getShowEpisodes; } });
|
|
145
|
+
Object.defineProperty(exports, "getMyPlaylists", { enumerable: true, get: function () { return gerdur_core_3.getMyPlaylists; } });
|
|
146
|
+
Object.defineProperty(exports, "getMyFavoriteTracks", { enumerable: true, get: function () { return gerdur_core_3.getMyFavoriteTracks; } });
|
|
147
|
+
Object.defineProperty(exports, "getMyFavoriteTrackIds", { enumerable: true, get: function () { return gerdur_core_3.getMyFavoriteTrackIds; } });
|
|
148
|
+
Object.defineProperty(exports, "getMyFavoriteAlbums", { enumerable: true, get: function () { return gerdur_core_3.getMyFavoriteAlbums; } });
|
|
149
|
+
Object.defineProperty(exports, "getMyFavoriteArtists", { enumerable: true, get: function () { return gerdur_core_3.getMyFavoriteArtists; } });
|
|
150
|
+
Object.defineProperty(exports, "getMyFavoritePlaylists", { enumerable: true, get: function () { return gerdur_core_3.getMyFavoritePlaylists; } });
|
|
151
|
+
Object.defineProperty(exports, "getMyFavoriteRadios", { enumerable: true, get: function () { return gerdur_core_3.getMyFavoriteRadios; } });
|
|
152
|
+
Object.defineProperty(exports, "getMyFavoriteShows", { enumerable: true, get: function () { return gerdur_core_3.getMyFavoriteShows; } });
|
|
153
|
+
Object.defineProperty(exports, "getTrackMix", { enumerable: true, get: function () { return gerdur_core_3.getTrackMix; } });
|
|
145
154
|
Object.defineProperty(exports, "refreshTrackTokens", { enumerable: true, get: function () { return gerdur_core_3.refreshTrackTokens; } });
|
|
146
155
|
Object.defineProperty(exports, "getTrackByISRC", { enumerable: true, get: function () { return gerdur_core_3.getTrackByISRC; } });
|
|
147
156
|
Object.defineProperty(exports, "getAlbumByUPC", { enumerable: true, get: function () { return gerdur_core_3.getAlbumByUPC; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gerdur",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.0",
|
|
4
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
5
|
"keywords": [
|
|
6
6
|
"deezer",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"adm-zip": "^0.5.16",
|
|
64
64
|
"chalk": "^4.1.2",
|
|
65
65
|
"commander": "^9.5.0",
|
|
66
|
-
"gerdur-core": "^2.
|
|
66
|
+
"gerdur-core": "^2.19.0",
|
|
67
67
|
"dot-prop": "^6.0.1",
|
|
68
68
|
"got": "^11.8.6",
|
|
69
69
|
"gradient-string": "^2.0.2",
|