music-metadata 7.11.6 → 7.11.7

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.
Files changed (152) hide show
  1. package/README.md +432 -432
  2. package/lib/ParserFactory.d.ts +49 -49
  3. package/lib/ParserFactory.js +251 -251
  4. package/lib/aiff/AiffParser.d.ts +15 -15
  5. package/lib/aiff/AiffParser.js +85 -85
  6. package/lib/aiff/AiffToken.d.ts +22 -22
  7. package/lib/aiff/AiffToken.js +43 -43
  8. package/lib/apev2/APEv2Parser.d.ts +30 -30
  9. package/lib/apev2/APEv2Parser.js +162 -162
  10. package/lib/apev2/APEv2TagMapper.d.ts +4 -4
  11. package/lib/apev2/APEv2TagMapper.js +86 -86
  12. package/lib/apev2/APEv2Token.d.ts +100 -100
  13. package/lib/apev2/APEv2Token.js +126 -126
  14. package/lib/asf/AsfObject.d.ts +319 -319
  15. package/lib/asf/AsfObject.js +384 -384
  16. package/lib/asf/AsfParser.d.ts +17 -17
  17. package/lib/asf/AsfParser.js +135 -135
  18. package/lib/asf/AsfTagMapper.d.ts +7 -7
  19. package/lib/asf/AsfTagMapper.js +95 -95
  20. package/lib/asf/AsfUtil.d.ts +13 -13
  21. package/lib/asf/AsfUtil.js +40 -40
  22. package/lib/asf/GUID.d.ts +86 -86
  23. package/lib/asf/GUID.js +123 -123
  24. package/lib/common/BasicParser.d.ts +17 -17
  25. package/lib/common/BasicParser.js +18 -18
  26. package/lib/common/CaseInsensitiveTagMap.d.ts +10 -10
  27. package/lib/common/CaseInsensitiveTagMap.js +21 -21
  28. package/lib/common/CombinedTagMapper.d.ts +19 -19
  29. package/lib/common/CombinedTagMapper.js +51 -51
  30. package/lib/common/FourCC.d.ts +6 -6
  31. package/lib/common/FourCC.js +28 -28
  32. package/lib/common/GenericTagMapper.d.ts +51 -51
  33. package/lib/common/GenericTagMapper.js +55 -55
  34. package/lib/common/GenericTagTypes.d.ts +33 -33
  35. package/lib/common/GenericTagTypes.js +131 -131
  36. package/lib/common/MetadataCollector.d.ts +76 -76
  37. package/lib/common/MetadataCollector.js +275 -275
  38. package/lib/common/RandomFileReader.d.ts +20 -20
  39. package/lib/common/RandomFileReader.js +37 -37
  40. package/lib/common/RandomUint8ArrayReader.d.ts +18 -18
  41. package/lib/common/RandomUint8ArrayReader.js +25 -25
  42. package/lib/common/Util.d.ts +58 -57
  43. package/lib/common/Util.js +162 -169
  44. package/lib/core.d.ts +48 -48
  45. package/lib/core.js +90 -90
  46. package/lib/dsdiff/DsdiffParser.d.ts +14 -14
  47. package/lib/dsdiff/DsdiffParser.js +143 -143
  48. package/lib/dsdiff/DsdiffToken.d.ts +9 -9
  49. package/lib/dsdiff/DsdiffToken.js +21 -21
  50. package/lib/dsf/DsfChunk.d.ts +86 -86
  51. package/lib/dsf/DsfChunk.js +54 -54
  52. package/lib/dsf/DsfParser.d.ts +9 -9
  53. package/lib/dsf/DsfParser.js +56 -56
  54. package/lib/flac/FlacParser.d.ts +28 -28
  55. package/lib/flac/FlacParser.js +175 -175
  56. package/lib/id3v1/ID3v1Parser.d.ts +13 -13
  57. package/lib/id3v1/ID3v1Parser.js +134 -134
  58. package/lib/id3v1/ID3v1TagMap.d.ts +4 -4
  59. package/lib/id3v1/ID3v1TagMap.js +22 -22
  60. package/lib/id3v2/AbstractID3Parser.d.ts +17 -17
  61. package/lib/id3v2/AbstractID3Parser.js +60 -60
  62. package/lib/id3v2/FrameParser.d.ts +32 -32
  63. package/lib/id3v2/FrameParser.js +329 -323
  64. package/lib/id3v2/ID3v22TagMapper.d.ts +9 -9
  65. package/lib/id3v2/ID3v22TagMapper.js +55 -55
  66. package/lib/id3v2/ID3v24TagMapper.d.ts +14 -14
  67. package/lib/id3v2/ID3v24TagMapper.js +193 -193
  68. package/lib/id3v2/ID3v2Parser.d.ts +29 -29
  69. package/lib/id3v2/ID3v2Parser.js +194 -194
  70. package/lib/id3v2/ID3v2Token.d.ts +73 -73
  71. package/lib/id3v2/ID3v2Token.js +106 -106
  72. package/lib/iff/index.d.ts +33 -33
  73. package/lib/iff/index.js +19 -19
  74. package/lib/index.d.ts +45 -45
  75. package/lib/index.js +74 -74
  76. package/lib/lyrics3/Lyrics3.d.ts +3 -3
  77. package/lib/lyrics3/Lyrics3.js +17 -17
  78. package/lib/matroska/MatroskaDtd.d.ts +8 -8
  79. package/lib/matroska/MatroskaDtd.js +279 -279
  80. package/lib/matroska/MatroskaParser.d.ts +37 -37
  81. package/lib/matroska/MatroskaParser.js +235 -235
  82. package/lib/matroska/MatroskaTagMapper.d.ts +4 -4
  83. package/lib/matroska/MatroskaTagMapper.js +35 -35
  84. package/lib/matroska/types.d.ts +175 -175
  85. package/lib/matroska/types.js +32 -32
  86. package/lib/mp4/Atom.d.ts +16 -16
  87. package/lib/mp4/Atom.js +70 -70
  88. package/lib/mp4/AtomToken.d.ts +395 -395
  89. package/lib/mp4/AtomToken.js +406 -406
  90. package/lib/mp4/MP4Parser.d.ts +30 -30
  91. package/lib/mp4/MP4Parser.js +511 -511
  92. package/lib/mp4/MP4TagMapper.d.ts +5 -5
  93. package/lib/mp4/MP4TagMapper.js +115 -115
  94. package/lib/mpeg/ExtendedLameHeader.d.ts +27 -27
  95. package/lib/mpeg/ExtendedLameHeader.js +31 -31
  96. package/lib/mpeg/MpegParser.d.ts +49 -49
  97. package/lib/mpeg/MpegParser.js +529 -529
  98. package/lib/mpeg/ReplayGainDataFormat.d.ts +55 -55
  99. package/lib/mpeg/ReplayGainDataFormat.js +69 -69
  100. package/lib/mpeg/XingTag.d.ts +45 -45
  101. package/lib/mpeg/XingTag.js +69 -69
  102. package/lib/musepack/index.d.ts +5 -5
  103. package/lib/musepack/index.js +32 -32
  104. package/lib/musepack/sv7/BitReader.d.ts +13 -13
  105. package/lib/musepack/sv7/BitReader.js +54 -54
  106. package/lib/musepack/sv7/MpcSv7Parser.d.ts +8 -8
  107. package/lib/musepack/sv7/MpcSv7Parser.js +46 -46
  108. package/lib/musepack/sv7/StreamVersion7.d.ts +28 -28
  109. package/lib/musepack/sv7/StreamVersion7.js +41 -41
  110. package/lib/musepack/sv8/MpcSv8Parser.d.ts +6 -6
  111. package/lib/musepack/sv8/MpcSv8Parser.js +55 -55
  112. package/lib/musepack/sv8/StreamVersion8.d.ts +40 -40
  113. package/lib/musepack/sv8/StreamVersion8.js +80 -80
  114. package/lib/ogg/Ogg.d.ts +72 -72
  115. package/lib/ogg/Ogg.js +2 -2
  116. package/lib/ogg/OggParser.d.ts +23 -23
  117. package/lib/ogg/OggParser.js +126 -126
  118. package/lib/ogg/opus/Opus.d.ts +48 -48
  119. package/lib/ogg/opus/Opus.js +28 -28
  120. package/lib/ogg/opus/OpusParser.d.ts +25 -25
  121. package/lib/ogg/opus/OpusParser.js +56 -56
  122. package/lib/ogg/speex/Speex.d.ts +36 -36
  123. package/lib/ogg/speex/Speex.js +31 -31
  124. package/lib/ogg/speex/SpeexParser.d.ts +22 -22
  125. package/lib/ogg/speex/SpeexParser.js +35 -35
  126. package/lib/ogg/theora/Theora.d.ts +20 -20
  127. package/lib/ogg/theora/Theora.js +23 -23
  128. package/lib/ogg/theora/TheoraParser.d.ts +28 -28
  129. package/lib/ogg/theora/TheoraParser.js +44 -44
  130. package/lib/ogg/vorbis/Vorbis.d.ts +79 -79
  131. package/lib/ogg/vorbis/Vorbis.js +78 -78
  132. package/lib/ogg/vorbis/VorbisDecoder.d.ts +12 -12
  133. package/lib/ogg/vorbis/VorbisDecoder.js +32 -32
  134. package/lib/ogg/vorbis/VorbisParser.d.ts +36 -36
  135. package/lib/ogg/vorbis/VorbisParser.js +128 -128
  136. package/lib/ogg/vorbis/VorbisTagMapper.d.ts +7 -7
  137. package/lib/ogg/vorbis/VorbisTagMapper.js +132 -132
  138. package/lib/riff/RiffChunk.d.ts +16 -16
  139. package/lib/riff/RiffChunk.js +32 -32
  140. package/lib/riff/RiffInfoTagMap.d.ts +10 -10
  141. package/lib/riff/RiffInfoTagMap.js +37 -37
  142. package/lib/type.d.ts +599 -599
  143. package/lib/type.js +13 -13
  144. package/lib/wav/WaveChunk.d.ts +64 -64
  145. package/lib/wav/WaveChunk.js +65 -65
  146. package/lib/wav/WaveParser.d.ts +24 -24
  147. package/lib/wav/WaveParser.js +144 -144
  148. package/lib/wavpack/WavPackParser.d.ts +14 -14
  149. package/lib/wavpack/WavPackParser.js +105 -105
  150. package/lib/wavpack/WavPackToken.d.ts +64 -64
  151. package/lib/wavpack/WavPackToken.js +76 -76
  152. package/package.json +142 -142
@@ -1,323 +1,329 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FrameParser = exports.parseGenre = void 0;
4
- const initDebug = require("debug");
5
- const Token = require("token-types");
6
- const util = require("../common/Util");
7
- const ID3v2Token_1 = require("./ID3v2Token");
8
- const ID3v1Parser_1 = require("../id3v1/ID3v1Parser");
9
- const debug = initDebug('music-metadata:id3v2:frame-parser');
10
- const defaultEnc = 'latin1'; // latin1 == iso-8859-1;
11
- function parseGenre(origVal) {
12
- // match everything inside parentheses
13
- const genres = [];
14
- let code;
15
- let word = '';
16
- for (const c of origVal) {
17
- if (typeof code === 'string') {
18
- if (c === '(' && code === '') {
19
- word += '(';
20
- code = undefined;
21
- }
22
- else if (c === ')') {
23
- if (word !== '') {
24
- genres.push(word);
25
- word = '';
26
- }
27
- const genre = parseGenreCode(code);
28
- if (genre) {
29
- genres.push(genre);
30
- }
31
- code = undefined;
32
- }
33
- else
34
- code += c;
35
- }
36
- else if (c === '(') {
37
- code = '';
38
- }
39
- else {
40
- word += c;
41
- }
42
- }
43
- if (word) {
44
- if (genres.length === 0 && word.match(/^\d*$/)) {
45
- word = ID3v1Parser_1.Genres[word];
46
- }
47
- genres.push(word);
48
- }
49
- return genres;
50
- }
51
- exports.parseGenre = parseGenre;
52
- function parseGenreCode(code) {
53
- if (code === 'RX')
54
- return 'Remix';
55
- if (code === 'CR')
56
- return 'Cover';
57
- if (code.match(/^\d*$/)) {
58
- return ID3v1Parser_1.Genres[code];
59
- }
60
- }
61
- class FrameParser {
62
- /**
63
- * Create id3v2 frame parser
64
- * @param major - Major version, e.g. (4) for id3v2.4
65
- * @param warningCollector - Used to collect decode issue
66
- */
67
- constructor(major, warningCollector) {
68
- this.major = major;
69
- this.warningCollector = warningCollector;
70
- }
71
- readData(b, type, includeCovers) {
72
- if (b.length === 0) {
73
- this.warningCollector.addWarning(`id3v2.${this.major} header has empty tag type=${type}`);
74
- return;
75
- }
76
- const { encoding, bom } = ID3v2Token_1.TextEncodingToken.get(b, 0);
77
- const length = b.length;
78
- let offset = 0;
79
- let output = []; // ToDo
80
- const nullTerminatorLength = FrameParser.getNullTerminatorLength(encoding);
81
- let fzero;
82
- const out = {};
83
- debug(`Parsing tag type=${type}, encoding=${encoding}, bom=${bom}`);
84
- switch (type !== 'TXXX' && type[0] === 'T' ? 'T*' : type) {
85
- case 'T*': // 4.2.1. Text information frames - details
86
- case 'IPLS': // v2.3: Involved people list
87
- case 'MVIN':
88
- case 'MVNM':
89
- case 'PCS':
90
- case 'PCST':
91
- const text = util.decodeString(b.slice(1), encoding).replace(/\x00+$/, '');
92
- switch (type) {
93
- case 'TMCL': // Musician credits list
94
- case 'TIPL': // Involved people list
95
- case 'IPLS': // Involved people list
96
- output = this.splitValue(type, text);
97
- output = FrameParser.functionList(output);
98
- break;
99
- case 'TRK':
100
- case 'TRCK':
101
- case 'TPOS':
102
- output = text;
103
- break;
104
- case 'TCOM':
105
- case 'TEXT':
106
- case 'TOLY':
107
- case 'TOPE':
108
- case 'TPE1':
109
- case 'TSRC':
110
- // id3v2.3 defines that TCOM, TEXT, TOLY, TOPE & TPE1 values are separated by /
111
- output = this.splitValue(type, text);
112
- break;
113
- case 'TCO':
114
- case 'TCON':
115
- output = this.splitValue(type, text).map(v => parseGenre(v)).reduce((acc, val) => acc.concat(val), []);
116
- break;
117
- case 'PCS':
118
- case 'PCST':
119
- // TODO: Why `default` not results `1` but `''`?
120
- output = this.major >= 4 ? this.splitValue(type, text) : [text];
121
- output = (Array.isArray(output) && output[0] === '') ? 1 : 0;
122
- break;
123
- default:
124
- output = this.major >= 4 ? this.splitValue(type, text) : [text];
125
- }
126
- break;
127
- case 'TXXX':
128
- output = FrameParser.readIdentifierAndData(b, offset + 1, length, encoding);
129
- output = {
130
- description: output.id,
131
- text: this.splitValue(type, util.decodeString(output.data, encoding).replace(/\x00+$/, ''))
132
- };
133
- break;
134
- case 'PIC':
135
- case 'APIC':
136
- if (includeCovers) {
137
- const pic = {};
138
- offset += 1;
139
- switch (this.major) {
140
- case 2:
141
- pic.format = util.decodeString(b.slice(offset, offset + 3), 'latin1'); // 'latin1'; // latin1 == iso-8859-1;
142
- offset += 3;
143
- break;
144
- case 3:
145
- case 4:
146
- fzero = util.findZero(b, offset, length, defaultEnc);
147
- pic.format = util.decodeString(b.slice(offset, fzero), defaultEnc);
148
- offset = fzero + 1;
149
- break;
150
- default:
151
- throw new Error('Warning: unexpected major versionIndex: ' + this.major);
152
- }
153
- pic.format = FrameParser.fixPictureMimeType(pic.format);
154
- pic.type = ID3v2Token_1.AttachedPictureType[b[offset]];
155
- offset += 1;
156
- fzero = util.findZero(b, offset, length, encoding);
157
- pic.description = util.decodeString(b.slice(offset, fzero), encoding);
158
- offset = fzero + nullTerminatorLength;
159
- pic.data = Buffer.from(b.slice(offset, length));
160
- output = pic;
161
- }
162
- break;
163
- case 'CNT':
164
- case 'PCNT':
165
- output = Token.UINT32_BE.get(b, 0);
166
- break;
167
- case 'SYLT':
168
- // skip text encoding (1 byte),
169
- // language (3 bytes),
170
- // time stamp format (1 byte),
171
- // content tagTypes (1 byte),
172
- // content descriptor (1 byte)
173
- offset += 7;
174
- output = [];
175
- while (offset < length) {
176
- const txt = b.slice(offset, offset = util.findZero(b, offset, length, encoding));
177
- offset += 5; // push offset forward one + 4 byte timestamp
178
- output.push(util.decodeString(txt, encoding));
179
- }
180
- break;
181
- case 'ULT':
182
- case 'USLT':
183
- case 'COM':
184
- case 'COMM':
185
- offset += 1;
186
- out.language = util.decodeString(b.slice(offset, offset + 3), defaultEnc);
187
- offset += 3;
188
- fzero = util.findZero(b, offset, length, encoding);
189
- out.description = util.decodeString(b.slice(offset, fzero), encoding);
190
- offset = fzero + nullTerminatorLength;
191
- out.text = util.decodeString(b.slice(offset, length), encoding).replace(/\x00+$/, '');
192
- output = [out];
193
- break;
194
- case 'UFID':
195
- output = FrameParser.readIdentifierAndData(b, offset, length, defaultEnc);
196
- output = { owner_identifier: output.id, identifier: output.data };
197
- break;
198
- case 'PRIV': // private frame
199
- output = FrameParser.readIdentifierAndData(b, offset, length, defaultEnc);
200
- output = { owner_identifier: output.id, data: output.data };
201
- break;
202
- case 'POPM': // Popularimeter
203
- fzero = util.findZero(b, offset, length, defaultEnc);
204
- const email = util.decodeString(b.slice(offset, fzero), defaultEnc);
205
- offset = fzero + 1;
206
- const dataLen = length - offset;
207
- output = {
208
- email,
209
- rating: b.readUInt8(offset),
210
- counter: dataLen >= 5 ? b.readUInt32BE(offset + 1) : undefined
211
- };
212
- break;
213
- case 'GEOB': { // General encapsulated object
214
- fzero = util.findZero(b, offset + 1, length, encoding);
215
- const mimeType = util.decodeString(b.slice(offset + 1, fzero), defaultEnc);
216
- offset = fzero + 1;
217
- fzero = util.findZero(b, offset, length - offset, encoding);
218
- const filename = util.decodeString(b.slice(offset, fzero), defaultEnc);
219
- offset = fzero + 1;
220
- fzero = util.findZero(b, offset, length - offset, encoding);
221
- const description = util.decodeString(b.slice(offset, fzero), defaultEnc);
222
- output = {
223
- type: mimeType,
224
- filename,
225
- description,
226
- data: b.slice(offset + 1, length)
227
- };
228
- break;
229
- }
230
- // W-Frames:
231
- case 'WCOM':
232
- case 'WCOP':
233
- case 'WOAF':
234
- case 'WOAR':
235
- case 'WOAS':
236
- case 'WORS':
237
- case 'WPAY':
238
- case 'WPUB':
239
- // Decode URL
240
- output = util.decodeString(b.slice(offset, fzero), defaultEnc);
241
- break;
242
- case 'WXXX': {
243
- // Decode URL
244
- fzero = util.findZero(b, offset + 1, length, encoding);
245
- const description = util.decodeString(b.slice(offset + 1, fzero), encoding);
246
- offset = fzero + (encoding === 'utf16le' ? 2 : 1);
247
- output = { description, url: util.decodeString(b.slice(offset, length), defaultEnc) };
248
- break;
249
- }
250
- case 'WFD':
251
- case 'WFED':
252
- output = util.decodeString(b.slice(offset + 1, util.findZero(b, offset + 1, length, encoding)), encoding);
253
- break;
254
- case 'MCDI': {
255
- // Music CD identifier
256
- output = b.slice(0, length);
257
- break;
258
- }
259
- default:
260
- debug('Warning: unsupported id3v2-tag-type: ' + type);
261
- break;
262
- }
263
- return output;
264
- }
265
- static fixPictureMimeType(pictureType) {
266
- pictureType = pictureType.toLocaleLowerCase();
267
- switch (pictureType) {
268
- case 'jpg':
269
- return 'image/jpeg';
270
- case 'png':
271
- return 'image/png';
272
- }
273
- return pictureType;
274
- }
275
- /**
276
- * Converts TMCL (Musician credits list) or TIPL (Involved people list)
277
- * @param entries
278
- */
279
- static functionList(entries) {
280
- const res = {};
281
- for (let i = 0; i + 1 < entries.length; i += 2) {
282
- const names = entries[i + 1].split(',');
283
- res[entries[i]] = res.hasOwnProperty(entries[i]) ? res[entries[i]].concat(names) : names;
284
- }
285
- return res;
286
- }
287
- /**
288
- * id3v2.4 defines that multiple T* values are separated by 0x00
289
- * id3v2.3 defines that TCOM, TEXT, TOLY, TOPE & TPE1 values are separated by /
290
- * @param tag - Tag name
291
- * @param text - Concatenated tag value
292
- * @returns Split tag value
293
- */
294
- splitValue(tag, text) {
295
- let values;
296
- if (this.major < 4) {
297
- values = text.split(/\x00/g);
298
- if (values.length > 1) {
299
- this.warningCollector.addWarning(`ID3v2.${this.major} ${tag} uses non standard null-separator.`);
300
- }
301
- else {
302
- values = text.split(/\//g);
303
- }
304
- }
305
- else {
306
- values = text.split(/\x00/g);
307
- }
308
- return FrameParser.trimArray(values);
309
- }
310
- static trimArray(values) {
311
- return values.map(value => value.replace(/\x00+$/, '').trim());
312
- }
313
- static readIdentifierAndData(b, offset, length, encoding) {
314
- const fzero = util.findZero(b, offset, length, encoding);
315
- const id = util.decodeString(b.slice(offset, fzero), encoding);
316
- offset = fzero + FrameParser.getNullTerminatorLength(encoding);
317
- return { id, data: b.slice(offset, length) };
318
- }
319
- static getNullTerminatorLength(enc) {
320
- return enc === 'utf16le' ? 2 : 1;
321
- }
322
- }
323
- exports.FrameParser = FrameParser;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FrameParser = exports.parseGenre = void 0;
4
+ const initDebug = require("debug");
5
+ const Token = require("token-types");
6
+ const util = require("../common/Util");
7
+ const ID3v2Token_1 = require("./ID3v2Token");
8
+ const ID3v1Parser_1 = require("../id3v1/ID3v1Parser");
9
+ const debug = initDebug('music-metadata:id3v2:frame-parser');
10
+ const defaultEnc = 'latin1'; // latin1 == iso-8859-1;
11
+ function parseGenre(origVal) {
12
+ // match everything inside parentheses
13
+ const genres = [];
14
+ let code;
15
+ let word = '';
16
+ for (const c of origVal) {
17
+ if (typeof code === 'string') {
18
+ if (c === '(' && code === '') {
19
+ word += '(';
20
+ code = undefined;
21
+ }
22
+ else if (c === ')') {
23
+ if (word !== '') {
24
+ genres.push(word);
25
+ word = '';
26
+ }
27
+ const genre = parseGenreCode(code);
28
+ if (genre) {
29
+ genres.push(genre);
30
+ }
31
+ code = undefined;
32
+ }
33
+ else
34
+ code += c;
35
+ }
36
+ else if (c === '(') {
37
+ code = '';
38
+ }
39
+ else {
40
+ word += c;
41
+ }
42
+ }
43
+ if (word) {
44
+ if (genres.length === 0 && word.match(/^\d*$/)) {
45
+ word = ID3v1Parser_1.Genres[word];
46
+ }
47
+ genres.push(word);
48
+ }
49
+ return genres;
50
+ }
51
+ exports.parseGenre = parseGenre;
52
+ function parseGenreCode(code) {
53
+ if (code === 'RX')
54
+ return 'Remix';
55
+ if (code === 'CR')
56
+ return 'Cover';
57
+ if (code.match(/^\d*$/)) {
58
+ return ID3v1Parser_1.Genres[code];
59
+ }
60
+ }
61
+ class FrameParser {
62
+ /**
63
+ * Create id3v2 frame parser
64
+ * @param major - Major version, e.g. (4) for id3v2.4
65
+ * @param warningCollector - Used to collect decode issue
66
+ */
67
+ constructor(major, warningCollector) {
68
+ this.major = major;
69
+ this.warningCollector = warningCollector;
70
+ }
71
+ readData(b, type, includeCovers) {
72
+ if (b.length === 0) {
73
+ this.warningCollector.addWarning(`id3v2.${this.major} header has empty tag type=${type}`);
74
+ return;
75
+ }
76
+ const { encoding, bom } = ID3v2Token_1.TextEncodingToken.get(b, 0);
77
+ const length = b.length;
78
+ let offset = 0;
79
+ let output = []; // ToDo
80
+ const nullTerminatorLength = FrameParser.getNullTerminatorLength(encoding);
81
+ let fzero;
82
+ const out = {};
83
+ debug(`Parsing tag type=${type}, encoding=${encoding}, bom=${bom}`);
84
+ switch (type !== 'TXXX' && type[0] === 'T' ? 'T*' : type) {
85
+ case 'T*': // 4.2.1. Text information frames - details
86
+ case 'IPLS': // v2.3: Involved people list
87
+ case 'MVIN':
88
+ case 'MVNM':
89
+ case 'PCS':
90
+ case 'PCST':
91
+ let text;
92
+ try {
93
+ text = util.decodeString(b.subarray(1), encoding).replace(/\x00+$/, '');
94
+ }
95
+ catch (error) {
96
+ this.warningCollector.addWarning(`id3v2.${this.major} type=${type} header has invalid string value: ${error.message}`);
97
+ }
98
+ switch (type) {
99
+ case 'TMCL': // Musician credits list
100
+ case 'TIPL': // Involved people list
101
+ case 'IPLS': // Involved people list
102
+ output = this.splitValue(type, text);
103
+ output = FrameParser.functionList(output);
104
+ break;
105
+ case 'TRK':
106
+ case 'TRCK':
107
+ case 'TPOS':
108
+ output = text;
109
+ break;
110
+ case 'TCOM':
111
+ case 'TEXT':
112
+ case 'TOLY':
113
+ case 'TOPE':
114
+ case 'TPE1':
115
+ case 'TSRC':
116
+ // id3v2.3 defines that TCOM, TEXT, TOLY, TOPE & TPE1 values are separated by /
117
+ output = this.splitValue(type, text);
118
+ break;
119
+ case 'TCO':
120
+ case 'TCON':
121
+ output = this.splitValue(type, text).map(v => parseGenre(v)).reduce((acc, val) => acc.concat(val), []);
122
+ break;
123
+ case 'PCS':
124
+ case 'PCST':
125
+ // TODO: Why `default` not results `1` but `''`?
126
+ output = this.major >= 4 ? this.splitValue(type, text) : [text];
127
+ output = (Array.isArray(output) && output[0] === '') ? 1 : 0;
128
+ break;
129
+ default:
130
+ output = this.major >= 4 ? this.splitValue(type, text) : [text];
131
+ }
132
+ break;
133
+ case 'TXXX':
134
+ output = FrameParser.readIdentifierAndData(b, offset + 1, length, encoding);
135
+ output = {
136
+ description: output.id,
137
+ text: this.splitValue(type, util.decodeString(output.data, encoding).replace(/\x00+$/, ''))
138
+ };
139
+ break;
140
+ case 'PIC':
141
+ case 'APIC':
142
+ if (includeCovers) {
143
+ const pic = {};
144
+ offset += 1;
145
+ switch (this.major) {
146
+ case 2:
147
+ pic.format = util.decodeString(b.slice(offset, offset + 3), 'latin1'); // 'latin1'; // latin1 == iso-8859-1;
148
+ offset += 3;
149
+ break;
150
+ case 3:
151
+ case 4:
152
+ fzero = util.findZero(b, offset, length, defaultEnc);
153
+ pic.format = util.decodeString(b.slice(offset, fzero), defaultEnc);
154
+ offset = fzero + 1;
155
+ break;
156
+ default:
157
+ throw new Error('Warning: unexpected major versionIndex: ' + this.major);
158
+ }
159
+ pic.format = FrameParser.fixPictureMimeType(pic.format);
160
+ pic.type = ID3v2Token_1.AttachedPictureType[b[offset]];
161
+ offset += 1;
162
+ fzero = util.findZero(b, offset, length, encoding);
163
+ pic.description = util.decodeString(b.slice(offset, fzero), encoding);
164
+ offset = fzero + nullTerminatorLength;
165
+ pic.data = Buffer.from(b.slice(offset, length));
166
+ output = pic;
167
+ }
168
+ break;
169
+ case 'CNT':
170
+ case 'PCNT':
171
+ output = Token.UINT32_BE.get(b, 0);
172
+ break;
173
+ case 'SYLT':
174
+ // skip text encoding (1 byte),
175
+ // language (3 bytes),
176
+ // time stamp format (1 byte),
177
+ // content tagTypes (1 byte),
178
+ // content descriptor (1 byte)
179
+ offset += 7;
180
+ output = [];
181
+ while (offset < length) {
182
+ const txt = b.slice(offset, offset = util.findZero(b, offset, length, encoding));
183
+ offset += 5; // push offset forward one + 4 byte timestamp
184
+ output.push(util.decodeString(txt, encoding));
185
+ }
186
+ break;
187
+ case 'ULT':
188
+ case 'USLT':
189
+ case 'COM':
190
+ case 'COMM':
191
+ offset += 1;
192
+ out.language = util.decodeString(b.slice(offset, offset + 3), defaultEnc);
193
+ offset += 3;
194
+ fzero = util.findZero(b, offset, length, encoding);
195
+ out.description = util.decodeString(b.slice(offset, fzero), encoding);
196
+ offset = fzero + nullTerminatorLength;
197
+ out.text = util.decodeString(b.slice(offset, length), encoding).replace(/\x00+$/, '');
198
+ output = [out];
199
+ break;
200
+ case 'UFID':
201
+ output = FrameParser.readIdentifierAndData(b, offset, length, defaultEnc);
202
+ output = { owner_identifier: output.id, identifier: output.data };
203
+ break;
204
+ case 'PRIV': // private frame
205
+ output = FrameParser.readIdentifierAndData(b, offset, length, defaultEnc);
206
+ output = { owner_identifier: output.id, data: output.data };
207
+ break;
208
+ case 'POPM': // Popularimeter
209
+ fzero = util.findZero(b, offset, length, defaultEnc);
210
+ const email = util.decodeString(b.slice(offset, fzero), defaultEnc);
211
+ offset = fzero + 1;
212
+ const dataLen = length - offset;
213
+ output = {
214
+ email,
215
+ rating: b.readUInt8(offset),
216
+ counter: dataLen >= 5 ? b.readUInt32BE(offset + 1) : undefined
217
+ };
218
+ break;
219
+ case 'GEOB': { // General encapsulated object
220
+ fzero = util.findZero(b, offset + 1, length, encoding);
221
+ const mimeType = util.decodeString(b.slice(offset + 1, fzero), defaultEnc);
222
+ offset = fzero + 1;
223
+ fzero = util.findZero(b, offset, length - offset, encoding);
224
+ const filename = util.decodeString(b.slice(offset, fzero), defaultEnc);
225
+ offset = fzero + 1;
226
+ fzero = util.findZero(b, offset, length - offset, encoding);
227
+ const description = util.decodeString(b.slice(offset, fzero), defaultEnc);
228
+ output = {
229
+ type: mimeType,
230
+ filename,
231
+ description,
232
+ data: b.slice(offset + 1, length)
233
+ };
234
+ break;
235
+ }
236
+ // W-Frames:
237
+ case 'WCOM':
238
+ case 'WCOP':
239
+ case 'WOAF':
240
+ case 'WOAR':
241
+ case 'WOAS':
242
+ case 'WORS':
243
+ case 'WPAY':
244
+ case 'WPUB':
245
+ // Decode URL
246
+ output = util.decodeString(b.slice(offset, fzero), defaultEnc);
247
+ break;
248
+ case 'WXXX': {
249
+ // Decode URL
250
+ fzero = util.findZero(b, offset + 1, length, encoding);
251
+ const description = util.decodeString(b.slice(offset + 1, fzero), encoding);
252
+ offset = fzero + (encoding === 'utf16le' ? 2 : 1);
253
+ output = { description, url: util.decodeString(b.slice(offset, length), defaultEnc) };
254
+ break;
255
+ }
256
+ case 'WFD':
257
+ case 'WFED':
258
+ output = util.decodeString(b.slice(offset + 1, util.findZero(b, offset + 1, length, encoding)), encoding);
259
+ break;
260
+ case 'MCDI': {
261
+ // Music CD identifier
262
+ output = b.slice(0, length);
263
+ break;
264
+ }
265
+ default:
266
+ debug('Warning: unsupported id3v2-tag-type: ' + type);
267
+ break;
268
+ }
269
+ return output;
270
+ }
271
+ static fixPictureMimeType(pictureType) {
272
+ pictureType = pictureType.toLocaleLowerCase();
273
+ switch (pictureType) {
274
+ case 'jpg':
275
+ return 'image/jpeg';
276
+ case 'png':
277
+ return 'image/png';
278
+ }
279
+ return pictureType;
280
+ }
281
+ /**
282
+ * Converts TMCL (Musician credits list) or TIPL (Involved people list)
283
+ * @param entries
284
+ */
285
+ static functionList(entries) {
286
+ const res = {};
287
+ for (let i = 0; i + 1 < entries.length; i += 2) {
288
+ const names = entries[i + 1].split(',');
289
+ res[entries[i]] = res.hasOwnProperty(entries[i]) ? res[entries[i]].concat(names) : names;
290
+ }
291
+ return res;
292
+ }
293
+ /**
294
+ * id3v2.4 defines that multiple T* values are separated by 0x00
295
+ * id3v2.3 defines that TCOM, TEXT, TOLY, TOPE & TPE1 values are separated by /
296
+ * @param tag - Tag name
297
+ * @param text - Concatenated tag value
298
+ * @returns Split tag value
299
+ */
300
+ splitValue(tag, text) {
301
+ let values;
302
+ if (this.major < 4) {
303
+ values = text.split(/\x00/g);
304
+ if (values.length > 1) {
305
+ this.warningCollector.addWarning(`ID3v2.${this.major} ${tag} uses non standard null-separator.`);
306
+ }
307
+ else {
308
+ values = text.split(/\//g);
309
+ }
310
+ }
311
+ else {
312
+ values = text.split(/\x00/g);
313
+ }
314
+ return FrameParser.trimArray(values);
315
+ }
316
+ static trimArray(values) {
317
+ return values.map(value => value.replace(/\x00+$/, '').trim());
318
+ }
319
+ static readIdentifierAndData(b, offset, length, encoding) {
320
+ const fzero = util.findZero(b, offset, length, encoding);
321
+ const id = util.decodeString(b.slice(offset, fzero), encoding);
322
+ offset = fzero + FrameParser.getNullTerminatorLength(encoding);
323
+ return { id, data: b.slice(offset, length) };
324
+ }
325
+ static getNullTerminatorLength(enc) {
326
+ return enc === 'utf16le' ? 2 : 1;
327
+ }
328
+ }
329
+ exports.FrameParser = FrameParser;