music-metadata 11.8.3 → 11.9.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.
@@ -137,5 +137,5 @@ export class ParserFactory {
137
137
  }
138
138
  function getExtension(fname) {
139
139
  const i = fname.lastIndexOf('.');
140
- return i === -1 ? '' : fname.slice(i);
140
+ return i === -1 ? '' : fname.substring(i);
141
141
  }
@@ -132,8 +132,8 @@ export class APEv2Parser extends BasicParser {
132
132
  const picData = new Uint8Array(tagItemHeader.size);
133
133
  await this.tokenizer.readBuffer(picData);
134
134
  zero = util.findZero(picData, 0, picData.length);
135
- const description = textDecode(picData.slice(0, zero), 'utf-8');
136
- const data = picData.slice(zero + 1);
135
+ const description = textDecode(picData.subarray(0, zero), 'utf-8');
136
+ const data = picData.subarray(zero + 1);
137
137
  await this.metadata.addTag(tagFormat, key, {
138
138
  description,
139
139
  data
@@ -210,7 +210,7 @@ export class ContentDescriptionObjectState extends State {
210
210
  if (length > 0) {
211
211
  const tagName = ContentDescriptionObjectState.contentDescTags[i];
212
212
  const end = pos + length;
213
- tags.push({ id: tagName, value: parseUnicodeAttr(buf.slice(off + pos, off + end)) });
213
+ tags.push({ id: tagName, value: parseUnicodeAttr(buf.subarray(off + pos, off + end)) });
214
214
  pos = end;
215
215
  }
216
216
  }
@@ -232,13 +232,13 @@ export class ExtendedContentDescriptionObjectState extends State {
232
232
  for (let i = 0; i < attrCount; i += 1) {
233
233
  const nameLen = view.getUint16(pos, true);
234
234
  pos += 2;
235
- const name = parseUnicodeAttr(buf.slice(off + pos, off + pos + nameLen));
235
+ const name = parseUnicodeAttr(buf.subarray(off + pos, off + pos + nameLen));
236
236
  pos += nameLen;
237
237
  const valueType = view.getUint16(pos, true);
238
238
  pos += 2;
239
239
  const valueLen = view.getUint16(pos, true);
240
240
  pos += 2;
241
- const value = buf.slice(off + pos, off + pos + valueLen);
241
+ const value = buf.subarray(off + pos, off + pos + valueLen);
242
242
  pos += valueLen;
243
243
  this.postProcessTag(tags, name, valueType, value);
244
244
  }
@@ -298,9 +298,9 @@ export class MetadataObjectState extends State {
298
298
  pos += 2;
299
299
  const dataLen = view.getUint32(pos, true);
300
300
  pos += 4;
301
- const name = parseUnicodeAttr(uint8Array.slice(off + pos, off + pos + nameLen));
301
+ const name = parseUnicodeAttr(uint8Array.subarray(off + pos, off + pos + nameLen));
302
302
  pos += nameLen;
303
- const data = uint8Array.slice(off + pos, off + pos + dataLen);
303
+ const data = uint8Array.subarray(off + pos, off + pos + dataLen);
304
304
  pos += dataLen;
305
305
  this.postProcessTag(tags, name, dataType, data);
306
306
  }
package/lib/asf/GUID.js CHANGED
@@ -25,7 +25,7 @@ class GUID {
25
25
  */
26
26
  static decode(objectId, offset = 0) {
27
27
  const view = new DataView(objectId.buffer, offset);
28
- const guid = `${view.getUint32(0, true).toString(16)}-${view.getUint16(4, true).toString(16)}-${view.getUint16(6, true).toString(16)}-${view.getUint16(8).toString(16)}-${uint8ArrayToHex(objectId.slice(offset + 10, offset + 16))}`;
28
+ const guid = `${view.getUint32(0, true).toString(16)}-${view.getUint16(4, true).toString(16)}-${view.getUint16(6, true).toString(16)}-${view.getUint16(8).toString(16)}-${uint8ArrayToHex(objectId.subarray(offset + 10, offset + 16))}`;
29
29
  return guid.toUpperCase();
30
30
  }
31
31
  /**
@@ -51,11 +51,11 @@ class GUID {
51
51
  static encode(str) {
52
52
  const bin = new Uint8Array(16);
53
53
  const view = new DataView(bin.buffer);
54
- view.setUint32(0, Number.parseInt(str.slice(0, 8), 16), true);
55
- view.setUint16(4, Number.parseInt(str.slice(9, 13), 16), true);
56
- view.setUint16(6, Number.parseInt(str.slice(14, 18), 16), true);
57
- bin.set(hexToUint8Array(str.slice(19, 23)), 8);
58
- bin.set(hexToUint8Array(str.slice(24)), 10);
54
+ view.setUint32(0, Number.parseInt(str.substring(0, 8), 16), true);
55
+ view.setUint16(4, Number.parseInt(str.substring(9, 13), 16), true);
56
+ view.setUint16(6, Number.parseInt(str.substring(14, 18), 16), true);
57
+ bin.set(hexToUint8Array(str.substring(19, 23)), 8);
58
+ bin.set(hexToUint8Array(str.substring(24)), 10);
59
59
  return bin;
60
60
  }
61
61
  constructor(str) {
@@ -9,7 +9,7 @@ const validFourCC = /^[\x21-\x7e©][\x20-\x7e\x00()]{3}/;
9
9
  export const FourCcToken = {
10
10
  len: 4,
11
11
  get: (buf, off) => {
12
- const id = textDecode(buf.slice(off, off + FourCcToken.len), 'latin1');
12
+ const id = textDecode(buf.subarray(off, off + FourCcToken.len), 'latin1');
13
13
  if (!id.match(validFourCC)) {
14
14
  throw new FieldDecodingError(`FourCC contains invalid characters: ${util.a2hex(id)} "${id}"`);
15
15
  }
@@ -4,7 +4,7 @@ export interface IGenericTag {
4
4
  id: keyof ICommonTagsResult;
5
5
  value: AnyTagValue;
6
6
  }
7
- export type GenericTagId = 'track' | 'disk' | 'year' | 'title' | 'artist' | 'artists' | 'albumartist' | 'album' | 'date' | 'originaldate' | 'originalyear' | 'releasedate' | 'comment' | 'genre' | 'picture' | 'composer' | 'lyrics' | 'albumsort' | 'titlesort' | 'work' | 'artistsort' | 'albumartistsort' | 'composersort' | 'lyricist' | 'writer' | 'conductor' | 'remixer' | 'arranger' | 'engineer' | 'technician' | 'producer' | 'djmixer' | 'mixer' | 'publisher' | 'label' | 'grouping' | 'subtitle' | 'discsubtitle' | 'totaltracks' | 'totaldiscs' | 'compilation' | 'rating' | 'bpm' | 'mood' | 'media' | 'catalognumber' | 'tvShow' | 'tvShowSort' | 'tvEpisode' | 'tvEpisodeId' | 'tvNetwork' | 'tvSeason' | 'podcast' | 'podcasturl' | 'releasestatus' | 'releasetype' | 'releasecountry' | 'script' | 'language' | 'copyright' | 'license' | 'encodedby' | 'encodersettings' | 'gapless' | 'barcode' | 'isrc' | 'asin' | 'musicbrainz_recordingid' | 'musicbrainz_trackid' | 'musicbrainz_albumid' | 'musicbrainz_artistid' | 'musicbrainz_albumartistid' | 'musicbrainz_releasegroupid' | 'musicbrainz_workid' | 'musicbrainz_trmid' | 'musicbrainz_discid' | 'acoustid_id' | 'acoustid_fingerprint' | 'musicip_puid' | 'musicip_fingerprint' | 'website' | 'performer:instrument' | 'peakLevel' | 'averageLevel' | 'notes' | 'key' | 'originalalbum' | 'originalartist' | 'discogs_artist_id' | 'discogs_label_id' | 'discogs_master_release_id' | 'discogs_rating' | 'discogs_release_id' | 'discogs_votes' | 'replaygain_track_gain' | 'replaygain_track_peak' | 'replaygain_album_gain' | 'replaygain_album_peak' | 'replaygain_track_minmax' | 'replaygain_album_minmax' | 'replaygain_undo' | 'description' | 'longDescription' | 'category' | 'hdVideo' | 'keywords' | 'movement' | 'movementIndex' | 'movementTotal' | 'podcastId' | 'showMovement' | 'stik';
7
+ export type GenericTagId = 'track' | 'disk' | 'year' | 'title' | 'artist' | 'artists' | 'albumartist' | 'album' | 'date' | 'originaldate' | 'originalyear' | 'releasedate' | 'comment' | 'genre' | 'picture' | 'composer' | 'lyrics' | 'albumsort' | 'titlesort' | 'work' | 'artistsort' | 'albumartistsort' | 'composersort' | 'lyricist' | 'writer' | 'conductor' | 'remixer' | 'arranger' | 'engineer' | 'technician' | 'producer' | 'djmixer' | 'mixer' | 'publisher' | 'label' | 'grouping' | 'subtitle' | 'discsubtitle' | 'totaltracks' | 'totaldiscs' | 'compilation' | 'rating' | 'bpm' | 'mood' | 'media' | 'catalognumber' | 'tvShow' | 'tvShowSort' | 'tvEpisode' | 'tvEpisodeId' | 'tvNetwork' | 'tvSeason' | 'podcast' | 'podcasturl' | 'releasestatus' | 'releasetype' | 'releasecountry' | 'script' | 'language' | 'copyright' | 'license' | 'encodedby' | 'encodersettings' | 'gapless' | 'barcode' | 'isrc' | 'asin' | 'musicbrainz_recordingid' | 'musicbrainz_trackid' | 'musicbrainz_albumid' | 'musicbrainz_artistid' | 'musicbrainz_albumartistid' | 'musicbrainz_releasegroupid' | 'musicbrainz_workid' | 'musicbrainz_trmid' | 'musicbrainz_discid' | 'acoustid_id' | 'acoustid_fingerprint' | 'musicip_puid' | 'musicip_fingerprint' | 'website' | 'performer:instrument' | 'peakLevel' | 'averageLevel' | 'notes' | 'key' | 'originalalbum' | 'originalartist' | 'discogs_artist_id' | 'discogs_label_id' | 'discogs_master_release_id' | 'discogs_rating' | 'discogs_release_id' | 'discogs_votes' | 'replaygain_track_gain' | 'replaygain_track_peak' | 'replaygain_album_gain' | 'replaygain_album_peak' | 'replaygain_track_minmax' | 'replaygain_album_minmax' | 'replaygain_undo' | 'description' | 'longDescription' | 'category' | 'hdVideo' | 'keywords' | 'movement' | 'movementIndex' | 'movementTotal' | 'podcastId' | 'showMovement' | 'stik' | 'playCounter';
8
8
  export interface INativeTagMap {
9
9
  [index: string]: GenericTagId;
10
10
  }
@@ -112,7 +112,8 @@ const commonTags = {
112
112
  movementTotal: defaultTagInfo,
113
113
  podcastId: defaultTagInfo,
114
114
  showMovement: defaultTagInfo,
115
- stik: defaultTagInfo
115
+ stik: defaultTagInfo,
116
+ playCounter: defaultTagInfo
116
117
  };
117
118
  export const commonTagsKeys = /* @__PURE__ */ Object.keys(commonTags);
118
119
  /**
@@ -51,3 +51,8 @@ export declare function dbToRatio(dB: number): number;
51
51
  * @param value string holding a ratio like '0.034' or '-7.54 dB'
52
52
  */
53
53
  export declare function toRatio(value: string): IRatio | undefined;
54
+ /**
55
+ * Decode a big-endian unsigned integer from a Uint8Array.
56
+ * Supports dynamic length (1–8 bytes).
57
+ */
58
+ export declare function decodeUintBE(uint8Array: Uint8Array): number;
@@ -1,5 +1,6 @@
1
1
  import { StringType } from 'token-types';
2
2
  import { FieldDecodingError } from '../ParseError.js';
3
+ import { getUintBE } from 'uint8array-extras';
3
4
  export function getBit(buf, off, bit) {
4
5
  return (buf[off] & (1 << bit)) !== 0;
5
6
  }
@@ -140,3 +141,14 @@ export function toRatio(value) {
140
141
  };
141
142
  }
142
143
  }
144
+ /**
145
+ * Decode a big-endian unsigned integer from a Uint8Array.
146
+ * Supports dynamic length (1–8 bytes).
147
+ */
148
+ export function decodeUintBE(uint8Array) {
149
+ if (uint8Array.length === 0) {
150
+ throw new Error("decodeUintBE: empty Uint8Array");
151
+ }
152
+ const view = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
153
+ return getUintBE(view);
154
+ }
@@ -96,13 +96,21 @@ export class FlacParser extends AbstractID3Parser {
96
96
  */
97
97
  async parseComment(data) {
98
98
  const decoder = new VorbisDecoder(data, 0);
99
- decoder.readStringUtf8(); // vendor (skip)
99
+ const vendor = decoder.readStringUtf8();
100
+ if (vendor.length > 0) {
101
+ this.metadata.setFormat('tool', vendor);
102
+ }
100
103
  const commentListLength = decoder.readInt32();
101
104
  const tags = new Array(commentListLength);
102
105
  for (let i = 0; i < commentListLength; i++) {
103
106
  tags[i] = decoder.parseUserComment();
104
107
  }
105
- await Promise.all(tags.map(tag => this.addTag(tag.key, tag.value)));
108
+ await Promise.all(tags.map(tag => {
109
+ if (tag.key === 'ENCODER') {
110
+ this.metadata.setFormat('tool', tag.value);
111
+ }
112
+ return this.addTag(tag.key, tag.value);
113
+ }));
106
114
  }
107
115
  async parsePicture(dataLen) {
108
116
  if (this.options.skipCovers) {
@@ -4,6 +4,7 @@ import * as util from '../common/Util.js';
4
4
  import { AttachedPictureType, SyncTextHeader, TextEncodingToken, TextHeader } from './ID3v2Token.js';
5
5
  import { Genres } from '../id3v1/ID3v1Parser.js';
6
6
  import { makeUnexpectedFileContentError } from '../ParseError.js';
7
+ import { decodeUintBE } from '../common/Util.js';
7
8
  const debug = initDebug('music-metadata:id3v2:frame-parser');
8
9
  const defaultEnc = 'latin1'; // latin1 == iso-8859-1;
9
10
  export function parseGenre(origVal) {
@@ -89,7 +90,7 @@ export class FrameParser {
89
90
  case 'PCST': {
90
91
  let text;
91
92
  try {
92
- text = util.decodeString(uint8Array.slice(1), encoding).replace(/\x00+$/, '');
93
+ text = util.decodeString(uint8Array.subarray(1), encoding).replace(/\x00+$/, '');
93
94
  }
94
95
  catch (error) {
95
96
  if (error instanceof Error) {
@@ -149,13 +150,13 @@ export class FrameParser {
149
150
  offset += 1;
150
151
  switch (this.major) {
151
152
  case 2:
152
- pic.format = util.decodeString(uint8Array.slice(offset, offset + 3), 'latin1'); // 'latin1'; // latin1 == iso-8859-1;
153
+ pic.format = util.decodeString(uint8Array.subarray(offset, offset + 3), 'latin1'); // 'latin1'; // latin1 == iso-8859-1;
153
154
  offset += 3;
154
155
  break;
155
156
  case 3:
156
157
  case 4:
157
158
  fzero = util.findZero(uint8Array, offset, length, defaultEnc);
158
- pic.format = util.decodeString(uint8Array.slice(offset, fzero), defaultEnc);
159
+ pic.format = util.decodeString(uint8Array.subarray(offset, fzero), defaultEnc);
159
160
  offset = fzero + 1;
160
161
  break;
161
162
  default:
@@ -165,15 +166,15 @@ export class FrameParser {
165
166
  pic.type = AttachedPictureType[uint8Array[offset]];
166
167
  offset += 1;
167
168
  fzero = util.findZero(uint8Array, offset, length, encoding);
168
- pic.description = util.decodeString(uint8Array.slice(offset, fzero), encoding);
169
+ pic.description = util.decodeString(uint8Array.subarray(offset, fzero), encoding);
169
170
  offset = fzero + nullTerminatorLength;
170
- pic.data = uint8Array.slice(offset, length);
171
+ pic.data = uint8Array.subarray(offset, length);
171
172
  output = pic;
172
173
  }
173
174
  break;
174
175
  case 'CNT':
175
176
  case 'PCNT':
176
- output = Token.UINT32_BE.get(uint8Array, 0);
177
+ output = decodeUintBE(uint8Array);
177
178
  break;
178
179
  case 'SYLT': {
179
180
  const syltHeader = SyncTextHeader.get(uint8Array, 0);
@@ -234,31 +235,31 @@ export class FrameParser {
234
235
  }
235
236
  case 'POPM': { // Popularimeter
236
237
  fzero = util.findZero(uint8Array, offset, length, defaultEnc);
237
- const email = util.decodeString(uint8Array.slice(offset, fzero), defaultEnc);
238
+ const email = util.decodeString(uint8Array.subarray(offset, fzero), defaultEnc);
238
239
  offset = fzero + 1;
239
- const dataLen = length - offset;
240
+ const valueLen = length - offset - 1;
240
241
  output = {
241
242
  email,
242
243
  rating: Token.UINT8.get(uint8Array, offset),
243
- counter: dataLen >= 5 ? Token.UINT32_BE.get(uint8Array, offset + 1) : undefined
244
+ counter: valueLen > 0 ? util.decodeUintBE(uint8Array.subarray(offset + 1)) : undefined
244
245
  };
245
246
  break;
246
247
  }
247
248
  case 'GEOB': { // General encapsulated object
248
249
  fzero = util.findZero(uint8Array, offset + 1, length, encoding);
249
- const mimeType = util.decodeString(uint8Array.slice(offset + 1, fzero), defaultEnc);
250
+ const mimeType = util.decodeString(uint8Array.subarray(offset + 1, fzero), defaultEnc);
250
251
  offset = fzero + 1;
251
252
  fzero = util.findZero(uint8Array, offset, length, encoding);
252
- const filename = util.decodeString(uint8Array.slice(offset, fzero), defaultEnc);
253
+ const filename = util.decodeString(uint8Array.subarray(offset, fzero), defaultEnc);
253
254
  offset = fzero + 1;
254
255
  fzero = util.findZero(uint8Array, offset, length, encoding);
255
- const description = util.decodeString(uint8Array.slice(offset, fzero), defaultEnc);
256
+ const description = util.decodeString(uint8Array.subarray(offset, fzero), defaultEnc);
256
257
  offset = fzero + 1;
257
258
  const geob = {
258
259
  type: mimeType,
259
260
  filename,
260
261
  description,
261
- data: uint8Array.slice(offset, length)
262
+ data: uint8Array.subarray(offset, length)
262
263
  };
263
264
  output = geob;
264
265
  break;
@@ -274,23 +275,23 @@ export class FrameParser {
274
275
  case 'WPUB':
275
276
  // Decode URL
276
277
  fzero = util.findZero(uint8Array, offset + 1, length, encoding);
277
- output = util.decodeString(uint8Array.slice(offset, fzero), defaultEnc);
278
+ output = util.decodeString(uint8Array.subarray(offset, fzero), defaultEnc);
278
279
  break;
279
280
  case 'WXXX': {
280
281
  // Decode URL
281
282
  fzero = util.findZero(uint8Array, offset + 1, length, encoding);
282
- const description = util.decodeString(uint8Array.slice(offset + 1, fzero), encoding);
283
+ const description = util.decodeString(uint8Array.subarray(offset + 1, fzero), encoding);
283
284
  offset = fzero + (encoding === 'utf-16le' ? 2 : 1);
284
- output = { description, url: util.decodeString(uint8Array.slice(offset, length), defaultEnc) };
285
+ output = { description, url: util.decodeString(uint8Array.subarray(offset, length), defaultEnc) };
285
286
  break;
286
287
  }
287
288
  case 'WFD':
288
289
  case 'WFED':
289
- output = util.decodeString(uint8Array.slice(offset + 1, util.findZero(uint8Array, offset + 1, length, encoding)), encoding);
290
+ output = util.decodeString(uint8Array.subarray(offset + 1, util.findZero(uint8Array, offset + 1, length, encoding)), encoding);
290
291
  break;
291
292
  case 'MCDI': {
292
293
  // Music CD identifier
293
- output = uint8Array.slice(0, length);
294
+ output = uint8Array.subarray(0, length);
294
295
  break;
295
296
  }
296
297
  default:
@@ -302,7 +303,7 @@ export class FrameParser {
302
303
  static readNullTerminatedString(uint8Array, encoding) {
303
304
  let offset = encoding.bom ? 2 : 0;
304
305
  const zeroIndex = util.findZero(uint8Array, offset, uint8Array.length, encoding.encoding);
305
- const txt = uint8Array.slice(offset, zeroIndex);
306
+ const txt = uint8Array.subarray(offset, zeroIndex);
306
307
  if (encoding.encoding === 'utf-16le') {
307
308
  offset = zeroIndex + 2;
308
309
  }
@@ -364,9 +365,9 @@ export class FrameParser {
364
365
  }
365
366
  static readIdentifierAndData(uint8Array, offset, length, encoding) {
366
367
  const fzero = util.findZero(uint8Array, offset, length, encoding);
367
- const id = util.decodeString(uint8Array.slice(offset, fzero), encoding);
368
+ const id = util.decodeString(uint8Array.subarray(offset, fzero), encoding);
368
369
  offset = fzero + FrameParser.getNullTerminatorLength(encoding);
369
- return { id, data: uint8Array.slice(offset, length) };
370
+ return { id, data: uint8Array.subarray(offset, length) };
370
371
  }
371
372
  static getNullTerminatorLength(enc) {
372
373
  return enc === 'utf-16le' ? 2 : 1;
@@ -137,7 +137,8 @@ const id3v24TagMap = {
137
137
  TGID: 'podcastId',
138
138
  TKWD: 'keywords',
139
139
  WFED: 'podcasturl',
140
- GRP1: 'grouping'
140
+ GRP1: 'grouping',
141
+ PCNT: 'playCounter',
141
142
  };
142
143
  export class ID3v24TagMapper extends CaseInsensitiveTagMap {
143
144
  static toRating(popm) {
@@ -24,7 +24,7 @@ export class ID3v2Parser {
24
24
  if (readI < buffer.length) {
25
25
  buffer[writeI++] = buffer[readI];
26
26
  }
27
- return buffer.slice(0, writeI);
27
+ return buffer.subarray(0, writeI);
28
28
  }
29
29
  static getFrameHeaderLength(majorVer) {
30
30
  switch (majorVer) {
@@ -64,7 +64,7 @@ export class ID3v2Parser {
64
64
  uint8Array = ID3v2Parser.removeUnsyncBytes(uint8Array);
65
65
  }
66
66
  if (frameHeader.flags?.format.data_length_indicator) {
67
- uint8Array = uint8Array.slice(4, uint8Array.length);
67
+ uint8Array = uint8Array.subarray(4, uint8Array.length);
68
68
  }
69
69
  return frameParser.readData(uint8Array, frameHeader.id, includeCovers);
70
70
  default:
@@ -132,10 +132,10 @@ export class ID3v2Parser {
132
132
  this.metadata.addWarning('Illegal ID3v2 tag length');
133
133
  break;
134
134
  }
135
- const frameHeaderBytes = data.slice(offset, offset + frameHeaderLength);
135
+ const frameHeaderBytes = data.subarray(offset, offset + frameHeaderLength);
136
136
  offset += frameHeaderLength;
137
137
  const frameHeader = this.readFrameHeader(frameHeaderBytes, this.id3Header.version.major);
138
- const frameDataBytes = data.slice(offset, offset + frameHeader.length);
138
+ const frameDataBytes = data.subarray(offset, offset + frameHeader.length);
139
139
  offset += frameHeader.length;
140
140
  const values = ID3v2Parser.readFrameData(frameDataBytes, frameHeader, this.id3Header.version.major, !this.options.skipCovers, this.metadata);
141
141
  if (values) {
@@ -149,7 +149,7 @@ export class ID3v2Parser {
149
149
  switch (majorVer) {
150
150
  case 2:
151
151
  header = {
152
- id: textDecode(uint8Array.slice(0, 3), 'ascii'),
152
+ id: textDecode(uint8Array.subarray(0, 3), 'ascii'),
153
153
  length: Token.UINT24_BE.get(uint8Array, 3)
154
154
  };
155
155
  if (!header.id.match(/[A-Z0-9]{3}/g)) {
@@ -159,9 +159,9 @@ export class ID3v2Parser {
159
159
  case 3:
160
160
  case 4:
161
161
  header = {
162
- id: textDecode(uint8Array.slice(0, 4), 'ascii'),
162
+ id: textDecode(uint8Array.subarray(0, 4), 'ascii'),
163
163
  length: (majorVer === 4 ? UINT32SYNCSAFE : Token.UINT32_BE).get(uint8Array, 4),
164
- flags: ID3v2Parser.readFrameFlags(uint8Array.slice(8, 10))
164
+ flags: ID3v2Parser.readFrameFlags(uint8Array.subarray(8, 10))
165
165
  };
166
166
  if (!header.id.match(/[A-Z0-9]{4}/g)) {
167
167
  this.metadata.addWarning(`Invalid ID3v2.${this.id3Header.version.major} frame-header-ID: ${header.id}`);
@@ -8,9 +8,9 @@ export async function getLyricsHeaderLength(tokenizer) {
8
8
  await tokenizer.readBuffer(buf, { position: fileSize - 143 });
9
9
  tokenizer.setPosition(position); // Restore position
10
10
  const txt = textDecode(buf, 'latin1');
11
- const tag = txt.slice(6);
11
+ const tag = txt.substring(6);
12
12
  if (tag === endTag2) {
13
- return Number.parseInt(txt.slice(0, 6), 10) + 15;
13
+ return Number.parseInt(txt.substring(0, 6), 10) + 15;
14
14
  }
15
15
  }
16
16
  return 0;
@@ -406,7 +406,7 @@ export class MpegParser extends AbstractID3Parser {
406
406
  await this.skipSideInformation();
407
407
  return false;
408
408
  }
409
- if (this.frameCount === 3) {
409
+ if (this.frameCount === 4) {
410
410
  // the stream is CBR if the first 3 frame bitrates are the same
411
411
  if (this.areAllSame(this.bitrates)) {
412
412
  // Actual calculation will be done in finalize
@@ -29,7 +29,7 @@ class OggStream {
29
29
  debug('firstPage=%s, lastPage=%s, continued=%s', header.headerType.firstPage, header.headerType.lastPage, header.headerType.continued);
30
30
  if (header.headerType.firstPage) {
31
31
  this.metadata.setFormat('container', 'Ogg');
32
- const idData = pageData.slice(0, 7); // Copy this portion
32
+ const idData = pageData.subarray(0, 7); // Copy this portion
33
33
  const asciiId = Array.from(idData)
34
34
  .filter(b => b >= 32 && b <= 126) // Keep only printable ASCII
35
35
  .map(b => String.fromCharCode(b))
@@ -38,7 +38,7 @@ export class VorbisPictureToken {
38
38
  offset += 4;
39
39
  const picDataLen = Token.UINT32_BE.get(buffer, offset);
40
40
  offset += 4;
41
- const data = Uint8Array.from(buffer.slice(offset, offset + picDataLen));
41
+ const data = buffer.slice(offset, offset + picDataLen);
42
42
  return {
43
43
  type,
44
44
  format,
@@ -21,8 +21,8 @@ export class VorbisDecoder {
21
21
  const v = this.readStringUtf8();
22
22
  const idx = v.indexOf('=');
23
23
  return {
24
- key: v.slice(0, idx).toUpperCase(),
25
- value: v.slice(idx + 1),
24
+ key: v.substring(0, idx).toUpperCase(),
25
+ value: v.substring(idx + 1),
26
26
  len: this.offset - offset0
27
27
  };
28
28
  }
package/lib/type.d.ts CHANGED
@@ -359,6 +359,7 @@ export interface ICommonTagsResult {
359
359
  * https://github.com/sergiomb2/libmp4v2/wiki/iTunesMetadata#user-content-media-type-stik
360
360
  */
361
361
  stik?: number;
362
+ playCounter?: number;
362
363
  }
363
364
  export interface IRatio {
364
365
  /**
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.8.3",
4
+ "version": "11.9.0",
5
5
  "author": {
6
6
  "name": "Borewit",
7
7
  "url": "https://github.com/Borewit"
@@ -109,28 +109,28 @@
109
109
  "@borewit/text-codec": "^0.2.0",
110
110
  "@tokenizer/token": "^0.3.0",
111
111
  "content-type": "^1.0.5",
112
- "debug": "^4.4.1",
112
+ "debug": "^4.4.3",
113
113
  "file-type": "^21.0.0",
114
114
  "media-typer": "^1.1.0",
115
115
  "strtok3": "^10.3.4",
116
116
  "token-types": "^6.1.1",
117
- "uint8array-extras": "^1.4.1"
117
+ "uint8array-extras": "^1.5.0"
118
118
  },
119
119
  "devDependencies": {
120
- "@biomejs/biome": "2.2.0",
120
+ "@biomejs/biome": "2.2.4",
121
121
  "@types/chai": "^5.2.2",
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.2.1",
127
+ "@types/node": "^24.3.1",
128
128
  "c8": "^10.1.3",
129
- "chai": "^5.2.1",
130
- "chai-as-promised": "^8.0.1",
129
+ "chai": "^6.0.1",
130
+ "chai-as-promised": "^8.0.2",
131
131
  "del-cli": "^6.0.0",
132
- "mime": "^4.0.7",
133
- "mocha": "^11.7.1",
132
+ "mime": "^4.1.0",
133
+ "mocha": "^11.7.2",
134
134
  "node-readable-to-web-readable-stream": "^0.4.2",
135
135
  "remark-cli": "^12.0.1",
136
136
  "remark-preset-lint-consistent": "^6.0.1",