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 +51 -0
- package/README.md +17 -9
- package/dist/lib/metaflac-js.d.ts +1 -1
- package/dist/lib/metaflac-js.js +3 -1
- package/dist/metadata-writer/abumCover.d.ts +9 -5
- package/dist/metadata-writer/abumCover.js +28 -14
- package/dist/metadata-writer/contributors.d.ts +31 -0
- package/dist/metadata-writer/contributors.js +86 -0
- package/dist/metadata-writer/flacmetata.d.ts +11 -2
- package/dist/metadata-writer/flacmetata.js +89 -64
- package/dist/metadata-writer/id3.d.ts +8 -2
- package/dist/metadata-writer/id3.js +110 -103
- package/dist/metadata-writer/index.d.ts +60 -6
- package/dist/metadata-writer/index.js +96 -25
- package/dist/metadata-writer/lrc.d.ts +16 -0
- package/dist/metadata-writer/lrc.js +43 -0
- package/dist/metadata-writer/model.d.ts +75 -0
- package/dist/metadata-writer/model.js +103 -0
- package/dist/metadata-writer/rich-album.d.ts +39 -0
- package/dist/metadata-writer/rich-album.js +53 -0
- package/dist/types/album.d.ts +10 -2
- package/dist/types/tracks.d.ts +13 -12
- package/package.json +1 -1
|
@@ -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;
|
package/dist/types/album.d.ts
CHANGED
|
@@ -21,8 +21,8 @@ export interface albumTypeMinimal {
|
|
|
21
21
|
}
|
|
22
22
|
export interface albumType {
|
|
23
23
|
ALB_CONTRIBUTORS: {
|
|
24
|
-
|
|
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';
|
package/dist/types/tracks.d.ts
CHANGED
|
@@ -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;
|