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.
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildTagModel = void 0;
4
+ const contributors_1 = require("./contributors");
5
+ const lrc_1 = require("./lrc");
6
+ const yearOf = (d) => (d && /^\d{4}/.test(d) ? d.slice(0, 4) : undefined);
7
+ const explicitFrom = (status) => {
8
+ // Deezer EXPLICIT_LYRICS_STATUS: 0 none · 1 explicit · 2 unknown · 3 edited/clean
9
+ // · 4 partially-explicit · 6 no-advice · 7 partially-no-advice
10
+ if (status === 1 || status === 4)
11
+ return { explicit: 'explicit', itunesAdvisory: 1 };
12
+ if (status === 3)
13
+ return { explicit: 'clean', itunesAdvisory: 2 };
14
+ return { explicit: 'unknown', itunesAdvisory: 0 };
15
+ };
16
+ const buildTagModel = (input) => {
17
+ var _a, _b, _c, _d, _e;
18
+ const { track, album, publicTrack, lyrics, coverSize } = input;
19
+ const c = (0, contributors_1.normalizeContributors)(track.SNG_CONTRIBUTORS);
20
+ // main artists: contributors first, else the ARTISTS[] list, else ART_NAME
21
+ const artistsAll = (track.ARTISTS || []).map((a) => a.ART_NAME).filter(Boolean);
22
+ const mainArtists = c.mainArtists.length ? c.mainArtists : artistsAll.length ? artistsAll : [track.ART_NAME];
23
+ const featuredArtists = c.featuring.length ? c.featuring : artistsAll.filter((n) => !mainArtists.includes(n));
24
+ const artists = [...new Set([...mainArtists, ...featuredArtists])];
25
+ const albMeta = album || { genres: [], isCompilation: false, isLive: false };
26
+ const isCompilation = albMeta.isCompilation || /^various/i.test(track.ART_NAME);
27
+ const date = albMeta.releaseDate ||
28
+ ((publicTrack === null || publicTrack === void 0 ? void 0 : publicTrack.release_date) && /^\d{4}-\d{2}-\d{2}$/.test(publicTrack.release_date)
29
+ ? publicTrack.release_date
30
+ : undefined);
31
+ const originalDate = albMeta.originalDate && albMeta.originalDate !== date ? albMeta.originalDate : undefined;
32
+ const gain = parseFloat((_c = (_a = track.GAIN) !== null && _a !== void 0 ? _a : (_b = publicTrack === null || publicTrack === void 0 ? void 0 : publicTrack.gain) === null || _b === void 0 ? void 0 : _b.toString()) !== null && _c !== void 0 ? _c : '');
33
+ const bpmRaw = (_d = publicTrack === null || publicTrack === void 0 ? void 0 : publicTrack.bpm) !== null && _d !== void 0 ? _d : 0;
34
+ const explicit = explicitFrom((_e = track.EXPLICIT_TRACK_CONTENT) === null || _e === void 0 ? void 0 : _e.EXPLICIT_LYRICS_STATUS);
35
+ let releaseType = albMeta.recordType;
36
+ if (albMeta.isLive)
37
+ releaseType = 'live';
38
+ else if (isCompilation)
39
+ releaseType = 'compilation';
40
+ const version = (track.VERSION || (publicTrack === null || publicTrack === void 0 ? void 0 : publicTrack.title_version) || '').trim();
41
+ return {
42
+ title: track.SNG_TITLE,
43
+ subtitle: version || undefined,
44
+ album: track.ALB_TITLE || albMeta.title,
45
+ artists,
46
+ mainArtists,
47
+ featuredArtists,
48
+ albumArtist: albMeta.albumArtist || track.ART_NAME,
49
+ composers: c.composers,
50
+ lyricists: c.lyricists,
51
+ producers: c.producers,
52
+ engineers: c.engineers,
53
+ mixers: c.mixers,
54
+ performers: c.performers,
55
+ publishers: c.publishers,
56
+ trackNumber: Number(track.TRACK_NUMBER) || 0,
57
+ trackTotal: albMeta.trackTotal,
58
+ discNumber: Number(track.DISK_NUMBER) || 1,
59
+ discTotal: albMeta.discTotal,
60
+ isrc: track.ISRC || (publicTrack === null || publicTrack === void 0 ? void 0 : publicTrack.isrc) || undefined,
61
+ barcode: albMeta.upc,
62
+ durationMs: (Number(track.DURATION) || 0) * 1000,
63
+ bpm: bpmRaw > 0 ? bpmRaw : undefined,
64
+ genres: albMeta.genres || [],
65
+ label: albMeta.label,
66
+ releaseType,
67
+ isCompilation,
68
+ date,
69
+ year: yearOf(date),
70
+ originalDate,
71
+ originalYear: yearOf(originalDate),
72
+ copyright: albMeta.copyright,
73
+ producerLine: albMeta.producerLine,
74
+ replayGainTrackGain: Number.isFinite(gain) ? `${gain.toFixed(2)} dB` : undefined,
75
+ explicit: explicit.explicit,
76
+ itunesAdvisory: explicit.itunesAdvisory,
77
+ lyrics: (lyrics === null || lyrics === void 0 ? void 0 : lyrics.LYRICS_TEXT) || undefined,
78
+ lyricsSynced: (0, lrc_1.toLrc)(lyrics === null || lyrics === void 0 ? void 0 : lyrics.LYRICS_SYNC_JSON, {
79
+ title: track.SNG_TITLE,
80
+ artist: mainArtists.join(', '),
81
+ album: track.ALB_TITLE,
82
+ writers: lyrics === null || lyrics === void 0 ? void 0 : lyrics.LYRICS_WRITERS,
83
+ length: Number(track.DURATION) || undefined,
84
+ }) || undefined,
85
+ lyricsWriters: (lyrics === null || lyrics === void 0 ? void 0 : lyrics.LYRICS_WRITERS) || undefined,
86
+ lyricsCopyright: (lyrics === null || lyrics === void 0 ? void 0 : lyrics.LYRICS_COPYRIGHTS) || undefined,
87
+ ids: input.deezerIds
88
+ ? {
89
+ deezerTrack: track.SNG_ID,
90
+ deezerAlbum: track.ALB_ID,
91
+ deezerArtist: track.ART_ID,
92
+ slug: track.URL_REWRITING || undefined,
93
+ labelId: albMeta.labelId,
94
+ providerId: track.PROVIDER_ID || undefined,
95
+ }
96
+ : {},
97
+ rank: input.includeRank && track.RANK ? Number(track.RANK) || undefined : undefined,
98
+ cover: input.cover,
99
+ coverSize,
100
+ artistImage: input.artistImage,
101
+ };
102
+ };
103
+ exports.buildTagModel = buildTagModel;
@@ -0,0 +1,39 @@
1
+ import type { albumType, albumTypePublicApi } from '../types';
2
+ export interface RichAlbum {
3
+ id: string;
4
+ title: string;
5
+ albumArtist: string;
6
+ /** © line — from gw `COPYRIGHT`, falls back to `PRODUCER_LINE` */
7
+ copyright?: string;
8
+ /** ℗ line — only set when it differs from `copyright` */
9
+ producerLine?: string;
10
+ /** best available release date, YYYY-MM-DD */
11
+ releaseDate?: string;
12
+ /** ORIGINAL_RELEASE_DATE — the true first release; only when it differs from releaseDate */
13
+ originalDate?: string;
14
+ upc?: string;
15
+ label?: string;
16
+ labelId?: string;
17
+ genres: string[];
18
+ /** album | single | ep | compile | live … */
19
+ recordType?: string;
20
+ isCompilation: boolean;
21
+ isLive: boolean;
22
+ trackTotal?: number;
23
+ discTotal?: number;
24
+ fans?: number;
25
+ /** the two raw payloads, for callers that want more */
26
+ gw?: albumType;
27
+ publicApi?: albumTypePublicApi;
28
+ }
29
+ /**
30
+ * One merged album view from the two endpoints that actually carry the good
31
+ * fields: `album.getData` (gw — ©/℗, original release date, disc total,
32
+ * subtypes) and the public `/album/` (label, genres, record type). Both calls
33
+ * are memoised and in-flight-coalesced by the api layer, so calling this once
34
+ * per track of an album costs a single network round-trip for the whole album.
35
+ *
36
+ * The public endpoint 404s for some small-label albums; that path is optional
37
+ * and the gw data still comes through.
38
+ */
39
+ export declare const getRichAlbum: (albId: string) => Promise<RichAlbum>;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRichAlbum = void 0;
4
+ const api_1 = require("../api");
5
+ const pickDate = (s) => s && /^\d{4}-\d{2}-\d{2}$/.test(s) && !/^0000/.test(s) ? s : undefined;
6
+ /**
7
+ * One merged album view from the two endpoints that actually carry the good
8
+ * fields: `album.getData` (gw — ©/℗, original release date, disc total,
9
+ * subtypes) and the public `/album/` (label, genres, record type). Both calls
10
+ * are memoised and in-flight-coalesced by the api layer, so calling this once
11
+ * per track of an album costs a single network round-trip for the whole album.
12
+ *
13
+ * The public endpoint 404s for some small-label albums; that path is optional
14
+ * and the gw data still comes through.
15
+ */
16
+ const getRichAlbum = async (albId) => {
17
+ var _a, _b, _c, _d, _e;
18
+ const [gwResult, publicResult] = await Promise.allSettled([(0, api_1.getAlbumInfo)(albId), (0, api_1.getAlbumInfoPublicApi)(albId)]);
19
+ const gw = gwResult.status === 'fulfilled' ? gwResult.value : undefined;
20
+ const pub = publicResult.status === 'fulfilled' ? publicResult.value : undefined;
21
+ const copyright = ((gw === null || gw === void 0 ? void 0 : gw.COPYRIGHT) || (gw === null || gw === void 0 ? void 0 : gw.PRODUCER_LINE) || '').trim() || undefined;
22
+ const producerLineRaw = ((gw === null || gw === void 0 ? void 0 : gw.PRODUCER_LINE) || '').trim() || undefined;
23
+ const producerLine = producerLineRaw && producerLineRaw !== copyright ? producerLineRaw : undefined;
24
+ const releaseDate = pickDate(gw === null || gw === void 0 ? void 0 : gw.DIGITAL_RELEASE_DATE) ||
25
+ pickDate(pub === null || pub === void 0 ? void 0 : pub.release_date) ||
26
+ pickDate(gw === null || gw === void 0 ? void 0 : gw.PHYSICAL_RELEASE_DATE) ||
27
+ pickDate(gw === null || gw === void 0 ? void 0 : gw.ORIGINAL_RELEASE_DATE);
28
+ const originalRaw = pickDate(gw === null || gw === void 0 ? void 0 : gw.ORIGINAL_RELEASE_DATE);
29
+ const originalDate = originalRaw && originalRaw !== releaseDate ? originalRaw : undefined;
30
+ const recordType = (pub === null || pub === void 0 ? void 0 : pub.record_type) || undefined;
31
+ return {
32
+ id: albId,
33
+ title: (gw === null || gw === void 0 ? void 0 : gw.ALB_TITLE) || (pub === null || pub === void 0 ? void 0 : pub.title) || '',
34
+ albumArtist: (gw === null || gw === void 0 ? void 0 : gw.ART_NAME) || ((_a = pub === null || pub === void 0 ? void 0 : pub.artist) === null || _a === void 0 ? void 0 : _a.name) || '',
35
+ copyright,
36
+ producerLine,
37
+ releaseDate,
38
+ originalDate,
39
+ upc: (gw === null || gw === void 0 ? void 0 : gw.UPC) || (pub === null || pub === void 0 ? void 0 : pub.upc) || undefined,
40
+ label: (pub === null || pub === void 0 ? void 0 : pub.label) || undefined,
41
+ labelId: (gw === null || gw === void 0 ? void 0 : gw.LABEL_ID) || undefined,
42
+ genres: (((_b = pub === null || pub === void 0 ? void 0 : pub.genres) === null || _b === void 0 ? void 0 : _b.data) || []).map((g) => g.name).filter(Boolean),
43
+ recordType,
44
+ isCompilation: Boolean((_c = gw === null || gw === void 0 ? void 0 : gw.SUBTYPES) === null || _c === void 0 ? void 0 : _c.isCompilation) || recordType === 'compile',
45
+ isLive: Boolean((_d = gw === null || gw === void 0 ? void 0 : gw.SUBTYPES) === null || _d === void 0 ? void 0 : _d.isLive),
46
+ trackTotal: (gw === null || gw === void 0 ? void 0 : gw.NUMBER_TRACK) ? Number(gw.NUMBER_TRACK) : pub === null || pub === void 0 ? void 0 : pub.nb_tracks,
47
+ discTotal: (gw === null || gw === void 0 ? void 0 : gw.NUMBER_DISK) ? Number(gw.NUMBER_DISK) : undefined,
48
+ fans: (_e = pub === null || pub === void 0 ? void 0 : pub.fans) !== null && _e !== void 0 ? _e : gw === null || gw === void 0 ? void 0 : gw.NB_FAN,
49
+ gw,
50
+ publicApi: pub,
51
+ };
52
+ };
53
+ exports.getRichAlbum = getRichAlbum;
@@ -21,8 +21,8 @@ export interface albumTypeMinimal {
21
21
  }
22
22
  export interface albumType {
23
23
  ALB_CONTRIBUTORS: {
24
- main_artist: string[];
25
- };
24
+ [role: string]: string[];
25
+ } | [];
26
26
  ALB_ID: string;
27
27
  ALB_PICTURE: string;
28
28
  EXPLICIT_ALBUM_CONTENT: {
@@ -34,7 +34,9 @@ export interface albumType {
34
34
  ART_ID: string;
35
35
  ART_NAME: string;
36
36
  ARTIST_IS_DUMMY: boolean;
37
+ COPYRIGHT?: string;
37
38
  DIGITAL_RELEASE_DATE: string;
39
+ ORIGINAL_RELEASE_DATE?: string;
38
40
  EXPLICIT_LYRICS?: string;
39
41
  NB_FAN: number;
40
42
  NUMBER_DISK: string;
@@ -45,6 +47,12 @@ export interface albumType {
45
47
  RANK: string;
46
48
  RANK_ART: string;
47
49
  STATUS: string;
50
+ SUBTYPES?: {
51
+ isStudio: boolean;
52
+ isLive: boolean;
53
+ isCompilation: boolean;
54
+ isKaraoke: boolean;
55
+ };
48
56
  TYPE: string;
49
57
  UPC: string;
50
58
  __TYPE__: 'album';
@@ -3,7 +3,7 @@ interface mediaType {
3
3
  TYPE: 'preview';
4
4
  HREF: string;
5
5
  }
6
- interface lyricsSync {
6
+ export interface lyricsSync {
7
7
  lrc_timestamp: string;
8
8
  milliseconds: string;
9
9
  duration: string;
@@ -16,6 +16,17 @@ export interface lyricsType {
16
16
  LYRICS_COPYRIGHTS?: string;
17
17
  LYRICS_WRITERS?: string;
18
18
  }
19
+ /**
20
+ * `SNG_CONTRIBUTORS` as Deezer actually ships it: a bag of role -> names.
21
+ * The role keys are inconsistent across the catalogue — `main_artist` vs
22
+ * `mainartist` vs `artist`, `musicpublisher` vs `music publisher` — and the
23
+ * whole thing is sometimes an empty array. Normalise with
24
+ * `normalizeContributors()` from `metadata-writer/contributors` rather than
25
+ * reading keys directly.
26
+ */
27
+ export type sngContributors = {
28
+ [role: string]: string[];
29
+ } | [];
19
30
  interface songType {
20
31
  ALB_ID: string;
21
32
  ALB_TITLE: string;
@@ -40,17 +51,7 @@ interface songType {
40
51
  SMARTRADIO: string;
41
52
  SNG_ID: string;
42
53
  SNG_TITLE: string;
43
- SNG_CONTRIBUTORS?: {
44
- main_artist: string[];
45
- author?: string[];
46
- composer?: string[];
47
- musicpublisher?: string[];
48
- producer?: string[];
49
- publisher: string[];
50
- engineer?: string[];
51
- writer?: string[];
52
- mixer?: string[];
53
- } | [];
54
+ SNG_CONTRIBUTORS?: sngContributors;
54
55
  STATUS: number;
55
56
  S_MOD: number;
56
57
  S_PREMIUM: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gerdur-core",
3
- "version": "1.0.4",
3
+ "version": "2.0.0",
4
4
  "description": "Core module for gerdur.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",