gerdur 2.4.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 CHANGED
@@ -1,5 +1,24 @@
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
+
3
22
  ## 2.4.0 - 2026-08-31
4
23
 
5
24
  ### Added
package/README.md CHANGED
@@ -258,13 +258,17 @@ Session methods:
258
258
  - **browse** — `genres`, `chart`, `chartTracks`, `editorialSections`,
259
259
  `artistTopTracks`, `relatedArtists`, `artistAlbums`, `artistRadio`,
260
260
  `trackByISRC`, `albumByUPC`
261
- - **user / download** — `getUser`, `getTrackBuffer`, `downloadTrack`,
262
- `downloadTracks`, `downloadUrl`, `trackPreview`, `downloadPreview`
261
+ - **user / download** — `getUser`, `getTrackBuffer`, `streamTrack`,
262
+ `downloadTrack`, `downloadTracks`, `downloadUrl`, `trackPreview`, `downloadPreview`
263
263
 
264
264
  Every download call is silent; `downloadTracks` / `downloadUrl` accept
265
265
  `concurrency` and an `onProgress` callback and return one `{path, written} | null`
266
266
  per track.
267
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
+
268
272
  `searchAdvanced(filters, options?)` builds a Deezer advanced-search query from
269
273
  `{query?, artist?, album?, track?, label?, durMin?, durMax?, bpmMin?, bpmMax?}`
270
274
  and takes `{order?, strict?, limit?, index?, fallback?}`. Deezer's operators are
@@ -315,8 +319,10 @@ or `createSession`):
315
319
  `getEditorialCharts`, `getArtistTopTracks`, `getRelatedArtists`,
316
320
  `getArtistAlbums`, `getArtistPlaylists`, `getArtistRadioTracks`,
317
321
  `getTrackByISRC`, `getAlbumByUPC`
318
- - **Download** — `getTrackDownloadUrl`, `resolveDownloadUrls`, `getTrackPreview`,
319
- `downloadPreview`, `formatName`, `toFormat`, `DEEZER_FORMATS`, `GeoBlocked`
322
+ - **Download** — `getTrackDownloadUrl`, `resolveDownloadUrls`,
323
+ `streamTrackDownload`, `createDecryptStream`, `getStream`, `getTrackPreview`,
324
+ `downloadPreview`, `formatName`, `toFormat`, `DEEZER_FORMATS`
325
+ - **Errors** — `DeezerError` (`code` / `keys` / `retryable`), `GeoBlocked`
320
326
 
321
327
  ### Auth & config helpers
322
328
 
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gerdur",
3
- "version": "2.4.0",
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.3.0",
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",
@@ -59,7 +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, 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';
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 } from 'gerdur-core';
65
+ 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.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
+ 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; } });
@@ -128,8 +128,12 @@ Object.defineProperty(exports, "getDiscography", { enumerable: true, get: functi
128
128
  Object.defineProperty(exports, "getLyrics", { enumerable: true, get: function () { return gerdur_core_1.getLyrics; } });
129
129
  Object.defineProperty(exports, "getTrackDownloadUrl", { enumerable: true, get: function () { return gerdur_core_1.getTrackDownloadUrl; } });
130
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; } });
131
134
  Object.defineProperty(exports, "addTrackTags", { enumerable: true, get: function () { return gerdur_core_1.addTrackTags; } });
132
135
  Object.defineProperty(exports, "getRichAlbum", { enumerable: true, get: function () { return gerdur_core_1.getRichAlbum; } });
133
136
  Object.defineProperty(exports, "normalizeContributors", { enumerable: true, get: function () { return gerdur_core_1.normalizeContributors; } });
134
137
  Object.defineProperty(exports, "toLrc", { enumerable: true, get: function () { return gerdur_core_1.toLrc; } });
135
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>;
@@ -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
- /** Decrypt a track that was streamed to a temp file. */
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
- outFile = await (0, decrypt_1.decryptDownloadFile)(tmpfile, track.SNG_ID);
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,7 +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 } from 'gerdur-core';
6
+ import type { TrackPreview, StreamTrackOptions, TrackStream } from 'gerdur-core';
7
7
  /** Search result categories accepted by {@link Session.search}. */
8
8
  export type SearchType = 'ALBUM' | 'ARTIST' | 'TRACK' | 'PLAYLIST' | 'RADIO' | 'SHOW' | 'USER' | 'LIVESTREAM' | 'CHANNEL';
9
9
  export interface SessionOptions {
@@ -92,6 +92,13 @@ export declare class Session {
92
92
  downloadPreview(track: trackType | string | number): Promise<Buffer | null>;
93
93
  /** Return a fully tagged audio Buffer for a track (nothing written to disk). */
94
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>;
95
102
  /** Download a single track to disk. */
96
103
  downloadTrack(track: trackType, quality?: Quality, options?: DownloadTrackOptions): Promise<DownloadResult | null>;
97
104
  /**
@@ -128,6 +128,15 @@ class Session {
128
128
  getTrackBuffer(track, quality = '320', options = {}) {
129
129
  return (0, api_download_1.getTrackBuffer)(track, quality, options);
130
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
+ }
131
140
  /** Download a single track to disk. */
132
141
  downloadTrack(track, quality = '320', options = {}) {
133
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.4.0",
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.3.0",
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",