music-metadata 11.10.3 → 11.10.5

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/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright © 2025 Borewit
3
+ Copyright © 2026 Borewit
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
6
 
@@ -113,7 +113,7 @@ export class APEv2Parser extends BasicParser {
113
113
  const tagItemHeader = await this.tokenizer.readToken(TagItemHeader);
114
114
  bytesRemaining -= TagItemHeader.len + tagItemHeader.size;
115
115
  await this.tokenizer.peekBuffer(keyBuffer, { length: Math.min(keyBuffer.length, bytesRemaining) });
116
- let zero = util.findZero(keyBuffer, 0, keyBuffer.length);
116
+ let zero = util.findZero(keyBuffer);
117
117
  const key = await this.tokenizer.readToken(new StringType(zero, 'ascii'));
118
118
  await this.tokenizer.ignore(1);
119
119
  bytesRemaining -= key.length + 1;
@@ -131,7 +131,7 @@ export class APEv2Parser extends BasicParser {
131
131
  else {
132
132
  const picData = new Uint8Array(tagItemHeader.size);
133
133
  await this.tokenizer.readBuffer(picData);
134
- zero = util.findZero(picData, 0, picData.length);
134
+ zero = util.findZero(picData);
135
135
  const description = textDecode(picData.subarray(0, zero), 'utf-8');
136
136
  const data = picData.subarray(zero + 1);
137
137
  await this.metadata.addTag(tagFormat, key, {
@@ -2,14 +2,12 @@ import type { IRatio } from '../type.js';
2
2
  export type StringEncoding = 'ascii' | 'utf8' | 'utf-16le' | 'ucs2' | 'base64url' | 'latin1' | 'hex';
3
3
  export declare function getBit(buf: Uint8Array, off: number, bit: number): boolean;
4
4
  /**
5
- * Found delimiting zero in uint8Array
5
+ * Find delimiting zero in uint8Array
6
6
  * @param uint8Array Uint8Array to find the zero delimiter in
7
- * @param start Offset in uint8Array
8
- * @param end Last position to parse in uint8Array
9
7
  * @param encoding The string encoding used
10
- * @return Absolute position on uint8Array where zero found
8
+ * @return position in uint8Array where zero found, or uint8Array.length if not found
11
9
  */
12
- export declare function findZero(uint8Array: Uint8Array, start: number, end: number, encoding?: StringEncoding): number;
10
+ export declare function findZero(uint8Array: Uint8Array, encoding?: StringEncoding): number;
13
11
  export declare function trimRightNull(x: string): string;
14
12
  /**
15
13
  * Decode string
@@ -5,33 +5,31 @@ export function getBit(buf, off, bit) {
5
5
  return (buf[off] & (1 << bit)) !== 0;
6
6
  }
7
7
  /**
8
- * Found delimiting zero in uint8Array
8
+ * Find delimiting zero in uint8Array
9
9
  * @param uint8Array Uint8Array to find the zero delimiter in
10
- * @param start Offset in uint8Array
11
- * @param end Last position to parse in uint8Array
12
10
  * @param encoding The string encoding used
13
- * @return Absolute position on uint8Array where zero found
11
+ * @return position in uint8Array where zero found, or uint8Array.length if not found
14
12
  */
15
- export function findZero(uint8Array, start, end, encoding) {
16
- let i = start;
13
+ export function findZero(uint8Array, encoding) {
14
+ const len = uint8Array.length;
17
15
  if (encoding === 'utf-16le') {
18
- while (uint8Array[i] !== 0 || uint8Array[i + 1] !== 0) {
19
- if (i >= end)
20
- return end;
21
- i += 2;
16
+ // Look for 0x00 0x00 on 2-byte boundary
17
+ for (let i = 0; i + 1 < len; i += 2) {
18
+ if (uint8Array[i] === 0 && uint8Array[i + 1] === 0)
19
+ return i;
22
20
  }
23
- return i;
21
+ return len;
24
22
  }
25
- while (uint8Array[i] !== 0) {
26
- if (i >= end)
27
- return end;
28
- i++;
23
+ // latin1 / utf8 / utf16be (caller typically handles utf16be separately or via decode)
24
+ for (let i = 0; i < len; i++) {
25
+ if (uint8Array[i] === 0)
26
+ return i;
29
27
  }
30
- return i;
28
+ return len;
31
29
  }
32
30
  export function trimRightNull(x) {
33
31
  const pos0 = x.indexOf('\0');
34
- return pos0 === -1 ? x : x.substr(0, pos0);
32
+ return pos0 === -1 ? x : x.substring(0, pos0);
35
33
  }
36
34
  function swapBytes(uint8Array) {
37
35
  const l = uint8Array.length;
@@ -0,0 +1,31 @@
1
+ import { type ID3v2MajorVersion } from './ID3v2Token.js';
2
+ import type { IWarningCollector } from '../common/MetadataCollector.js';
3
+ export interface IFrameFlags {
4
+ status: {
5
+ tag_alter_preservation: boolean;
6
+ file_alter_preservation: boolean;
7
+ read_only: boolean;
8
+ };
9
+ format: {
10
+ grouping_identity: boolean;
11
+ compression: boolean;
12
+ encryption: boolean;
13
+ unsynchronisation: boolean;
14
+ data_length_indicator: boolean;
15
+ };
16
+ }
17
+ export interface IFrameHeader {
18
+ id: string;
19
+ length: number;
20
+ flags?: IFrameFlags;
21
+ }
22
+ /**
23
+ * Frame header length (bytes) depending on ID3v2 major version.
24
+ */
25
+ export declare function getFrameHeaderLength(majorVer: number): 6 | 10;
26
+ /**
27
+ * Factory: parse a frame header from its header bytes (6 for v2.2, 10 for v2.3/v2.4).
28
+ *
29
+ * Note: It only *parses* and does light validation. It does not read payload bytes.
30
+ */
31
+ export declare function readFrameHeader(uint8Array: Uint8Array, majorVer: ID3v2MajorVersion, warningCollector: IWarningCollector): IFrameHeader;
@@ -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
+ }
@@ -54,6 +54,7 @@ export declare class FrameParser {
54
54
  */
55
55
  private splitValue;
56
56
  private static trimArray;
57
+ private static trimNullPadding;
57
58
  private static readIdentifierAndData;
58
59
  private static getNullTerminatorLength;
59
60
  }
@@ -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).replace(/\x00+$/, '');
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, offset + 1, length, encoding);
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
- offset += 1;
151
+ uint8Array = uint8Array.subarray(1);
151
152
  switch (this.major) {
152
153
  case 2:
153
- pic.format = util.decodeString(uint8Array.subarray(offset, offset + 3), 'latin1'); // 'latin1'; // latin1 == iso-8859-1;
154
- offset += 3;
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, offset, length, defaultEnc);
159
- pic.format = util.decodeString(uint8Array.subarray(offset, fzero), defaultEnc);
160
- offset = fzero + 1;
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[offset]];
167
- offset += 1;
168
- fzero = util.findZero(uint8Array, offset, length, encoding);
169
- pic.description = util.decodeString(uint8Array.subarray(offset, fzero), encoding);
170
- offset = fzero + nullTerminatorLength;
171
- pic.data = uint8Array.subarray(offset, length);
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
- offset += SyncTextHeader.len;
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 (offset < length) {
191
- const nullStr = FrameParser.readNullTerminatedString(uint8Array.subarray(offset), syltHeader.encoding);
192
- offset += nullStr.len;
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, offset);
195
- offset += Token.UINT32_BE.len;
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, offset, length, defaultEnc);
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, offset, length, defaultEnc);
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
- fzero = util.findZero(uint8Array, offset, length, defaultEnc);
238
- const email = util.decodeString(uint8Array.subarray(offset, fzero), defaultEnc);
239
- offset = fzero + 1;
240
- const valueLen = length - offset - 1;
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: Token.UINT8.get(uint8Array, offset),
244
- counter: valueLen > 0 ? util.decodeUintBE(uint8Array.subarray(offset + 1)) : undefined
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
- fzero = util.findZero(uint8Array, offset + 1, length, encoding);
250
- const mimeType = util.decodeString(uint8Array.subarray(offset + 1, fzero), defaultEnc);
251
- offset = fzero + 1;
252
- fzero = util.findZero(uint8Array, offset, length, encoding);
253
- const filename = util.decodeString(uint8Array.subarray(offset, fzero), defaultEnc);
254
- offset = fzero + 1;
255
- fzero = util.findZero(uint8Array, offset, length, encoding);
256
- const description = util.decodeString(uint8Array.subarray(offset, fzero), defaultEnc);
257
- offset = fzero + 1;
257
+ // [encoding] <MIME> 0x00 <filename> 0x00/0x00 0x00 <description> 0x00/0x00 0x00 <data>
258
+ const { encoding: geobEncoding, bom: geobBom } = 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: geobEncoding, bom: geobBom });
264
+ const filename = filenameStr.text;
265
+ uint8Array = uint8Array.subarray(filenameStr.len);
266
+ const descriptionStr = FrameParser.readNullTerminatedString(uint8Array, { encoding: geobEncoding, bom: geobBom });
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.subarray(offset, length)
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
- fzero = util.findZero(uint8Array, offset + 1, length, encoding);
278
- output = util.decodeString(uint8Array.subarray(offset, fzero), defaultEnc);
288
+ output = FrameParser.readNullTerminatedString(uint8Array, urlEnc).text;
279
289
  break;
280
290
  case 'WXXX': {
281
- // Decode URL
282
- fzero = util.findZero(uint8Array, offset + 1, length, encoding);
283
- const description = util.decodeString(uint8Array.subarray(offset + 1, fzero), encoding);
284
- offset = fzero + (encoding === 'utf-16le' ? 2 : 1);
285
- output = { description, url: util.decodeString(uint8Array.subarray(offset, length), defaultEnc) };
291
+ // [encoding] <description> 0x00/0x00 0x00 <url>
292
+ const { encoding: wxxxEncoding, bom: wxxxBom } = TextEncodingToken.get(uint8Array, 0);
293
+ uint8Array = uint8Array.subarray(1);
294
+ const descriptionStr = FrameParser.readNullTerminatedString(uint8Array, { encoding: wxxxEncoding, bom: wxxxBom });
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
- output = util.decodeString(uint8Array.subarray(offset + 1, util.findZero(uint8Array, offset + 1, length, encoding)), encoding);
302
+ case 'WFED': {
303
+ const { encoding: wfdEncoding, bom: wfdBom } = TextEncodingToken.get(uint8Array, 0);
304
+ uint8Array = uint8Array.subarray(1);
305
+ output = FrameParser.readNullTerminatedString(uint8Array, { encoding: wfdEncoding, bom: wfdBom }).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
- let offset = encoding.bom ? 2 : 0;
305
- const zeroIndex = util.findZero(uint8Array, offset, uint8Array.length, encoding.encoding);
306
- const txt = uint8Array.subarray(offset, zeroIndex);
307
- if (encoding.encoding === 'utf-16le') {
308
- offset = zeroIndex + 2;
309
- }
310
- else {
311
- offset = zeroIndex + 1;
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: offset
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 => value.replace(/\x00+$/, '').trim());
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, offset, length, encoding) {
367
- const fzero = util.findZero(uint8Array, offset, length, encoding);
368
- const id = util.decodeString(uint8Array.subarray(offset, fzero), encoding);
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 === 'utf-16le' ? 2 : 1;
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
  }
@@ -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, UINT32SYNCSAFE } from './ID3v2Token.js';
5
- import { textDecode } from '@borewit/text-codec';
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 = ID3v2Parser.getFrameHeaderLength(this.id3Header.version.major);
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 = this.readFrameHeader(frameHeaderBytes, this.id3Header.version.major);
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/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.3",
4
+ "version": "11.10.5",
5
5
  "author": {
6
6
  "name": "Borewit",
7
7
  "url": "https://github.com/Borewit"
@@ -106,27 +106,27 @@
106
106
  "update-biome": "yarn add -D --exact @biomejs/biome && npx @biomejs/biome migrate --write"
107
107
  },
108
108
  "dependencies": {
109
- "@borewit/text-codec": "^0.2.0",
109
+ "@borewit/text-codec": "^0.2.1",
110
110
  "@tokenizer/token": "^0.3.0",
111
111
  "content-type": "^1.0.5",
112
112
  "debug": "^4.4.3",
113
- "file-type": "^21.1.1",
113
+ "file-type": "^21.2.0",
114
114
  "media-typer": "^1.1.0",
115
115
  "strtok3": "^10.3.4",
116
- "token-types": "^6.1.1",
116
+ "token-types": "^6.1.2",
117
117
  "uint8array-extras": "^1.5.0"
118
118
  },
119
119
  "devDependencies": {
120
- "@biomejs/biome": "2.3.7",
121
- "@types/chai": "^5.2.2",
120
+ "@biomejs/biome": "2.3.10",
121
+ "@types/chai": "^5.2.3",
122
122
  "@types/chai-as-promised": "^8.0.2",
123
123
  "@types/content-type": "^1.1.9",
124
124
  "@types/debug": "^4.1.12",
125
125
  "@types/media-typer": "^1.1.3",
126
126
  "@types/mocha": "^10.0.10",
127
- "@types/node": "^24.5.0",
127
+ "@types/node": "^25.0.3",
128
128
  "c8": "^10.1.3",
129
- "chai": "^6.2.1",
129
+ "chai": "^6.2.2",
130
130
  "chai-as-promised": "^8.0.2",
131
131
  "del-cli": "^7.0.0",
132
132
  "mime": "^4.1.0",