gerdur-core 1.0.4 → 2.0.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,56 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.0 - 2026-08-31
4
+
5
+ Metadata overhaul — extract everything Deezer actually exposes for a track.
6
+
7
+ ### Breaking
8
+
9
+ - **`addTrackTags(buffer, track, options)`** — the third argument is now an
10
+ options object, not a cover-size number, and it resolves to
11
+ **`{buffer, model}`** instead of a bare `Buffer`. Migrate:
12
+ `await addTrackTags(buf, track, 500)` → `(await addTrackTags(buf, track, {coverSize: 500})).buffer`.
13
+ `model` is the full `TrackTagModel` (all resolved fields + `model.lyricsSynced`,
14
+ an LRC document).
15
+ - `writeMetadataMp3` / `writeMetadataFlac` now take the `TrackTagModel`, not a
16
+ raw track + public-album payload.
17
+ - `downloadAlbumCover` clamps its size to Deezer's real ceiling of **1800**
18
+ (was unbounded); `coverSize` is re-exported from here.
19
+
20
+ ### Added
21
+
22
+ - **ReplayGain** — `GAIN` (present on every `song.getData`) is written as
23
+ `REPLAYGAIN_TRACK_GAIN` (`TXXX` on MP3, Vorbis comment on FLAC).
24
+ - **Rich credits** via `normalizeContributors()` — `SNG_CONTRIBUTORS` has
25
+ inconsistent keys across the catalogue (`main_artist` / `mainartist` / `artist`,
26
+ `music publisher` with a space, …); this normalises them and surfaces
27
+ `featuring`, mastering / mixing / recording engineers, producers, mixers,
28
+ performers.
29
+ - **Featured artists** from `SNG_CONTRIBUTORS.featuring` → `TXXX:FEATURING` /
30
+ `FEATURING`, plus a combined `ARTISTS` tag.
31
+ - **Original release date** — `getRichAlbum()` merges gw `album.getData`
32
+ (`ORIGINAL_RELEASE_DATE`, `COPYRIGHT`/`PRODUCER_LINE`, `NUMBER_DISK`,
33
+ `SUBTYPES`) with the public `/album/` (label, genres, record type). Writes
34
+ `ORIGINALDATE`/`ORIGINALYEAR` distinct from the reissue `DATE`.
35
+ - **BPM** from the public `/track/` endpoint (`TBPM` + precise `TXXX:BPM`).
36
+ - **Synced lyrics** — `toLrc()` renders `LYRICS_SYNC_JSON` (already fetched with
37
+ the plain lyrics) as an LRC document, exposed on `model.lyricsSynced` and
38
+ embedded in FLAC `LYRICS`.
39
+ - Real `©` / `℗` lines, disc totals, compilation/live flags from `SUBTYPES`,
40
+ proper `EXPLICIT_LYRICS_STATUS` enum handling (`1`/`4` = explicit; `2`/`6` =
41
+ unknown, not "explicit"), `iTunesAdvisory`, Deezer track/album/artist/label/
42
+ provider ids, `URL_REWRITING` slug, popularity rank, and the artist photo as a
43
+ second embedded picture.
44
+ - New exports: `getRichAlbum`, `RichAlbum`, `normalizeContributors`,
45
+ `NormalizedContributors`, `toLrc`, `buildTagModel`, `TrackTagModel`, `Person`,
46
+ `AddTrackTagsOptions`, `TaggedTrack`, `downloadArtistImage`, `MAX_COVER_SIZE`.
47
+
48
+ ### Changed
49
+
50
+ - Album/playlist tracks (which come without `SNG_CONTRIBUTORS` / `VERSION` /
51
+ `GAIN`) are transparently hydrated with one coalesced `song.getData` before
52
+ tagging. Disable via `addTrackTags(…, {richCredits: false})`.
53
+
3
54
  ## 1.0.4 - 2026-08-31
4
55
 
5
56
  ### Added
package/README.md CHANGED
@@ -68,11 +68,14 @@ const data = await downloadBuffer(trackData.trackUrl);
68
68
  // Decrypt track if needed
69
69
  const outFile = trackData.isEncrypted ? api.decryptDownload(data, track.SNG_ID) : data;
70
70
 
71
- // Add id3 metadata
72
- const trackWithMetadata = await api.addTrackTags(outFile, track, 500);
71
+ // Add metadata — resolves album info, credits, lyrics and cover from Deezer
72
+ const {buffer, model} = await api.addTrackTags(outFile, track, {coverSize: 500});
73
73
 
74
74
  // Save file to disk
75
- fs.writeFileSync(track.SNG_TITLE + '.mp3', trackWithMetadata);
75
+ fs.writeFileSync(track.SNG_TITLE + '.mp3', buffer);
76
+
77
+ // Time-synced lyrics, when Deezer has them, come back as an LRC document
78
+ if (model.lyricsSynced) fs.writeFileSync(track.SNG_TITLE + '.lrc', model.lyricsSynced);
76
79
  ```
77
80
 
78
81
  ### [Read FAQ](docs/faq.md)
@@ -173,13 +176,18 @@ All method returns `Object` or throws `Error`. Make sure to catch error on your
173
176
  | `data` | Yes | `buffer` | downloaded song buffer |
174
177
  | `song_id` | Yes | `string` | track id |
175
178
 
176
- ### `.addTrackTags(data, track,coverSize)`
179
+ ### `.addTrackTags(data, track, options?)`
180
+
181
+ Resolves album info, credits, lyrics and artwork from Deezer and writes them into
182
+ the audio (ID3v2.3 for MP3, Vorbis comments for FLAC). Returns
183
+ `{buffer, model}` — `model` is the full `TrackTagModel`, including
184
+ `model.lyricsSynced` (an LRC document) when the track has time-synced lyrics.
177
185
 
178
- | Parameters | Required | Type | Description |
179
- | ----------- | :------: | --------: | ---------------------: |
180
- | `data` | Yes | `buffer` | downloaded song buffer |
181
- | `track` | Yes | `string` | track object |
182
- | `coverSize` | No | `56-1800` | cover art size |
186
+ | Parameters | Required | Type | Description |
187
+ | ---------- | :------: | -------: | --------------------------------------------- |
188
+ | `data` | Yes | `buffer` | downloaded, decrypted song buffer |
189
+ | `track` | Yes | `object` | track object from `getTrackInfo` / `parseInfo` |
190
+ | `options` | No | `object` | `AddTrackTagsOptions` — `coverSize` (56–1800), pre-fetched `album`/`lyrics`/`cover`, and toggles (`richCredits`, `embedArtistImage`, `deezerIds`, …) |
183
191
 
184
192
  ###
185
193
 
@@ -91,7 +91,7 @@ declare class Metaflac {
91
91
  *
92
92
  * @param {string} filename
93
93
  */
94
- importPicture(picture: Buffer, dimension: number, mime: 'image/jpeg' | 'image/png'): void;
94
+ importPicture(picture: Buffer, dimension: number, mime: 'image/jpeg' | 'image/png', type?: number, description?: string): void;
95
95
  /**
96
96
  * Return all tags.
97
97
  */
@@ -236,9 +236,11 @@ class Metaflac {
236
236
  *
237
237
  * @param {string} filename
238
238
  */
239
- importPicture(picture, dimension, mime) {
239
+ importPicture(picture, dimension, mime, type = 3, description = '') {
240
240
  const spec = this.buildSpecification({
241
+ type,
241
242
  mime,
243
+ description,
242
244
  width: dimension,
243
245
  height: dimension,
244
246
  });
@@ -1,10 +1,14 @@
1
1
  /// <reference types="node" />
2
2
  import type { trackType } from '../types';
3
- type coverSize = 56 | 250 | 500 | 1000 | 1500 | 1800 | number;
3
+ export type coverSize = 56 | 250 | 500 | 1000 | 1200 | 1500 | 1800 | number;
4
+ export declare const MAX_COVER_SIZE = 1800;
4
5
  /**
5
- *
6
- * @param {Object} track track info json from deezer api
7
- * @param {Number} albumCoverSize in pixel, between 56-1800
6
+ * @param track track info json from deezer api
7
+ * @param albumCoverSize in pixels, 56–1800 (clamped)
8
8
  */
9
9
  export declare const downloadAlbumCover: (track: trackType, albumCoverSize: coverSize) => Promise<Buffer | null>;
10
- export {};
10
+ /**
11
+ * Artist photo (`ART_PICTURE` md5). Returns null when the track has no artist
12
+ * image (common for small-catalogue artists).
13
+ */
14
+ export declare const downloadArtistImage: (track: trackType, size?: coverSize) => Promise<Buffer | null>;
@@ -3,35 +3,49 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.downloadAlbumCover = void 0;
6
+ exports.downloadArtistImage = exports.downloadAlbumCover = exports.MAX_COVER_SIZE = void 0;
7
7
  const fast_lru_1 = __importDefault(require("../lib/fast-lru"));
8
8
  const http_1 = require("../lib/http");
9
+ exports.MAX_COVER_SIZE = 1800;
9
10
  // expire cache in 30 minutes
10
11
  const lru = new fast_lru_1.default({
11
12
  maxSize: 50,
12
13
  ttl: 30 * 60000,
13
14
  });
14
- /**
15
- *
16
- * @param {Object} track track info json from deezer api
17
- * @param {Number} albumCoverSize in pixel, between 56-1800
18
- */
19
- const downloadAlbumCover = async (track, albumCoverSize) => {
20
- if (!track.ALB_PICTURE) {
15
+ const clampSize = (n) => Math.max(56, Math.min(exports.MAX_COVER_SIZE, Math.round(n) || 500));
16
+ const imageUrl = (kind, md5, size) => `https://e-cdns-images.dzcdn.net/images/${kind}/${md5}/${size}x${size}-000000-80-0-0.jpg`;
17
+ const fetchImage = async (kind, md5, size) => {
18
+ if (!md5) {
21
19
  return null;
22
20
  }
23
- const cache = lru.get(track.ALB_PICTURE + albumCoverSize);
24
- if (cache) {
25
- return cache;
21
+ const px = clampSize(size);
22
+ const key = `${kind}:${md5}:${px}`;
23
+ const cached = lru.get(key);
24
+ if (cached) {
25
+ return cached;
26
26
  }
27
27
  try {
28
- const url = `https://e-cdns-images.dzcdn.net/images/cover/${track.ALB_PICTURE}/${albumCoverSize}x${albumCoverSize}-000000-80-0-0.jpg`;
29
- const data = await (0, http_1.getBuffer)(url);
30
- lru.set(track.ALB_PICTURE + albumCoverSize, data);
28
+ const data = await (0, http_1.getBuffer)(imageUrl(kind, md5, px));
29
+ lru.set(key, data);
31
30
  return data;
32
31
  }
33
32
  catch (err) {
34
33
  return null;
35
34
  }
36
35
  };
36
+ /**
37
+ * @param track track info json from deezer api
38
+ * @param albumCoverSize in pixels, 56–1800 (clamped)
39
+ */
40
+ const downloadAlbumCover = (track, albumCoverSize) => fetchImage('cover', track.ALB_PICTURE, albumCoverSize);
37
41
  exports.downloadAlbumCover = downloadAlbumCover;
42
+ /**
43
+ * Artist photo (`ART_PICTURE` md5). Returns null when the track has no artist
44
+ * image (common for small-catalogue artists).
45
+ */
46
+ const downloadArtistImage = (track, size = 1000) => {
47
+ var _a, _b;
48
+ const md5 = track.ART_PICTURE || ((_b = (_a = track.ARTISTS) === null || _a === void 0 ? void 0 : _a.find((a) => a.ART_PICTURE)) === null || _b === void 0 ? void 0 : _b.ART_PICTURE);
49
+ return fetchImage('artist', md5, size);
50
+ };
51
+ exports.downloadArtistImage = downloadArtistImage;
@@ -0,0 +1,31 @@
1
+ import type { sngContributors } from '../types';
2
+ export interface NormalizedContributors {
3
+ /** billed / lead artists */
4
+ mainArtists: string[];
5
+ /** "feat." artists */
6
+ featuring: string[];
7
+ composers: string[];
8
+ /** author / writer / lyricist, de-duplicated */
9
+ lyricists: string[];
10
+ producers: string[];
11
+ /** {role, name} for mastering / mixing / recording (+ "second") engineers */
12
+ engineers: {
13
+ role: string;
14
+ name: string;
15
+ }[];
16
+ mixers: string[];
17
+ /** other performers Deezer credits by role, e.g. background vocalist */
18
+ performers: {
19
+ role: string;
20
+ name: string;
21
+ }[];
22
+ publishers: string[];
23
+ }
24
+ /**
25
+ * Turn Deezer's inconsistent `SNG_CONTRIBUTORS` bag into a stable shape.
26
+ *
27
+ * The role keys vary across the catalogue (`main_artist` / `mainartist` /
28
+ * `artist`, `musicpublisher` / `music publisher`, …) and the value is
29
+ * occasionally an empty array — this hides all of that.
30
+ */
31
+ export declare const normalizeContributors: (raw: sngContributors | undefined) => NormalizedContributors;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeContributors = void 0;
4
+ /** collapse `Music Publisher`, `music_publisher`, `musicpublisher` → `musicpublisher` */
5
+ const canon = (key) => key.toLowerCase().replace(/[\s_-]+/g, '');
6
+ const ENGINEER_LABELS = {
7
+ masteringengineer: 'mastering engineer',
8
+ mixingengineer: 'mixing engineer',
9
+ recordingengineer: 'recording engineer',
10
+ recordingsecondengineer: 'assistant recording engineer',
11
+ assistantengineer: 'assistant engineer',
12
+ engineer: 'engineer',
13
+ studiopersonnel: 'engineer',
14
+ };
15
+ const uniq = (arr) => [...new Set(arr.filter((x) => x && x.trim()))].map((x) => x.trim());
16
+ /**
17
+ * Turn Deezer's inconsistent `SNG_CONTRIBUTORS` bag into a stable shape.
18
+ *
19
+ * The role keys vary across the catalogue (`main_artist` / `mainartist` /
20
+ * `artist`, `musicpublisher` / `music publisher`, …) and the value is
21
+ * occasionally an empty array — this hides all of that.
22
+ */
23
+ const normalizeContributors = (raw) => {
24
+ const out = {
25
+ mainArtists: [],
26
+ featuring: [],
27
+ composers: [],
28
+ lyricists: [],
29
+ producers: [],
30
+ engineers: [],
31
+ mixers: [],
32
+ performers: [],
33
+ publishers: [],
34
+ };
35
+ if (!raw || Array.isArray(raw)) {
36
+ return out;
37
+ }
38
+ const bucket = {};
39
+ for (const [key, value] of Object.entries(raw)) {
40
+ if (!Array.isArray(value)) {
41
+ continue;
42
+ }
43
+ const k = canon(key);
44
+ bucket[k] = (bucket[k] || []).concat(value);
45
+ }
46
+ out.mainArtists = uniq([...(bucket.mainartist || []), ...(bucket.artist || [])]);
47
+ out.featuring = uniq([...(bucket.featuring || []), ...(bucket.featuredartist || []), ...(bucket.feat || [])]);
48
+ out.composers = uniq(bucket.composer || []);
49
+ out.lyricists = uniq([
50
+ ...(bucket.author || []),
51
+ ...(bucket.writer || []),
52
+ ...(bucket.lyricist || []),
53
+ ...(bucket.songwriter || []),
54
+ ]);
55
+ out.producers = uniq([...(bucket.producer || []), ...(bucket.coproducer || []), ...(bucket.executiveproducer || [])]);
56
+ out.mixers = uniq([...(bucket.mixer || []), ...(bucket.remixer || [])]);
57
+ out.publishers = uniq([
58
+ ...(bucket.publisher || []),
59
+ ...(bucket.musicpublisher || []),
60
+ ...(bucket.originalpublisher || []),
61
+ ]);
62
+ for (const [canonKey, label] of Object.entries(ENGINEER_LABELS)) {
63
+ for (const name of uniq(bucket[canonKey] || [])) {
64
+ out.engineers.push({ role: label, name });
65
+ }
66
+ }
67
+ for (const [canonKey, names] of Object.entries(bucket)) {
68
+ if (canonKey === 'mainartist' ||
69
+ canonKey === 'artist' ||
70
+ canonKey === 'featuring' ||
71
+ canonKey === 'composer' ||
72
+ canonKey === 'mixer' ||
73
+ canonKey === 'producer' ||
74
+ canonKey in ENGINEER_LABELS ||
75
+ /publisher|author|writer|lyricist/.test(canonKey)) {
76
+ continue;
77
+ }
78
+ // remaining roles: vocalist, backgroundvocalist, instruments, arranger, …
79
+ const role = canonKey.replace(/([a-z])([A-Z])/g, '$1 $2');
80
+ for (const name of uniq(names)) {
81
+ out.performers.push({ role, name });
82
+ }
83
+ }
84
+ return out;
85
+ };
86
+ exports.normalizeContributors = normalizeContributors;
@@ -1,3 +1,12 @@
1
1
  /// <reference types="node" />
2
- import type { albumTypePublicApi, trackType } from '../types';
3
- export declare const writeMetadataFlac: (buffer: Buffer, track: trackType, album: albumTypePublicApi | null, dimension: number, cover?: Buffer | null) => Buffer;
2
+ import type { TrackTagModel } from './model';
3
+ export interface FlacWriteOptions {
4
+ /** embed the LRC document as a `SYNCEDLYRICS` Vorbis comment. default true */
5
+ embedSyncedLyrics?: boolean;
6
+ }
7
+ /**
8
+ * Write Vorbis comments + PICTURE blocks from the canonical model. Vorbis
9
+ * comments are free-form, so every field Deezer gives us lands here — including
10
+ * multi-valued credits (one comment per value) and synced lyrics.
11
+ */
12
+ export declare const writeMetadataFlac: (buffer: Buffer, m: TrackTagModel, options?: FlacWriteOptions) => Buffer;
@@ -5,76 +5,101 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.writeMetadataFlac = void 0;
7
7
  const metaflac_js_1 = __importDefault(require("../lib/metaflac-js"));
8
- const writeMetadataFlac = (buffer, track, album, dimension, cover) => {
8
+ /**
9
+ * Write Vorbis comments + PICTURE blocks from the canonical model. Vorbis
10
+ * comments are free-form, so every field Deezer gives us lands here — including
11
+ * multi-valued credits (one comment per value) and synced lyrics.
12
+ */
13
+ const writeMetadataFlac = (buffer, m, options = {}) => {
9
14
  const flac = new metaflac_js_1.default(buffer);
10
- const RELEASE_YEAR = album ? album.release_date.split('-')[0] : null;
11
- flac.setTag('TITLE=' + track.SNG_TITLE);
12
- flac.setTag('ALBUM=' + track.ALB_TITLE);
13
- flac.setTag('ARTIST=' + track.ARTISTS.map((a) => a.ART_NAME).join(', '));
14
- flac.setTag('TRACKNUMBER=' + track.TRACK_NUMBER.toLocaleString('en-US', { minimumIntegerDigits: 2 }));
15
- if (album) {
16
- const TOTALTRACKS = album.nb_tracks.toLocaleString('en-US', { minimumIntegerDigits: 2 });
17
- if (album.genres.data.length > 0) {
18
- for (const genre of album.genres.data) {
19
- flac.setTag('GENRE=' + genre.name);
20
- }
21
- }
22
- flac.setTag('TRACKTOTAL=' + TOTALTRACKS);
23
- flac.setTag('TOTALTRACKS=' + TOTALTRACKS);
24
- flac.setTag('RELEASETYPE=' + album.record_type);
25
- flac.setTag('ALBUMARTIST=' + album.artist.name);
26
- flac.setTag('BARCODE=' + album.upc);
27
- flac.setTag('LABEL=' + album.label);
28
- flac.setTag('DATE=' + album.release_date);
29
- flac.setTag('YEAR=' + RELEASE_YEAR);
30
- flac.setTag(`COMPILATION=${album.artist.name.match(/various/i) ? '1' : '0'}`);
15
+ const tag = (key, value) => {
16
+ if (value !== undefined && value !== null && value !== '')
17
+ flac.setTag(`${key}=${value}`);
18
+ };
19
+ const multi = (key, values) => {
20
+ for (const v of values)
21
+ if (v)
22
+ flac.setTag(`${key}=${v}`);
23
+ };
24
+ tag('TITLE', m.title);
25
+ tag('SUBTITLE', m.subtitle);
26
+ if (m.subtitle)
27
+ tag('VERSION', m.subtitle);
28
+ tag('ALBUM', m.album);
29
+ multi('ARTIST', m.artists);
30
+ multi('ARTISTS', m.artists);
31
+ tag('ALBUMARTIST', m.albumArtist);
32
+ multi('COMPOSER', m.composers);
33
+ multi('LYRICIST', m.lyricists);
34
+ multi('WRITER', m.lyricists);
35
+ multi('PRODUCER', m.producers);
36
+ multi('MIXER', [...m.mixers, ...m.engineers.filter((e) => e.role.startsWith('mixing')).map((e) => e.name)]);
37
+ multi('PUBLISHER', m.publishers);
38
+ for (const e of m.engineers)
39
+ tag('ENGINEER', e.name);
40
+ // one human-readable line keeping the role breakdown (Picard-style)
41
+ for (const e of m.engineers)
42
+ tag('CREDITS', `${e.name} (${e.role})`);
43
+ for (const p of m.performers)
44
+ tag('PERFORMER', `${p.name} (${p.role})`);
45
+ multi('FEATURING', m.featuredArtists);
46
+ tag('TRACKNUMBER', m.trackNumber || '');
47
+ tag('TRACKTOTAL', m.trackTotal);
48
+ tag('TOTALTRACKS', m.trackTotal);
49
+ tag('DISCNUMBER', m.discNumber || '');
50
+ tag('DISCTOTAL', m.discTotal);
51
+ tag('TOTALDISCS', m.discTotal);
52
+ tag('ISRC', m.isrc);
53
+ tag('BARCODE', m.barcode);
54
+ tag('LENGTH', Math.round(m.durationMs / 1000) || '');
55
+ if (m.bpm)
56
+ tag('BPM', m.bpm);
57
+ tag('MEDIA', 'Digital Media');
58
+ multi('GENRE', m.genres);
59
+ tag('LABEL', m.label);
60
+ tag('ORGANIZATION', m.label);
61
+ tag('RELEASETYPE', m.releaseType);
62
+ tag('COMPILATION', m.isCompilation ? '1' : '0');
63
+ tag('DATE', m.date);
64
+ tag('YEAR', m.year);
65
+ tag('ORIGINALDATE', m.originalDate);
66
+ tag('ORIGINALYEAR', m.originalYear);
67
+ tag('COPYRIGHT', m.copyright);
68
+ tag('PRODUCERLINE', m.producerLine);
69
+ tag('REPLAYGAIN_TRACK_GAIN', m.replayGainTrackGain);
70
+ tag('ITUNESADVISORY', m.itunesAdvisory);
71
+ if (m.explicit !== 'unknown')
72
+ tag('EXPLICIT', m.explicit === 'explicit' ? '1' : '0');
73
+ // `LYRICS` carries the synced LRC when we have it (players auto-detect the
74
+ // timestamps); `UNSYNCEDLYRICS` is always the plain fallback.
75
+ const synced = m.lyricsSynced && options.embedSyncedLyrics !== false ? m.lyricsSynced : null;
76
+ if (synced || m.lyrics) {
77
+ tag('LYRICS', synced || m.lyrics);
31
78
  }
32
- if (track.DISK_NUMBER) {
33
- flac.setTag('DISCNUMBER=' + track.DISK_NUMBER);
79
+ if (m.lyrics) {
80
+ tag('UNSYNCEDLYRICS', m.lyrics);
34
81
  }
35
- flac.setTag('ISRC=' + track.ISRC);
36
- flac.setTag('LENGTH=' + track.DURATION);
37
- flac.setTag('MEDIA=Digital Media');
38
- if (track.LYRICS) {
39
- flac.setTag('LYRICS=' + track.LYRICS.LYRICS_TEXT);
82
+ tag('LYRICS_WRITER', m.lyricsWriters);
83
+ tag('LYRICS_COPYRIGHT', m.lyricsCopyright);
84
+ if (m.rank !== undefined)
85
+ tag('DEEZER_RANK', m.rank);
86
+ tag('SOURCE', 'Deezer');
87
+ if (m.ids.deezerTrack) {
88
+ tag('SOURCEID', m.ids.deezerTrack);
89
+ tag('DEEZER_TRACK_ID', m.ids.deezerTrack);
40
90
  }
41
- if (track.EXPLICIT_LYRICS) {
42
- flac.setTag('EXPLICIT=' + track.EXPLICIT_LYRICS);
91
+ tag('DEEZER_ALBUM_ID', m.ids.deezerAlbum);
92
+ tag('DEEZER_ARTIST_ID', m.ids.deezerArtist);
93
+ tag('DEEZER_LABEL_ID', m.ids.labelId);
94
+ tag('DEEZER_PROVIDER_ID', m.ids.providerId);
95
+ if (m.ids.slug)
96
+ tag('WEBSITE', `https://www.deezer.com/track/${m.ids.deezerTrack}`);
97
+ if (m.cover) {
98
+ flac.importPicture(m.cover, m.coverSize, 'image/jpeg', 3, 'Front cover');
43
99
  }
44
- if (track.SNG_CONTRIBUTORS && !Array.isArray(track.SNG_CONTRIBUTORS)) {
45
- if (track.SNG_CONTRIBUTORS.main_artist) {
46
- flac.setTag(`COPYRIGHT=${RELEASE_YEAR ? RELEASE_YEAR + ' ' : ''}${track.SNG_CONTRIBUTORS.main_artist[0]}`);
47
- }
48
- if (track.SNG_CONTRIBUTORS.publisher) {
49
- flac.setTag('ORGANIZATION=' + track.SNG_CONTRIBUTORS.publisher.join(', '));
50
- }
51
- if (track.SNG_CONTRIBUTORS.composer) {
52
- flac.setTag('COMPOSER=' + track.SNG_CONTRIBUTORS.composer.join(', '));
53
- }
54
- if (track.SNG_CONTRIBUTORS.publisher) {
55
- flac.setTag('ORGANIZATION=' + track.SNG_CONTRIBUTORS.publisher.join(', '));
56
- }
57
- if (track.SNG_CONTRIBUTORS.producer) {
58
- flac.setTag('PRODUCER=' + track.SNG_CONTRIBUTORS.producer.join(', '));
59
- }
60
- if (track.SNG_CONTRIBUTORS.engineer) {
61
- flac.setTag('ENGINEER=' + track.SNG_CONTRIBUTORS.engineer.join(', '));
62
- }
63
- if (track.SNG_CONTRIBUTORS.writer) {
64
- flac.setTag('WRITER=' + track.SNG_CONTRIBUTORS.writer.join(', '));
65
- }
66
- if (track.SNG_CONTRIBUTORS.author) {
67
- flac.setTag('AUTHOR=' + track.SNG_CONTRIBUTORS.author.join(', '));
68
- }
69
- if (track.SNG_CONTRIBUTORS.mixer) {
70
- flac.setTag('MIXER=' + track.SNG_CONTRIBUTORS.mixer.join(', '));
71
- }
100
+ if (m.artistImage) {
101
+ flac.importPicture(m.artistImage, 1000, 'image/jpeg', 8, 'Artist');
72
102
  }
73
- if (cover) {
74
- flac.importPicture(cover, dimension, 'image/jpeg');
75
- }
76
- flac.setTag('SOURCE=Deezer');
77
- flac.setTag('SOURCEID=' + track.SNG_ID);
78
103
  return flac.getBuffer();
79
104
  };
80
105
  exports.writeMetadataFlac = writeMetadataFlac;
@@ -1,3 +1,9 @@
1
1
  /// <reference types="node" />
2
- import type { albumTypePublicApi, trackType } from '../types';
3
- export declare const writeMetadataMp3: (buffer: Buffer, track: trackType, album: albumTypePublicApi | null, cover?: Buffer | null) => Buffer;
2
+ import type { TrackTagModel } from './model';
3
+ /**
4
+ * Write an ID3v2.3 tag from the canonical model. `browser-id3-writer` is a v2.3
5
+ * writer, so there is no `SYLT` (synced lyrics) or `TDRC`/`TDOR` — synced lyrics
6
+ * ship as a `.lrc` sidecar from the CLI, and dates use `TYER`/`TDAT` plus
7
+ * `TXXX:ORIGINALDATE`.
8
+ */
9
+ export declare const writeMetadataMp3: (buffer: Buffer, m: TrackTagModel) => Buffer;