music-metadata 11.10.4 → 11.10.6
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/lib/apev2/APEv2Parser.js +2 -2
- package/lib/asf/AsfGuid.d.ts +83 -0
- package/lib/asf/AsfGuid.js +109 -0
- package/lib/asf/AsfObject.d.ts +13 -13
- package/lib/asf/AsfObject.js +15 -15
- package/lib/asf/AsfParser.js +8 -8
- package/lib/common/Util.d.ts +3 -5
- package/lib/common/Util.js +15 -17
- package/lib/ebml/EbmlIterator.d.ts +0 -1
- package/lib/ebml/EbmlIterator.js +1 -4
- package/lib/flac/FlacParser.d.ts +0 -1
- package/lib/flac/FlacParser.js +0 -2
- package/lib/id3v2/FrameHeader.d.ts +31 -0
- package/lib/id3v2/FrameHeader.js +73 -0
- package/lib/id3v2/FrameParser.d.ts +1 -0
- package/lib/id3v2/FrameParser.js +87 -63
- package/lib/id3v2/ID3v2Parser.d.ts +0 -3
- package/lib/id3v2/ID3v2Parser.js +4 -60
- package/lib/mpeg/MpegParser.d.ts +0 -2
- package/lib/mpeg/MpegParser.js +2 -5
- package/lib/ogg/vorbis/VorbisStream.d.ts +1 -1
- package/lib/ogg/vorbis/VorbisStream.js +1 -1
- package/package.json +5 -4
- package/lib/asf/GUID.d.ts +0 -83
- package/lib/asf/GUID.js +0 -118
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// lib/id3v2/FrameHeader.ts
|
|
2
|
+
import * as Token from 'token-types';
|
|
3
|
+
import * as util from '../common/Util.js';
|
|
4
|
+
import { UINT32SYNCSAFE } from './ID3v2Token.js';
|
|
5
|
+
import { textDecode } from '@borewit/text-codec';
|
|
6
|
+
import { Id3v2ContentError } from './FrameParser.js';
|
|
7
|
+
/**
|
|
8
|
+
* Frame header length (bytes) depending on ID3v2 major version.
|
|
9
|
+
*/
|
|
10
|
+
export function getFrameHeaderLength(majorVer) {
|
|
11
|
+
switch (majorVer) {
|
|
12
|
+
case 2: return 6;
|
|
13
|
+
case 3:
|
|
14
|
+
case 4: return 10;
|
|
15
|
+
default: throw makeUnexpectedMajorVersionError(majorVer);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function readFrameFlags(b) {
|
|
19
|
+
return {
|
|
20
|
+
status: {
|
|
21
|
+
tag_alter_preservation: util.getBit(b, 0, 6),
|
|
22
|
+
file_alter_preservation: util.getBit(b, 0, 5),
|
|
23
|
+
read_only: util.getBit(b, 0, 4)
|
|
24
|
+
},
|
|
25
|
+
format: {
|
|
26
|
+
grouping_identity: util.getBit(b, 1, 7),
|
|
27
|
+
compression: util.getBit(b, 1, 3),
|
|
28
|
+
encryption: util.getBit(b, 1, 2),
|
|
29
|
+
unsynchronisation: util.getBit(b, 1, 1),
|
|
30
|
+
data_length_indicator: util.getBit(b, 1, 0)
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Factory: parse a frame header from its header bytes (6 for v2.2, 10 for v2.3/v2.4).
|
|
36
|
+
*
|
|
37
|
+
* Note: It only *parses* and does light validation. It does not read payload bytes.
|
|
38
|
+
*/
|
|
39
|
+
export function readFrameHeader(uint8Array, majorVer, warningCollector) {
|
|
40
|
+
switch (majorVer) {
|
|
41
|
+
case 2:
|
|
42
|
+
return parseFrameHeaderV22(uint8Array, majorVer, warningCollector);
|
|
43
|
+
case 3:
|
|
44
|
+
case 4:
|
|
45
|
+
return parseFrameHeaderV23V24(uint8Array, majorVer, warningCollector);
|
|
46
|
+
default:
|
|
47
|
+
throw makeUnexpectedMajorVersionError(majorVer);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function parseFrameHeaderV22(uint8Array, majorVer, warningCollector) {
|
|
51
|
+
const header = {
|
|
52
|
+
id: textDecode(uint8Array.subarray(0, 3), 'ascii'),
|
|
53
|
+
length: Token.UINT24_BE.get(uint8Array, 3)
|
|
54
|
+
};
|
|
55
|
+
if (!header.id.match(/^[A-Z0-9]{3}$/)) {
|
|
56
|
+
warningCollector.addWarning(`Invalid ID3v2.${majorVer} frame-header-ID: ${header.id}`);
|
|
57
|
+
}
|
|
58
|
+
return header;
|
|
59
|
+
}
|
|
60
|
+
function parseFrameHeaderV23V24(uint8Array, majorVer, warningCollector) {
|
|
61
|
+
const header = {
|
|
62
|
+
id: textDecode(uint8Array.subarray(0, 4), 'ascii'),
|
|
63
|
+
length: (majorVer === 4 ? UINT32SYNCSAFE : Token.UINT32_BE).get(uint8Array, 4),
|
|
64
|
+
flags: readFrameFlags(uint8Array.subarray(8, 10))
|
|
65
|
+
};
|
|
66
|
+
if (!header.id.match(/^[A-Z0-9]{4}$/)) {
|
|
67
|
+
warningCollector.addWarning(`Invalid ID3v2.${majorVer} frame-header-ID: ${header.id}`);
|
|
68
|
+
}
|
|
69
|
+
return header;
|
|
70
|
+
}
|
|
71
|
+
function makeUnexpectedMajorVersionError(majorVer) {
|
|
72
|
+
throw new Id3v2ContentError(`Unexpected majorVer: ${majorVer}`);
|
|
73
|
+
}
|
package/lib/id3v2/FrameParser.js
CHANGED
|
@@ -7,6 +7,7 @@ import { makeUnexpectedFileContentError } from '../ParseError.js';
|
|
|
7
7
|
import { decodeUintBE } from '../common/Util.js';
|
|
8
8
|
const debug = initDebug('music-metadata:id3v2:frame-parser');
|
|
9
9
|
const defaultEnc = 'latin1'; // latin1 == iso-8859-1;
|
|
10
|
+
const urlEnc = { encoding: defaultEnc, bom: false };
|
|
10
11
|
export function parseGenre(origVal) {
|
|
11
12
|
// match everything inside parentheses
|
|
12
13
|
const genres = [];
|
|
@@ -90,7 +91,7 @@ export class FrameParser {
|
|
|
90
91
|
case 'PCST': {
|
|
91
92
|
let text;
|
|
92
93
|
try {
|
|
93
|
-
text = util.decodeString(uint8Array.subarray(1), encoding)
|
|
94
|
+
text = FrameParser.trimNullPadding(util.decodeString(uint8Array.subarray(1), encoding));
|
|
94
95
|
}
|
|
95
96
|
catch (error) {
|
|
96
97
|
if (error instanceof Error) {
|
|
@@ -135,7 +136,7 @@ export class FrameParser {
|
|
|
135
136
|
break;
|
|
136
137
|
}
|
|
137
138
|
case 'TXXX': {
|
|
138
|
-
const idAndData = FrameParser.readIdentifierAndData(uint8Array
|
|
139
|
+
const idAndData = FrameParser.readIdentifierAndData(uint8Array.subarray(1), encoding);
|
|
139
140
|
const textTag = {
|
|
140
141
|
description: idAndData.id,
|
|
141
142
|
text: this.splitValue(type, util.decodeString(idAndData.data, encoding).replace(/\x00+$/, ''))
|
|
@@ -147,28 +148,28 @@ export class FrameParser {
|
|
|
147
148
|
case 'APIC':
|
|
148
149
|
if (includeCovers) {
|
|
149
150
|
const pic = {};
|
|
150
|
-
|
|
151
|
+
uint8Array = uint8Array.subarray(1);
|
|
151
152
|
switch (this.major) {
|
|
152
153
|
case 2:
|
|
153
|
-
pic.format = util.decodeString(uint8Array.subarray(
|
|
154
|
-
|
|
154
|
+
pic.format = util.decodeString(uint8Array.subarray(0, 3), 'latin1'); // 'latin1'; // latin1 == iso-8859-1;
|
|
155
|
+
uint8Array = uint8Array.subarray(3);
|
|
155
156
|
break;
|
|
156
157
|
case 3:
|
|
157
158
|
case 4:
|
|
158
|
-
fzero = util.findZero(uint8Array,
|
|
159
|
-
pic.format = util.decodeString(uint8Array.subarray(
|
|
160
|
-
|
|
159
|
+
fzero = util.findZero(uint8Array, defaultEnc);
|
|
160
|
+
pic.format = util.decodeString(uint8Array.subarray(0, fzero), defaultEnc);
|
|
161
|
+
uint8Array = uint8Array.subarray(fzero + 1);
|
|
161
162
|
break;
|
|
162
163
|
default:
|
|
163
164
|
throw makeUnexpectedMajorVersionError(this.major);
|
|
164
165
|
}
|
|
165
166
|
pic.format = FrameParser.fixPictureMimeType(pic.format);
|
|
166
|
-
pic.type = AttachedPictureType[uint8Array[
|
|
167
|
-
|
|
168
|
-
fzero = util.findZero(uint8Array,
|
|
169
|
-
pic.description = util.decodeString(uint8Array.subarray(
|
|
170
|
-
|
|
171
|
-
pic.data = uint8Array
|
|
167
|
+
pic.type = AttachedPictureType[uint8Array[0]];
|
|
168
|
+
uint8Array = uint8Array.subarray(1);
|
|
169
|
+
fzero = util.findZero(uint8Array, encoding);
|
|
170
|
+
pic.description = util.decodeString(uint8Array.subarray(0, fzero), encoding);
|
|
171
|
+
uint8Array = uint8Array.subarray(fzero + nullTerminatorLength);
|
|
172
|
+
pic.data = uint8Array;
|
|
172
173
|
output = pic;
|
|
173
174
|
}
|
|
174
175
|
break;
|
|
@@ -178,7 +179,7 @@ export class FrameParser {
|
|
|
178
179
|
break;
|
|
179
180
|
case 'SYLT': {
|
|
180
181
|
const syltHeader = SyncTextHeader.get(uint8Array, 0);
|
|
181
|
-
|
|
182
|
+
uint8Array = uint8Array.subarray(SyncTextHeader.len);
|
|
182
183
|
const result = {
|
|
183
184
|
descriptor: '',
|
|
184
185
|
language: syltHeader.language,
|
|
@@ -187,12 +188,12 @@ export class FrameParser {
|
|
|
187
188
|
syncText: []
|
|
188
189
|
};
|
|
189
190
|
let readSyllables = false;
|
|
190
|
-
while (
|
|
191
|
-
const nullStr = FrameParser.readNullTerminatedString(uint8Array
|
|
192
|
-
|
|
191
|
+
while (uint8Array.length > 0) {
|
|
192
|
+
const nullStr = FrameParser.readNullTerminatedString(uint8Array, syltHeader.encoding);
|
|
193
|
+
uint8Array = uint8Array.subarray(nullStr.len);
|
|
193
194
|
if (readSyllables) {
|
|
194
|
-
const timestamp = Token.UINT32_BE.get(uint8Array,
|
|
195
|
-
|
|
195
|
+
const timestamp = Token.UINT32_BE.get(uint8Array, 0);
|
|
196
|
+
uint8Array = uint8Array.subarray(Token.UINT32_BE.len);
|
|
196
197
|
result.syncText.push({
|
|
197
198
|
text: nullStr.text,
|
|
198
199
|
timestamp
|
|
@@ -224,42 +225,52 @@ export class FrameParser {
|
|
|
224
225
|
break;
|
|
225
226
|
}
|
|
226
227
|
case 'UFID': {
|
|
227
|
-
const ufid = FrameParser.readIdentifierAndData(uint8Array,
|
|
228
|
+
const ufid = FrameParser.readIdentifierAndData(uint8Array, defaultEnc);
|
|
228
229
|
output = { owner_identifier: ufid.id, identifier: ufid.data };
|
|
229
230
|
break;
|
|
230
231
|
}
|
|
231
232
|
case 'PRIV': { // private frame
|
|
232
|
-
const priv = FrameParser.readIdentifierAndData(uint8Array,
|
|
233
|
+
const priv = FrameParser.readIdentifierAndData(uint8Array, defaultEnc);
|
|
233
234
|
output = { owner_identifier: priv.id, data: priv.data };
|
|
234
235
|
break;
|
|
235
236
|
}
|
|
236
237
|
case 'POPM': { // Popularimeter
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
uint8Array = uint8Array.subarray(offset);
|
|
239
|
+
const emailStr = FrameParser.readNullTerminatedString(uint8Array, urlEnc);
|
|
240
|
+
const email = emailStr.text;
|
|
241
|
+
uint8Array = uint8Array.subarray(emailStr.len);
|
|
242
|
+
if (uint8Array.length === 0) {
|
|
243
|
+
this.warningCollector.addWarning(`id3v2.${this.major} type=${type} POPM frame missing rating byte`);
|
|
244
|
+
output = { email, rating: 0, counter: undefined };
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
const rating = Token.UINT8.get(uint8Array, 0);
|
|
248
|
+
const counterBytes = uint8Array.subarray(Token.UINT8.len);
|
|
241
249
|
output = {
|
|
242
250
|
email,
|
|
243
|
-
rating
|
|
244
|
-
counter:
|
|
251
|
+
rating,
|
|
252
|
+
counter: counterBytes.length > 0 ? decodeUintBE(counterBytes) : undefined
|
|
245
253
|
};
|
|
246
254
|
break;
|
|
247
255
|
}
|
|
248
256
|
case 'GEOB': { // General encapsulated object
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
|
|
257
|
+
// [encoding] <MIME> 0x00 <filename> 0x00/0x00 0x00 <description> 0x00/0x00 0x00 <data>
|
|
258
|
+
const encoding = TextEncodingToken.get(uint8Array, 0);
|
|
259
|
+
uint8Array = uint8Array.subarray(1);
|
|
260
|
+
const mimeTypeStr = FrameParser.readNullTerminatedString(uint8Array, urlEnc);
|
|
261
|
+
const mimeType = mimeTypeStr.text;
|
|
262
|
+
uint8Array = uint8Array.subarray(mimeTypeStr.len);
|
|
263
|
+
const filenameStr = FrameParser.readNullTerminatedString(uint8Array, encoding);
|
|
264
|
+
const filename = filenameStr.text;
|
|
265
|
+
uint8Array = uint8Array.subarray(filenameStr.len);
|
|
266
|
+
const descriptionStr = FrameParser.readNullTerminatedString(uint8Array, encoding);
|
|
267
|
+
const description = descriptionStr.text;
|
|
268
|
+
uint8Array = uint8Array.subarray(descriptionStr.len);
|
|
258
269
|
const geob = {
|
|
259
270
|
type: mimeType,
|
|
260
271
|
filename,
|
|
261
272
|
description,
|
|
262
|
-
data: uint8Array
|
|
273
|
+
data: uint8Array
|
|
263
274
|
};
|
|
264
275
|
output = geob;
|
|
265
276
|
break;
|
|
@@ -274,21 +285,26 @@ export class FrameParser {
|
|
|
274
285
|
case 'WPAY':
|
|
275
286
|
case 'WPUB':
|
|
276
287
|
// Decode URL
|
|
277
|
-
|
|
278
|
-
output = util.decodeString(uint8Array.subarray(offset, fzero), defaultEnc);
|
|
288
|
+
output = FrameParser.readNullTerminatedString(uint8Array, urlEnc).text;
|
|
279
289
|
break;
|
|
280
290
|
case 'WXXX': {
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
291
|
+
// [encoding] <description> 0x00/0x00 0x00 <url>
|
|
292
|
+
const encoding = TextEncodingToken.get(uint8Array, 0);
|
|
293
|
+
uint8Array = uint8Array.subarray(1);
|
|
294
|
+
const descriptionStr = FrameParser.readNullTerminatedString(uint8Array, encoding);
|
|
295
|
+
const description = descriptionStr.text;
|
|
296
|
+
uint8Array = uint8Array.subarray(descriptionStr.len);
|
|
297
|
+
// URL is always ISO-8859-1
|
|
298
|
+
output = { description, url: FrameParser.trimNullPadding(util.decodeString(uint8Array, defaultEnc)) };
|
|
286
299
|
break;
|
|
287
300
|
}
|
|
288
301
|
case 'WFD':
|
|
289
|
-
case 'WFED':
|
|
290
|
-
|
|
302
|
+
case 'WFED': {
|
|
303
|
+
const encoding = TextEncodingToken.get(uint8Array, 0);
|
|
304
|
+
uint8Array = uint8Array.subarray(1);
|
|
305
|
+
output = FrameParser.readNullTerminatedString(uint8Array, encoding).text;
|
|
291
306
|
break;
|
|
307
|
+
}
|
|
292
308
|
case 'MCDI': {
|
|
293
309
|
// Music CD identifier
|
|
294
310
|
output = uint8Array.subarray(0, length);
|
|
@@ -301,18 +317,21 @@ export class FrameParser {
|
|
|
301
317
|
return output;
|
|
302
318
|
}
|
|
303
319
|
static readNullTerminatedString(uint8Array, encoding) {
|
|
304
|
-
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
320
|
+
const bomSize = encoding.bom ? 2 : 0;
|
|
321
|
+
const originalLen = uint8Array.length;
|
|
322
|
+
const valueArray = uint8Array.subarray(bomSize);
|
|
323
|
+
const zeroIndex = util.findZero(valueArray, encoding.encoding);
|
|
324
|
+
if (zeroIndex >= valueArray.length) {
|
|
325
|
+
// No terminator found, decode full buffer remainder
|
|
326
|
+
return {
|
|
327
|
+
text: util.decodeString(valueArray, encoding.encoding),
|
|
328
|
+
len: originalLen
|
|
329
|
+
};
|
|
312
330
|
}
|
|
331
|
+
const txt = valueArray.subarray(0, zeroIndex);
|
|
313
332
|
return {
|
|
314
333
|
text: util.decodeString(txt, encoding.encoding),
|
|
315
|
-
len:
|
|
334
|
+
len: bomSize + zeroIndex + FrameParser.getNullTerminatorLength(encoding.encoding)
|
|
316
335
|
};
|
|
317
336
|
}
|
|
318
337
|
static fixPictureMimeType(pictureType) {
|
|
@@ -361,16 +380,21 @@ export class FrameParser {
|
|
|
361
380
|
return FrameParser.trimArray(values);
|
|
362
381
|
}
|
|
363
382
|
static trimArray(values) {
|
|
364
|
-
return values.map(value =>
|
|
383
|
+
return values.map(value => FrameParser.trimNullPadding(value).trim());
|
|
384
|
+
}
|
|
385
|
+
static trimNullPadding(value) {
|
|
386
|
+
let end = value.length;
|
|
387
|
+
while (end > 0 && value.charCodeAt(end - 1) === 0) {
|
|
388
|
+
end--;
|
|
389
|
+
}
|
|
390
|
+
return end === value.length ? value : value.slice(0, end);
|
|
365
391
|
}
|
|
366
|
-
static readIdentifierAndData(uint8Array,
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
offset = fzero + FrameParser.getNullTerminatorLength(encoding);
|
|
370
|
-
return { id, data: uint8Array.subarray(offset, length) };
|
|
392
|
+
static readIdentifierAndData(uint8Array, encoding) {
|
|
393
|
+
const idStr = FrameParser.readNullTerminatedString(uint8Array, { encoding, bom: false });
|
|
394
|
+
return { id: idStr.text, data: uint8Array.subarray(idStr.len) };
|
|
371
395
|
}
|
|
372
396
|
static getNullTerminatorLength(enc) {
|
|
373
|
-
return enc
|
|
397
|
+
return enc.startsWith('utf-16') ? 2 : 1;
|
|
374
398
|
}
|
|
375
399
|
}
|
|
376
400
|
export class Id3v2ContentError extends makeUnexpectedFileContentError('id3v2') {
|
|
@@ -3,8 +3,6 @@ import type { IOptions } from '../type.js';
|
|
|
3
3
|
import type { INativeMetadataCollector } from '../common/MetadataCollector.js';
|
|
4
4
|
export declare class ID3v2Parser {
|
|
5
5
|
static removeUnsyncBytes(buffer: Uint8Array): Uint8Array;
|
|
6
|
-
private static getFrameHeaderLength;
|
|
7
|
-
private static readFrameFlags;
|
|
8
6
|
private static readFrameData;
|
|
9
7
|
/**
|
|
10
8
|
* Create a combined tag key, of tag & description
|
|
@@ -25,5 +23,4 @@ export declare class ID3v2Parser {
|
|
|
25
23
|
private handleTag;
|
|
26
24
|
private addTag;
|
|
27
25
|
private parseMetadata;
|
|
28
|
-
private readFrameHeader;
|
|
29
26
|
}
|
package/lib/id3v2/ID3v2Parser.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import * as Token from 'token-types';
|
|
2
|
-
import * as util from '../common/Util.js';
|
|
3
2
|
import { FrameParser, Id3v2ContentError } from './FrameParser.js';
|
|
4
|
-
import { ExtendedHeader, ID3v2Header
|
|
5
|
-
import {
|
|
3
|
+
import { ExtendedHeader, ID3v2Header } from './ID3v2Token.js';
|
|
4
|
+
import { getFrameHeaderLength, readFrameHeader } from './FrameHeader.js';
|
|
6
5
|
export class ID3v2Parser {
|
|
7
6
|
constructor() {
|
|
8
7
|
this.tokenizer = undefined;
|
|
@@ -26,33 +25,6 @@ export class ID3v2Parser {
|
|
|
26
25
|
}
|
|
27
26
|
return buffer.subarray(0, writeI);
|
|
28
27
|
}
|
|
29
|
-
static getFrameHeaderLength(majorVer) {
|
|
30
|
-
switch (majorVer) {
|
|
31
|
-
case 2:
|
|
32
|
-
return 6;
|
|
33
|
-
case 3:
|
|
34
|
-
case 4:
|
|
35
|
-
return 10;
|
|
36
|
-
default:
|
|
37
|
-
throw makeUnexpectedMajorVersionError(majorVer);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
static readFrameFlags(b) {
|
|
41
|
-
return {
|
|
42
|
-
status: {
|
|
43
|
-
tag_alter_preservation: util.getBit(b, 0, 6),
|
|
44
|
-
file_alter_preservation: util.getBit(b, 0, 5),
|
|
45
|
-
read_only: util.getBit(b, 0, 4)
|
|
46
|
-
},
|
|
47
|
-
format: {
|
|
48
|
-
grouping_identity: util.getBit(b, 1, 7),
|
|
49
|
-
compression: util.getBit(b, 1, 3),
|
|
50
|
-
encryption: util.getBit(b, 1, 2),
|
|
51
|
-
unsynchronisation: util.getBit(b, 1, 1),
|
|
52
|
-
data_length_indicator: util.getBit(b, 1, 0)
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
28
|
static readFrameData(uint8Array, frameHeader, majorVer, includeCovers, warningCollector) {
|
|
57
29
|
const frameParser = new FrameParser(majorVer, warningCollector);
|
|
58
30
|
switch (majorVer) {
|
|
@@ -127,14 +99,14 @@ export class ID3v2Parser {
|
|
|
127
99
|
while (true) {
|
|
128
100
|
if (offset === data.length)
|
|
129
101
|
break;
|
|
130
|
-
const frameHeaderLength =
|
|
102
|
+
const frameHeaderLength = getFrameHeaderLength(this.id3Header.version.major);
|
|
131
103
|
if (offset + frameHeaderLength > data.length) {
|
|
132
104
|
this.metadata.addWarning('Illegal ID3v2 tag length');
|
|
133
105
|
break;
|
|
134
106
|
}
|
|
135
107
|
const frameHeaderBytes = data.subarray(offset, offset + frameHeaderLength);
|
|
136
108
|
offset += frameHeaderLength;
|
|
137
|
-
const frameHeader =
|
|
109
|
+
const frameHeader = readFrameHeader(frameHeaderBytes, this.id3Header.version.major, this.metadata);
|
|
138
110
|
const frameDataBytes = data.subarray(offset, offset + frameHeader.length);
|
|
139
111
|
offset += frameHeader.length;
|
|
140
112
|
const values = ID3v2Parser.readFrameData(frameDataBytes, frameHeader, this.id3Header.version.major, !this.options.skipCovers, this.metadata);
|
|
@@ -144,34 +116,6 @@ export class ID3v2Parser {
|
|
|
144
116
|
}
|
|
145
117
|
return tags;
|
|
146
118
|
}
|
|
147
|
-
readFrameHeader(uint8Array, majorVer) {
|
|
148
|
-
let header;
|
|
149
|
-
switch (majorVer) {
|
|
150
|
-
case 2:
|
|
151
|
-
header = {
|
|
152
|
-
id: textDecode(uint8Array.subarray(0, 3), 'ascii'),
|
|
153
|
-
length: Token.UINT24_BE.get(uint8Array, 3)
|
|
154
|
-
};
|
|
155
|
-
if (!header.id.match(/[A-Z0-9]{3}/g)) {
|
|
156
|
-
this.metadata.addWarning(`Invalid ID3v2.${this.id3Header.version.major} frame-header-ID: ${header.id}`);
|
|
157
|
-
}
|
|
158
|
-
break;
|
|
159
|
-
case 3:
|
|
160
|
-
case 4:
|
|
161
|
-
header = {
|
|
162
|
-
id: textDecode(uint8Array.subarray(0, 4), 'ascii'),
|
|
163
|
-
length: (majorVer === 4 ? UINT32SYNCSAFE : Token.UINT32_BE).get(uint8Array, 4),
|
|
164
|
-
flags: ID3v2Parser.readFrameFlags(uint8Array.subarray(8, 10))
|
|
165
|
-
};
|
|
166
|
-
if (!header.id.match(/[A-Z0-9]{4}/g)) {
|
|
167
|
-
this.metadata.addWarning(`Invalid ID3v2.${this.id3Header.version.major} frame-header-ID: ${header.id}`);
|
|
168
|
-
}
|
|
169
|
-
break;
|
|
170
|
-
default:
|
|
171
|
-
throw makeUnexpectedMajorVersionError(majorVer);
|
|
172
|
-
}
|
|
173
|
-
return header;
|
|
174
|
-
}
|
|
175
119
|
}
|
|
176
120
|
function makeUnexpectedMajorVersionError(majorVer) {
|
|
177
121
|
throw new Id3v2ContentError(`Unexpected majorVer: ${majorVer}`);
|
package/lib/mpeg/MpegParser.d.ts
CHANGED
|
@@ -16,13 +16,11 @@ export declare class MpegContentError extends MpegContentError_base {
|
|
|
16
16
|
export declare class MpegParser extends AbstractID3Parser {
|
|
17
17
|
private frameCount;
|
|
18
18
|
private syncFrameCount;
|
|
19
|
-
private countSkipFrameData;
|
|
20
19
|
private totalDataLength;
|
|
21
20
|
private audioFrameHeader?;
|
|
22
21
|
private bitrates;
|
|
23
22
|
private offset;
|
|
24
23
|
private frame_size;
|
|
25
|
-
private crc;
|
|
26
24
|
private calculateEofDuration;
|
|
27
25
|
private samplesPerFrame;
|
|
28
26
|
private buf_frame_header;
|
package/lib/mpeg/MpegParser.js
CHANGED
|
@@ -233,12 +233,10 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
233
233
|
super(...arguments);
|
|
234
234
|
this.frameCount = 0;
|
|
235
235
|
this.syncFrameCount = -1;
|
|
236
|
-
this.countSkipFrameData = 0;
|
|
237
236
|
this.totalDataLength = 0;
|
|
238
237
|
this.bitrates = [];
|
|
239
238
|
this.offset = 0;
|
|
240
239
|
this.frame_size = 0;
|
|
241
|
-
this.crc = null;
|
|
242
240
|
this.calculateEofDuration = false;
|
|
243
241
|
this.samplesPerFrame = null;
|
|
244
242
|
this.buf_frame_header = new Uint8Array(4);
|
|
@@ -467,8 +465,8 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
467
465
|
return false;
|
|
468
466
|
}
|
|
469
467
|
async parseCrc() {
|
|
470
|
-
|
|
471
|
-
this.offset +=
|
|
468
|
+
await this.tokenizer.ignore(Token.INT16_BE.len); // Ignore CRC
|
|
469
|
+
this.offset += Token.INT16_BE.len;
|
|
472
470
|
return this.skipSideInformation();
|
|
473
471
|
}
|
|
474
472
|
async skipSideInformation() {
|
|
@@ -561,7 +559,6 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
561
559
|
if (frameDataLeft < 0)
|
|
562
560
|
throw new MpegContentError('frame-data-left cannot be negative');
|
|
563
561
|
await this.tokenizer.ignore(frameDataLeft);
|
|
564
|
-
this.countSkipFrameData += frameDataLeft;
|
|
565
562
|
}
|
|
566
563
|
areAllSame(array) {
|
|
567
564
|
const first = array[0];
|
|
@@ -37,7 +37,7 @@ export declare class VorbisStream implements IPageConsumer {
|
|
|
37
37
|
flush(): Promise<void>;
|
|
38
38
|
parseUserComment(pageData: Uint8Array, offset: number): Promise<number>;
|
|
39
39
|
addTag(id: string, value: string | IVorbisPicture): Promise<void>;
|
|
40
|
-
calculateDuration(
|
|
40
|
+
calculateDuration(_enfOfStream: boolean): void;
|
|
41
41
|
/**
|
|
42
42
|
* Parse first Ogg/Vorbis page
|
|
43
43
|
* @param _header
|
|
@@ -77,7 +77,7 @@ export class VorbisStream {
|
|
|
77
77
|
}
|
|
78
78
|
await this.metadata.addTag('vorbis', id, value);
|
|
79
79
|
}
|
|
80
|
-
calculateDuration(
|
|
80
|
+
calculateDuration(_enfOfStream) {
|
|
81
81
|
if (this.lastPageHeader && this.metadata.format.sampleRate && this.lastPageHeader.absoluteGranulePosition >= 0) {
|
|
82
82
|
// Calculate duration
|
|
83
83
|
this.metadata.setFormat('numberOfSamples', this.lastPageHeader.absoluteGranulePosition);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "music-metadata",
|
|
3
3
|
"description": "Music metadata parser for Node.js, supporting virtual any audio and tag format.",
|
|
4
|
-
"version": "11.10.
|
|
4
|
+
"version": "11.10.6",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Borewit",
|
|
7
7
|
"url": "https://github.com/Borewit"
|
|
@@ -110,14 +110,15 @@
|
|
|
110
110
|
"@tokenizer/token": "^0.3.0",
|
|
111
111
|
"content-type": "^1.0.5",
|
|
112
112
|
"debug": "^4.4.3",
|
|
113
|
-
"file-type": "^21.
|
|
113
|
+
"file-type": "^21.3.0",
|
|
114
114
|
"media-typer": "^1.1.0",
|
|
115
115
|
"strtok3": "^10.3.4",
|
|
116
116
|
"token-types": "^6.1.2",
|
|
117
|
-
"uint8array-extras": "^1.5.0"
|
|
117
|
+
"uint8array-extras": "^1.5.0",
|
|
118
|
+
"win-guid": "^0.1.3"
|
|
118
119
|
},
|
|
119
120
|
"devDependencies": {
|
|
120
|
-
"@biomejs/biome": "2.3.
|
|
121
|
+
"@biomejs/biome": "2.3.11",
|
|
121
122
|
"@types/chai": "^5.2.3",
|
|
122
123
|
"@types/chai-as-promised": "^8.0.2",
|
|
123
124
|
"@types/content-type": "^1.1.9",
|
package/lib/asf/GUID.d.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ref:
|
|
3
|
-
* - https://tools.ietf.org/html/draft-fleischman-asf-01, Appendix A: ASF GUIDs
|
|
4
|
-
* - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
|
|
5
|
-
* - http://drang.s4.xrea.com/program/tips/id3tag/wmp/index.html
|
|
6
|
-
* - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
|
|
7
|
-
*
|
|
8
|
-
* ASF File Structure:
|
|
9
|
-
* - https://msdn.microsoft.com/en-us/library/windows/desktop/ee663575(v=vs.85).aspx
|
|
10
|
-
*
|
|
11
|
-
* ASF GUIDs:
|
|
12
|
-
* - http://drang.s4.xrea.com/program/tips/id3tag/wmp/10_asf_guids.html
|
|
13
|
-
* - https://github.com/dji-sdk/FFmpeg/blob/master/libavformat/asf.c
|
|
14
|
-
*/
|
|
15
|
-
export default class GUID {
|
|
16
|
-
static HeaderObject: GUID;
|
|
17
|
-
static DataObject: GUID;
|
|
18
|
-
static SimpleIndexObject: GUID;
|
|
19
|
-
static IndexObject: GUID;
|
|
20
|
-
static MediaObjectIndexObject: GUID;
|
|
21
|
-
static TimecodeIndexObject: GUID;
|
|
22
|
-
static FilePropertiesObject: GUID;
|
|
23
|
-
static StreamPropertiesObject: GUID;
|
|
24
|
-
static HeaderExtensionObject: GUID;
|
|
25
|
-
static CodecListObject: GUID;
|
|
26
|
-
static ScriptCommandObject: GUID;
|
|
27
|
-
static MarkerObject: GUID;
|
|
28
|
-
static BitrateMutualExclusionObject: GUID;
|
|
29
|
-
static ErrorCorrectionObject: GUID;
|
|
30
|
-
static ContentDescriptionObject: GUID;
|
|
31
|
-
static ExtendedContentDescriptionObject: GUID;
|
|
32
|
-
static ContentBrandingObject: GUID;
|
|
33
|
-
static StreamBitratePropertiesObject: GUID;
|
|
34
|
-
static ContentEncryptionObject: GUID;
|
|
35
|
-
static ExtendedContentEncryptionObject: GUID;
|
|
36
|
-
static DigitalSignatureObject: GUID;
|
|
37
|
-
static PaddingObject: GUID;
|
|
38
|
-
static ExtendedStreamPropertiesObject: GUID;
|
|
39
|
-
static AdvancedMutualExclusionObject: GUID;
|
|
40
|
-
static GroupMutualExclusionObject: GUID;
|
|
41
|
-
static StreamPrioritizationObject: GUID;
|
|
42
|
-
static BandwidthSharingObject: GUID;
|
|
43
|
-
static LanguageListObject: GUID;
|
|
44
|
-
static MetadataObject: GUID;
|
|
45
|
-
static MetadataLibraryObject: GUID;
|
|
46
|
-
static IndexParametersObject: GUID;
|
|
47
|
-
static MediaObjectIndexParametersObject: GUID;
|
|
48
|
-
static TimecodeIndexParametersObject: GUID;
|
|
49
|
-
static CompatibilityObject: GUID;
|
|
50
|
-
static AdvancedContentEncryptionObject: GUID;
|
|
51
|
-
static AudioMedia: GUID;
|
|
52
|
-
static VideoMedia: GUID;
|
|
53
|
-
static CommandMedia: GUID;
|
|
54
|
-
static JFIF_Media: GUID;
|
|
55
|
-
static Degradable_JPEG_Media: GUID;
|
|
56
|
-
static FileTransferMedia: GUID;
|
|
57
|
-
static BinaryMedia: GUID;
|
|
58
|
-
static ASF_Index_Placeholder_Object: GUID;
|
|
59
|
-
static fromBin(bin: Uint8Array, offset?: number): GUID;
|
|
60
|
-
/**
|
|
61
|
-
* Decode GUID in format like "B503BF5F-2EA9-CF11-8EE3-00C00C205365"
|
|
62
|
-
* @param objectId Binary GUID
|
|
63
|
-
* @param offset Read offset in bytes, default 0
|
|
64
|
-
* @returns GUID as dashed hexadecimal representation
|
|
65
|
-
*/
|
|
66
|
-
static decode(objectId: Uint8Array, offset?: number): string;
|
|
67
|
-
/**
|
|
68
|
-
* Decode stream type
|
|
69
|
-
* @param mediaType Media type GUID
|
|
70
|
-
* @returns Media type
|
|
71
|
-
*/
|
|
72
|
-
static decodeMediaType(mediaType: GUID): 'audio' | 'video' | 'command' | 'degradable-jpeg' | 'file-transfer' | 'binary' | undefined;
|
|
73
|
-
/**
|
|
74
|
-
* Encode GUID
|
|
75
|
-
* @param guid GUID like: "B503BF5F-2EA9-CF11-8EE3-00C00C205365"
|
|
76
|
-
* @returns Encoded Binary GUID
|
|
77
|
-
*/
|
|
78
|
-
static encode(str: string): Uint8Array;
|
|
79
|
-
str: string;
|
|
80
|
-
constructor(str: string);
|
|
81
|
-
equals(guid: GUID): boolean;
|
|
82
|
-
toBin(): Uint8Array;
|
|
83
|
-
}
|