music-metadata 8.2.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +152 -132
  2. package/lib/ParserFactory.d.ts +1 -1
  3. package/lib/ParserFactory.js +1 -2
  4. package/lib/aiff/AiffParser.js +3 -4
  5. package/lib/aiff/AiffToken.d.ts +2 -4
  6. package/lib/aiff/AiffToken.js +7 -7
  7. package/lib/apev2/APEv2Parser.d.ts +1 -1
  8. package/lib/apev2/APEv2Parser.js +10 -12
  9. package/lib/apev2/APEv2TagMapper.js +1 -1
  10. package/lib/apev2/APEv2Token.d.ts +1 -1
  11. package/lib/asf/AsfObject.d.ts +15 -17
  12. package/lib/asf/AsfObject.js +52 -46
  13. package/lib/asf/AsfParser.js +6 -8
  14. package/lib/asf/AsfTagMapper.js +1 -1
  15. package/lib/asf/AsfUtil.d.ts +1 -3
  16. package/lib/asf/AsfUtil.js +4 -5
  17. package/lib/asf/GUID.d.ts +4 -5
  18. package/lib/asf/GUID.js +14 -11
  19. package/lib/common/BasicParser.d.ts +1 -1
  20. package/lib/common/FourCC.d.ts +1 -1
  21. package/lib/common/FourCC.js +5 -3
  22. package/lib/common/MetadataCollector.d.ts +3 -3
  23. package/lib/common/MetadataCollector.js +7 -8
  24. package/lib/common/RandomFileReader.d.ts +0 -1
  25. package/lib/common/Util.d.ts +1 -1
  26. package/lib/common/Util.js +4 -3
  27. package/lib/core.d.ts +16 -8
  28. package/lib/core.js +19 -5
  29. package/lib/dsdiff/DsdiffParser.js +1 -1
  30. package/lib/dsdiff/DsdiffToken.d.ts +1 -1
  31. package/lib/dsf/DsfChunk.d.ts +1 -1
  32. package/lib/flac/FlacParser.d.ts +1 -1
  33. package/lib/flac/FlacParser.js +6 -4
  34. package/lib/id3v1/ID3v1Parser.js +6 -6
  35. package/lib/id3v2/AbstractID3Parser.d.ts +1 -1
  36. package/lib/id3v2/AbstractID3Parser.js +1 -1
  37. package/lib/id3v2/FrameParser.js +4 -3
  38. package/lib/id3v2/ID3v24TagMapper.d.ts +2 -3
  39. package/lib/id3v2/ID3v24TagMapper.js +14 -9
  40. package/lib/id3v2/ID3v2Parser.d.ts +1 -1
  41. package/lib/id3v2/ID3v2Parser.js +10 -17
  42. package/lib/id3v2/ID3v2Token.d.ts +1 -1
  43. package/lib/id3v2/ID3v2Token.js +2 -2
  44. package/lib/iff/index.d.ts +1 -1
  45. package/lib/index.d.ts +1 -2
  46. package/lib/index.js +1 -1
  47. package/lib/lyrics3/Lyrics3.js +1 -1
  48. package/lib/matroska/MatroskaDtd.js +12 -12
  49. package/lib/matroska/MatroskaParser.d.ts +1 -1
  50. package/lib/matroska/MatroskaParser.js +16 -20
  51. package/lib/matroska/types.d.ts +0 -1
  52. package/lib/mp4/Atom.d.ts +1 -1
  53. package/lib/mp4/AtomToken.d.ts +2 -3
  54. package/lib/mp4/AtomToken.js +2 -2
  55. package/lib/mp4/MP4Parser.js +20 -19
  56. package/lib/mp4/MP4TagMapper.js +1 -1
  57. package/lib/mpeg/ExtendedLameHeader.d.ts +1 -1
  58. package/lib/mpeg/MpegParser.js +4 -4
  59. package/lib/mpeg/ReplayGainDataFormat.d.ts +1 -1
  60. package/lib/mpeg/XingTag.d.ts +1 -2
  61. package/lib/musepack/index.js +1 -1
  62. package/lib/musepack/sv7/BitReader.d.ts +1 -1
  63. package/lib/musepack/sv7/StreamVersion7.d.ts +1 -1
  64. package/lib/musepack/sv7/StreamVersion7.js +1 -1
  65. package/lib/musepack/sv8/StreamVersion8.d.ts +1 -1
  66. package/lib/musepack/sv8/StreamVersion8.js +1 -1
  67. package/lib/ogg/Ogg.d.ts +2 -2
  68. package/lib/ogg/OggParser.d.ts +1 -1
  69. package/lib/ogg/OggParser.js +5 -5
  70. package/lib/ogg/opus/Opus.d.ts +1 -1
  71. package/lib/ogg/opus/Opus.js +6 -6
  72. package/lib/ogg/opus/OpusParser.d.ts +2 -3
  73. package/lib/ogg/opus/OpusParser.js +2 -2
  74. package/lib/ogg/speex/Speex.d.ts +1 -1
  75. package/lib/ogg/speex/Speex.js +13 -13
  76. package/lib/ogg/speex/SpeexParser.d.ts +1 -2
  77. package/lib/ogg/theora/Theora.d.ts +1 -1
  78. package/lib/ogg/theora/Theora.js +6 -6
  79. package/lib/ogg/theora/TheoraParser.d.ts +4 -5
  80. package/lib/ogg/theora/TheoraParser.js +4 -4
  81. package/lib/ogg/vorbis/Vorbis.d.ts +3 -4
  82. package/lib/ogg/vorbis/Vorbis.js +11 -12
  83. package/lib/ogg/vorbis/VorbisDecoder.js +1 -1
  84. package/lib/ogg/vorbis/VorbisParser.d.ts +6 -7
  85. package/lib/ogg/vorbis/VorbisParser.js +11 -11
  86. package/lib/riff/RiffChunk.d.ts +1 -1
  87. package/lib/riff/RiffChunk.js +2 -2
  88. package/lib/riff/RiffInfoTagMap.js +16 -16
  89. package/lib/type.d.ts +9 -11
  90. package/lib/wav/BwfChunk.d.ts +1 -1
  91. package/lib/wav/WaveChunk.d.ts +2 -3
  92. package/lib/wav/WaveChunk.js +8 -7
  93. package/lib/wav/WaveParser.js +2 -2
  94. package/lib/wavpack/WavPackParser.js +1 -1
  95. package/lib/wavpack/WavPackToken.d.ts +1 -1
  96. package/lib/wavpack/WavPackToken.js +1 -1
  97. package/package.json +29 -28
@@ -1,3 +1,4 @@
1
+ import { StringType } from 'token-types';
1
2
  export function getBit(buf, off, bit) {
2
3
  return (buf[off] & (1 << bit)) !== 0;
3
4
  }
@@ -11,7 +12,7 @@ export function getBit(buf, off, bit) {
11
12
  */
12
13
  export function findZero(uint8Array, start, end, encoding) {
13
14
  let i = start;
14
- if (encoding === 'utf16le') {
15
+ if (encoding === 'utf-16le') {
15
16
  while (uint8Array[i] !== 0 || uint8Array[i + 1] !== 0) {
16
17
  if (i >= end)
17
18
  return end;
@@ -52,13 +53,13 @@ export function decodeString(uint8Array, encoding) {
52
53
  if (uint8Array[0] === 0xFF && uint8Array[1] === 0xFE) { // little endian
53
54
  return decodeString(uint8Array.subarray(2), encoding);
54
55
  }
55
- else if (encoding === 'utf16le' && uint8Array[0] === 0xFE && uint8Array[1] === 0xFF) {
56
+ else if (encoding === 'utf-16le' && uint8Array[0] === 0xFE && uint8Array[1] === 0xFF) {
56
57
  // BOM, indicating big endian decoding
57
58
  if ((uint8Array.length & 1) !== 0)
58
59
  throw new Error('Expected even number of octets for 16-bit unicode string');
59
60
  return decodeString(swapBytes(uint8Array), encoding);
60
61
  }
61
- return Buffer.from(uint8Array).toString(encoding);
62
+ return new StringType(uint8Array.length, encoding).get(uint8Array, 0);
62
63
  }
63
64
  export function stripNulls(str) {
64
65
  str = str.replace(/^\x00+/g, '');
package/lib/core.d.ts CHANGED
@@ -1,16 +1,24 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- import { Readable } from 'stream';
3
- import * as strtok3 from 'strtok3/core';
4
- import { IAudioMetadata, INativeTagDict, IOptions, IPicture, IPrivateOptions, IRandomReader, ITag } from './type.js';
5
- export { IFileInfo } from 'strtok3/core';
1
+ import * as strtok3 from 'strtok3';
2
+ import type { IAudioMetadata, INativeTagDict, IOptions, IPicture, IPrivateOptions, IRandomReader, ITag } from './type.js';
3
+ import type { ReadableStream as NodeReadableStream } from 'node:stream/web';
4
+ export { IFileInfo } from 'strtok3';
5
+ export type AnyWebStream<G> = NodeReadableStream<G> | ReadableStream<G>;
6
6
  /**
7
- * Parse audio from Node Stream.Readable
8
- * @param stream - Stream to read the audio track from
7
+ * Parse Web API File
8
+ * Requires Blob to be able to stream using a ReadableStreamBYOBReader, only available since Node.js ≥ 20
9
+ * @param blob - Blob to parse
10
+ * @param options - Parsing options
11
+ * @returns Metadata
12
+ */
13
+ export declare function parseBlob(blob: Blob, options?: IOptions): Promise<IAudioMetadata>;
14
+ /**
15
+ * Parse audio from Web Stream.Readable
16
+ * @param webStream - WebStream to read the audio track from
9
17
  * @param options - Parsing options
10
18
  * @param fileInfo - File information object or MIME-type string
11
19
  * @returns Metadata
12
20
  */
13
- export declare function parseStream(stream: Readable, fileInfo?: strtok3.IFileInfo | string, options?: IOptions): Promise<IAudioMetadata>;
21
+ export declare function parseWebStream(webStream: AnyWebStream<Uint8Array>, fileInfo?: strtok3.IFileInfo | string, options?: IOptions): Promise<IAudioMetadata>;
14
22
  /**
15
23
  * Parse audio from Node Buffer
16
24
  * @param uint8Array - Uint8Array holding audio data
package/lib/core.js CHANGED
@@ -1,18 +1,32 @@
1
- import * as strtok3 from 'strtok3/core';
1
+ import * as strtok3 from 'strtok3';
2
2
  import { ParserFactory } from './ParserFactory.js';
3
3
  import { RandomUint8ArrayReader } from './common/RandomUint8ArrayReader.js';
4
4
  import { APEv2Parser } from './apev2/APEv2Parser.js';
5
5
  import { hasID3v1Header } from './id3v1/ID3v1Parser.js';
6
6
  import { getLyricsHeaderLength } from './lyrics3/Lyrics3.js';
7
7
  /**
8
- * Parse audio from Node Stream.Readable
9
- * @param stream - Stream to read the audio track from
8
+ * Parse Web API File
9
+ * Requires Blob to be able to stream using a ReadableStreamBYOBReader, only available since Node.js ≥ 20
10
+ * @param blob - Blob to parse
11
+ * @param options - Parsing options
12
+ * @returns Metadata
13
+ */
14
+ export async function parseBlob(blob, options = {}) {
15
+ const fileInfo = { mimeType: blob.type, size: blob.size };
16
+ if (blob instanceof File) {
17
+ fileInfo.path = blob.name;
18
+ }
19
+ return parseWebStream(blob.stream(), fileInfo, options);
20
+ }
21
+ /**
22
+ * Parse audio from Web Stream.Readable
23
+ * @param webStream - WebStream to read the audio track from
10
24
  * @param options - Parsing options
11
25
  * @param fileInfo - File information object or MIME-type string
12
26
  * @returns Metadata
13
27
  */
14
- export function parseStream(stream, fileInfo, options = {}) {
15
- return parseFromTokenizer(strtok3.fromStream(stream, typeof fileInfo === 'string' ? { mimeType: fileInfo } : fileInfo), options);
28
+ export function parseWebStream(webStream, fileInfo, options = {}) {
29
+ return parseFromTokenizer(strtok3.fromWebStream(webStream, typeof fileInfo === 'string' ? { mimeType: fileInfo } : fileInfo), options);
16
30
  }
17
31
  /**
18
32
  * Parse audio from Node Buffer
@@ -1,6 +1,6 @@
1
1
  import * as Token from 'token-types';
2
2
  import initDebug from 'debug';
3
- import * as strtok3 from 'strtok3/core';
3
+ import * as strtok3 from 'strtok3';
4
4
  import { FourCcToken } from '../common/FourCC.js';
5
5
  import { BasicParser } from '../common/BasicParser.js';
6
6
  import { ID3v2Parser } from '../id3v2/ID3v2Parser.js';
@@ -1,4 +1,4 @@
1
- import { IGetToken } from 'strtok3/core';
1
+ import type { IGetToken } from 'strtok3';
2
2
  import { IChunkHeader64 } from '../iff/index.js';
3
3
  export { IChunkHeader64 } from '../iff/index.js';
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { IGetToken } from 'strtok3/core';
1
+ import type { IGetToken } from 'strtok3';
2
2
  /**
3
3
  * Common interface for the common chunk DSD header
4
4
  */
@@ -1,4 +1,4 @@
1
- import { ITokenizer } from 'strtok3/core';
1
+ import type { ITokenizer } from 'strtok3';
2
2
  import { AbstractID3Parser } from '../id3v2/AbstractID3Parser.js';
3
3
  import { INativeMetadataCollector } from '../common/MetadataCollector.js';
4
4
  import { IOptions } from '../type.js';
@@ -54,7 +54,7 @@ export class FlacParser extends AbstractID3Parser {
54
54
  this.metadata.setFormat('bitrate', 8 * dataSize / this.metadata.format.duration);
55
55
  }
56
56
  }
57
- parseDataBlock(blockHeader) {
57
+ async parseDataBlock(blockHeader) {
58
58
  debug(`blockHeader type=${blockHeader.type}, length=${blockHeader.length}`);
59
59
  switch (blockHeader.type) {
60
60
  case BlockType.STREAMINFO:
@@ -71,7 +71,8 @@ export class FlacParser extends AbstractID3Parser {
71
71
  case BlockType.CUESHEET:
72
72
  break;
73
73
  case BlockType.PICTURE:
74
- return this.parsePicture(blockHeader.length).then();
74
+ await this.parsePicture(blockHeader.length);
75
+ return;
75
76
  default:
76
77
  this.metadata.addWarning('Unknown block type: ' + blockHeader.type);
77
78
  }
@@ -104,10 +105,11 @@ export class FlacParser extends AbstractID3Parser {
104
105
  const decoder = new VorbisDecoder(data, 0);
105
106
  decoder.readStringUtf8(); // vendor (skip)
106
107
  const commentListLength = decoder.readInt32();
108
+ const tags = new Array(commentListLength);
107
109
  for (let i = 0; i < commentListLength; i++) {
108
- const tag = decoder.parseUserComment();
109
- this.vorbisParser.addTag(tag.key, tag.value);
110
+ tags[i] = decoder.parseUserComment();
110
111
  }
112
+ await Promise.all(tags.map(tag => this.vorbisParser.addTag(tag.key, tag.value)));
111
113
  }
112
114
  async parsePicture(dataLen) {
113
115
  if (this.options.skipCovers) {
@@ -68,7 +68,7 @@ const Iid3v1Token = {
68
68
  };
69
69
  class Id3v1StringType extends StringType {
70
70
  constructor(len) {
71
- super(len, 'binary');
71
+ super(len, 'latin1');
72
72
  }
73
73
  get(buf, off) {
74
74
  let value = super.get(buf, off);
@@ -105,25 +105,25 @@ export class ID3v1Parser extends BasicParser {
105
105
  debug('ID3v1 header found at: pos=%s', this.tokenizer.fileInfo.size - Iid3v1Token.len);
106
106
  for (const id of ['title', 'artist', 'album', 'comment', 'track', 'year']) {
107
107
  if (header[id] && header[id] !== '')
108
- this.addTag(id, header[id]);
108
+ await this.addTag(id, header[id]);
109
109
  }
110
110
  const genre = ID3v1Parser.getGenre(header.genre);
111
111
  if (genre)
112
- this.addTag('genre', genre);
112
+ await this.addTag('genre', genre);
113
113
  }
114
114
  else {
115
115
  debug('ID3v1 header not found at: pos=%s', this.tokenizer.fileInfo.size - Iid3v1Token.len);
116
116
  }
117
117
  }
118
- addTag(id, value) {
119
- this.metadata.addTag('ID3v1', id, value);
118
+ async addTag(id, value) {
119
+ await this.metadata.addTag('ID3v1', id, value);
120
120
  }
121
121
  }
122
122
  export async function hasID3v1Header(reader) {
123
123
  if (reader.fileSize >= 128) {
124
124
  const tag = Buffer.alloc(3);
125
125
  await reader.randomRead(tag, 0, tag.length, reader.fileSize - 128);
126
- return tag.toString('binary') === 'TAG';
126
+ return tag.toString('latin1') === 'TAG';
127
127
  }
128
128
  return false;
129
129
  }
@@ -1,4 +1,4 @@
1
- import { ITokenizer } from 'strtok3/core';
1
+ import { ITokenizer } from 'strtok3';
2
2
  import { BasicParser } from '../common/BasicParser.js';
3
3
  /**
4
4
  * Abstract parser which tries take ID3v2 and ID3v1 headers.
@@ -1,4 +1,4 @@
1
- import { EndOfStreamError } from 'strtok3/core';
1
+ import { EndOfStreamError } from 'strtok3';
2
2
  import initDebug from 'debug';
3
3
  import { ID3v2Header } from './ID3v2Token.js';
4
4
  import { ID3v2Parser } from './ID3v2Parser.js';
@@ -79,6 +79,7 @@ export class FrameParser {
79
79
  debug(`Parsing tag type=${type}, encoding=${encoding}, bom=${bom}`);
80
80
  switch (type !== 'TXXX' && type[0] === 'T' ? 'T*' : type) {
81
81
  case 'T*': // 4.2.1. Text information frames - details
82
+ case 'GRP1': // iTunes-specific ID3v2 grouping field
82
83
  case 'IPLS': // v2.3: Involved people list
83
84
  case 'MVIN':
84
85
  case 'MVNM':
@@ -158,7 +159,7 @@ export class FrameParser {
158
159
  fzero = util.findZero(uint8Array, offset, length, encoding);
159
160
  pic.description = util.decodeString(uint8Array.slice(offset, fzero), encoding);
160
161
  offset = fzero + nullTerminatorLength;
161
- pic.data = Buffer.from(uint8Array.slice(offset, length));
162
+ pic.data = uint8Array.slice(offset, length);
162
163
  output = pic;
163
164
  }
164
165
  break;
@@ -245,7 +246,7 @@ export class FrameParser {
245
246
  // Decode URL
246
247
  fzero = util.findZero(uint8Array, offset + 1, length, encoding);
247
248
  const description = util.decodeString(uint8Array.slice(offset + 1, fzero), encoding);
248
- offset = fzero + (encoding === 'utf16le' ? 2 : 1);
249
+ offset = fzero + (encoding === 'utf-16le' ? 2 : 1);
249
250
  output = { description, url: util.decodeString(uint8Array.slice(offset, length), defaultEnc) };
250
251
  break;
251
252
  }
@@ -319,6 +320,6 @@ export class FrameParser {
319
320
  return { id, data: uint8Array.slice(offset, length) };
320
321
  }
321
322
  static getNullTerminatorLength(enc) {
322
- return enc === 'utf16le' ? 2 : 1;
323
+ return enc === 'utf-16le' ? 2 : 1;
323
324
  }
324
325
  }
@@ -1,6 +1,6 @@
1
1
  import { CaseInsensitiveTagMap } from '../common/CaseInsensitiveTagMap.js';
2
- import { INativeMetadataCollector } from '../common/MetadataCollector.js';
3
- import { IRating, ITag } from '../type.js';
2
+ import type { INativeMetadataCollector } from '../common/MetadataCollector.js';
3
+ import type { IRating, ITag } from '../type.js';
4
4
  export declare class ID3v24TagMapper extends CaseInsensitiveTagMap {
5
5
  static toRating(popm: any): IRating;
6
6
  constructor();
@@ -8,7 +8,6 @@ export declare class ID3v24TagMapper extends CaseInsensitiveTagMap {
8
8
  * Handle post mapping exceptions / correction
9
9
  * @param tag to post map
10
10
  * @param warnings Wil be used to register (collect) warnings
11
- * @return Common value e.g. "Buena Vista Social Club"
12
11
  */
13
12
  protected postMap(tag: ITag, warnings: INativeMetadataCollector): void;
14
13
  }
@@ -1,6 +1,7 @@
1
+ import { UINT32_LE } from 'token-types';
1
2
  import { CommonTagMapper } from '../common/GenericTagMapper.js';
2
3
  import { CaseInsensitiveTagMap } from '../common/CaseInsensitiveTagMap.js';
3
- import * as util from '../common/Util.js';
4
+ import { decodeString } from '../common/Util.js';
4
5
  /**
5
6
  * ID3v2.3/ID3v2.4 tag mappings
6
7
  */
@@ -11,7 +12,7 @@ const id3v24TagMap = {
11
12
  'TXXX:Artists': 'artists',
12
13
  TPE2: 'albumartist',
13
14
  TALB: 'album',
14
- TDRV: 'date',
15
+ TDRV: 'date', // [ 'date', 'year' ] ToDo: improve 'year' mapping
15
16
  /**
16
17
  * Original release year
17
18
  */
@@ -20,7 +21,7 @@ const id3v24TagMap = {
20
21
  TCON: 'genre',
21
22
  APIC: 'picture',
22
23
  TCOM: 'composer',
23
- 'USLT:description': 'lyrics',
24
+ USLT: 'lyrics',
24
25
  TSOA: 'albumsort',
25
26
  TSOT: 'titlesort',
26
27
  TOAL: 'originalalbum',
@@ -84,7 +85,7 @@ const id3v24TagMap = {
84
85
  WOAR: 'website',
85
86
  // id3v2.4
86
87
  // ToDo: In same sequence as defined at http://id3.org/id3v2.4.0-frames
87
- TDRC: 'date',
88
+ TDRC: 'date', // date YYYY-MM-DD
88
89
  TYER: 'year',
89
90
  TDOR: 'originaldate',
90
91
  // 'TMCL:instrument': 'performer:instrument',
@@ -135,7 +136,8 @@ const id3v24TagMap = {
135
136
  TDRL: 'releasedate',
136
137
  TGID: 'podcastId',
137
138
  TKWD: 'keywords',
138
- WFED: 'podcasturl'
139
+ WFED: 'podcasturl',
140
+ GRP1: 'grouping'
139
141
  };
140
142
  export class ID3v24TagMapper extends CaseInsensitiveTagMap {
141
143
  static toRating(popm) {
@@ -151,14 +153,14 @@ export class ID3v24TagMapper extends CaseInsensitiveTagMap {
151
153
  * Handle post mapping exceptions / correction
152
154
  * @param tag to post map
153
155
  * @param warnings Wil be used to register (collect) warnings
154
- * @return Common value e.g. "Buena Vista Social Club"
155
156
  */
156
157
  postMap(tag, warnings) {
158
+ var _a, _b;
157
159
  switch (tag.id) {
158
160
  case 'UFID': // decode MusicBrainz Recording Id
159
161
  if (tag.value.owner_identifier === 'http://musicbrainz.org') {
160
162
  tag.id += ':' + tag.value.owner_identifier;
161
- tag.value = util.decodeString(tag.value.identifier, 'latin1'); // latin1 == iso-8859-1
163
+ tag.value = decodeString(tag.value.identifier, 'latin1'); // latin1 == iso-8859-1
162
164
  }
163
165
  break;
164
166
  case 'PRIV':
@@ -167,7 +169,7 @@ export class ID3v24TagMapper extends CaseInsensitiveTagMap {
167
169
  case 'AverageLevel':
168
170
  case 'PeakValue':
169
171
  tag.id += ':' + tag.value.owner_identifier;
170
- tag.value = tag.value.data.length === 4 ? tag.value.data.readUInt32LE(0) : null;
172
+ tag.value = tag.value.data.length === 4 ? UINT32_LE.get(tag.value.data, 0) : null;
171
173
  if (tag.value === null) {
172
174
  warnings.addWarning(`Failed to parse PRIV:PeakValue`);
173
175
  }
@@ -177,11 +179,14 @@ export class ID3v24TagMapper extends CaseInsensitiveTagMap {
177
179
  }
178
180
  break;
179
181
  case 'COMM':
180
- tag.value = tag.value ? tag.value.text : null;
182
+ tag.value = (_a = tag.value) === null || _a === void 0 ? void 0 : _a.text;
181
183
  break;
182
184
  case 'POPM':
183
185
  tag.value = ID3v24TagMapper.toRating(tag.value);
184
186
  break;
187
+ case 'USLT':
188
+ tag.value = (_b = tag.value) === null || _b === void 0 ? void 0 : _b.text;
189
+ break;
185
190
  default:
186
191
  break;
187
192
  }
@@ -1,4 +1,4 @@
1
- import { ITokenizer } from 'strtok3/core';
1
+ import type { ITokenizer } from 'strtok3';
2
2
  import { IOptions } from '../type.js';
3
3
  import { INativeMetadataCollector } from '../common/MetadataCollector.js';
4
4
  export declare class ID3v2Parser {
@@ -2,6 +2,7 @@ import * as Token from 'token-types';
2
2
  import * as util from '../common/Util.js';
3
3
  import { FrameParser } from './FrameParser.js';
4
4
  import { ExtendedHeader, ID3v2Header, UINT32SYNCSAFE } from './ID3v2Token.js';
5
+ const asciiDecoder = new TextDecoder('ascii');
5
6
  export class ID3v2Parser {
6
7
  static removeUnsyncBytes(buffer) {
7
8
  let readI = 0;
@@ -98,33 +99,25 @@ export class ID3v2Parser {
98
99
  for (const tag of this.parseMetadata(uint8Array)) {
99
100
  if (tag.id === 'TXXX') {
100
101
  if (tag.value) {
101
- for (const text of tag.value.text) {
102
- this.addTag(ID3v2Parser.makeDescriptionTagName(tag.id, tag.value.description), text);
103
- }
102
+ await Promise.all(tag.value.text.map(text => this.addTag(ID3v2Parser.makeDescriptionTagName(tag.id, tag.value.description), text)));
104
103
  }
105
104
  }
106
105
  else if (tag.id === 'COM') {
107
- for (const value of tag.value) {
108
- this.addTag(ID3v2Parser.makeDescriptionTagName(tag.id, value.description), value.text);
109
- }
106
+ await Promise.all(tag.value.map(value => this.addTag(ID3v2Parser.makeDescriptionTagName(tag.id, value.description), value.text)));
110
107
  }
111
108
  else if (tag.id === 'COMM') {
112
- for (const value of tag.value) {
113
- this.addTag(ID3v2Parser.makeDescriptionTagName(tag.id, value.description), value);
114
- }
109
+ await Promise.all(tag.value.map(value => this.addTag(ID3v2Parser.makeDescriptionTagName(tag.id, value.description), value)));
115
110
  }
116
111
  else if (Array.isArray(tag.value)) {
117
- for (const value of tag.value) {
118
- this.addTag(tag.id, value);
119
- }
112
+ await Promise.all(tag.value.map(value => this.addTag(tag.id, value)));
120
113
  }
121
114
  else {
122
- this.addTag(tag.id, tag.value);
115
+ await this.addTag(tag.id, tag.value);
123
116
  }
124
117
  }
125
118
  }
126
- addTag(id, value) {
127
- this.metadata.addTag(this.headerType, id, value);
119
+ async addTag(id, value) {
120
+ await this.metadata.addTag(this.headerType, id, value);
128
121
  }
129
122
  parseMetadata(data) {
130
123
  let offset = 0;
@@ -152,7 +145,7 @@ export class ID3v2Parser {
152
145
  switch (majorVer) {
153
146
  case 2:
154
147
  header = {
155
- id: Buffer.from(uint8Array.slice(0, 3)).toString('ascii'),
148
+ id: asciiDecoder.decode(uint8Array.slice(0, 3)),
156
149
  length: Token.UINT24_BE.get(uint8Array, 3)
157
150
  };
158
151
  if (!header.id.match(/[A-Z0-9]{3}/g)) {
@@ -162,7 +155,7 @@ export class ID3v2Parser {
162
155
  case 3:
163
156
  case 4:
164
157
  header = {
165
- id: Buffer.from(uint8Array.slice(0, 4)).toString('ascii'),
158
+ id: asciiDecoder.decode(uint8Array.slice(0, 4)),
166
159
  length: (majorVer === 4 ? UINT32SYNCSAFE : Token.UINT32_BE).get(uint8Array, 4),
167
160
  flags: ID3v2Parser.readFrameFlags(uint8Array.slice(8, 10))
168
161
  };
@@ -1,4 +1,4 @@
1
- import { IGetToken } from 'strtok3/core';
1
+ import type { IGetToken } from 'strtok3';
2
2
  import * as util from '../common/Util.js';
3
3
  /**
4
4
  * The picture type according to the ID3v2 APIC frame
@@ -91,9 +91,9 @@ export const TextEncodingToken = {
91
91
  case 0x00:
92
92
  return { encoding: 'latin1' }; // binary
93
93
  case 0x01:
94
- return { encoding: 'utf16le', bom: true };
94
+ return { encoding: 'utf-16le', bom: true };
95
95
  case 0x02:
96
- return { encoding: 'utf16le', bom: false };
96
+ return { encoding: 'utf-16le', bom: false };
97
97
  case 0x03:
98
98
  return { encoding: 'utf8', bom: false };
99
99
  default:
@@ -1,4 +1,4 @@
1
- import { IGetToken } from 'strtok3/core';
1
+ import type { IGetToken } from 'strtok3';
2
2
  /**
3
3
  * "EA IFF 85" Standard for Interchange Format Files
4
4
  * Ref: http://www.martinreddy.net/gfx/2d/IFF.txt
package/lib/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import * as Stream from 'stream';
3
2
  import * as strtok3 from 'strtok3';
4
3
  import { IAudioMetadata, IOptions } from './type.js';
5
4
  export { IAudioMetadata, IOptions, ITag, INativeTagDict, ICommonTagsResult, IFormat, IPicture, IRatio, IChapter } from './type.js';
6
- export { parseFromTokenizer, parseBuffer, selectCover, orderTags, ratingToStars, IFileInfo } from './core.js';
5
+ export { parseFromTokenizer, parseBuffer, parseBlob, selectCover, orderTags, ratingToStars, IFileInfo } from './core.js';
7
6
  /**
8
7
  * Parse audio from Node Stream.Readable
9
8
  * @param stream - Stream to read the audio track from
package/lib/index.js CHANGED
@@ -3,7 +3,7 @@ import initDebug from 'debug';
3
3
  import { parseFromTokenizer, scanAppendingHeaders } from './core.js';
4
4
  import { ParserFactory } from './ParserFactory.js';
5
5
  import { RandomFileReader } from './common/RandomFileReader.js';
6
- export { parseFromTokenizer, parseBuffer, selectCover, orderTags, ratingToStars } from './core.js';
6
+ export { parseFromTokenizer, parseBuffer, parseBlob, selectCover, orderTags, ratingToStars } from './core.js';
7
7
  const debug = initDebug('music-metadata:parser');
8
8
  /**
9
9
  * Parse audio from Node Stream.Readable
@@ -3,7 +3,7 @@ export async function getLyricsHeaderLength(reader) {
3
3
  if (reader.fileSize >= 143) {
4
4
  const buf = Buffer.alloc(15);
5
5
  await reader.randomRead(buf, 0, buf.length, reader.fileSize - 143);
6
- const txt = buf.toString('binary');
6
+ const txt = buf.toString('latin1');
7
7
  const tag = txt.substr(6);
8
8
  if (tag === endTag2) {
9
9
  return parseInt(txt.substr(0, 6), 10) + 15;
@@ -9,12 +9,12 @@ export const elements = {
9
9
  0x1a45dfa3: {
10
10
  name: 'ebml',
11
11
  container: {
12
- 0x4286: { name: 'ebmlVersion', value: DataType.uint },
13
- 0x42f7: { name: 'ebmlReadVersion', value: DataType.uint },
14
- 0x42f2: { name: 'ebmlMaxIDWidth', value: DataType.uint },
15
- 0x42f3: { name: 'ebmlMaxSizeWidth', value: DataType.uint },
16
- 0x4282: { name: 'docType', value: DataType.string },
17
- 0x4287: { name: 'docTypeVersion', value: DataType.uint },
12
+ 0x4286: { name: 'ebmlVersion', value: DataType.uint }, // 5.1.1
13
+ 0x42f7: { name: 'ebmlReadVersion', value: DataType.uint }, // 5.1.2
14
+ 0x42f2: { name: 'ebmlMaxIDWidth', value: DataType.uint }, // 5.1.3
15
+ 0x42f3: { name: 'ebmlMaxSizeWidth', value: DataType.uint }, // 5.1.4
16
+ 0x4282: { name: 'docType', value: DataType.string }, // 5.1.5
17
+ 0x4287: { name: 'docTypeVersion', value: DataType.uint }, // 5.1.6
18
18
  0x4285: { name: 'docTypeReadVersion', value: DataType.uint } // 5.1.7
19
19
  }
20
20
  },
@@ -77,7 +77,7 @@ export const elements = {
77
77
  0x83: { name: 'trackType', value: DataType.uint },
78
78
  0xb9: { name: 'flagEnabled', value: DataType.bool },
79
79
  0x88: { name: 'flagDefault', value: DataType.bool },
80
- 0x55aa: { name: 'flagForced', value: DataType.bool },
80
+ 0x55aa: { name: 'flagForced', value: DataType.bool }, // extended
81
81
  0x9c: { name: 'flagLacing', value: DataType.bool },
82
82
  0x6de7: { name: 'minCache', value: DataType.uint },
83
83
  0x6de8: { name: 'maxCache', value: DataType.uint },
@@ -114,7 +114,7 @@ export const elements = {
114
114
  container: {
115
115
  0xb5: { name: 'samplingFrequency', value: DataType.float },
116
116
  0x78b5: { name: 'outputSamplingFrequency', value: DataType.float },
117
- 0x9f: { name: 'channels', value: DataType.uint },
117
+ 0x9f: { name: 'channels', value: DataType.uint }, // https://www.matroska.org/technical/specs/index.html
118
118
  0x94: { name: 'channels', value: DataType.uint },
119
119
  0x7d7b: { name: 'channelPositions', value: DataType.binary },
120
120
  0x6264: { name: 'bitDepth', value: DataType.uint }
@@ -250,8 +250,8 @@ export const elements = {
250
250
  0x63c5: { name: 'tagTrackUID', value: DataType.uid },
251
251
  0x63c4: { name: 'tagChapterUID', value: DataType.uint },
252
252
  0x63c6: { name: 'tagAttachmentUID', value: DataType.uid },
253
- 0x63ca: { name: 'targetType', value: DataType.string },
254
- 0x68ca: { name: 'targetTypeValue', value: DataType.uint },
253
+ 0x63ca: { name: 'targetType', value: DataType.string }, // extended
254
+ 0x68ca: { name: 'targetTypeValue', value: DataType.uint }, // extended
255
255
  0x63c9: { name: 'tagEditionUID', value: DataType.uid } // extended
256
256
  }
257
257
  },
@@ -262,8 +262,8 @@ export const elements = {
262
262
  0x45a3: { name: 'name', value: DataType.string },
263
263
  0x4487: { name: 'string', value: DataType.string },
264
264
  0x4485: { name: 'binary', value: DataType.binary },
265
- 0x447a: { name: 'language', value: DataType.string },
266
- 0x447b: { name: 'languageIETF', value: DataType.string },
265
+ 0x447a: { name: 'language', value: DataType.string }, // extended
266
+ 0x447b: { name: 'languageIETF', value: DataType.string }, // extended
267
267
  0x4484: { name: 'default', value: DataType.bool } // extended
268
268
  }
269
269
  }
@@ -1,4 +1,4 @@
1
- import { ITokenizer } from 'strtok3/core';
1
+ import type { ITokenizer } from 'strtok3';
2
2
  import { INativeMetadataCollector } from '../common/MetadataCollector.js';
3
3
  import { BasicParser } from '../common/BasicParser.js';
4
4
  import { IOptions } from '../type.js';
@@ -44,7 +44,7 @@ export class MatroskaParser extends BasicParser {
44
44
  const timecodeScale = info.timecodeScale ? info.timecodeScale : 1000000;
45
45
  if (typeof info.duration === 'number') {
46
46
  const duration = info.duration * timecodeScale / 1000000000;
47
- this.addTag('segment:title', info.title);
47
+ await this.addTag('segment:title', info.title);
48
48
  this.metadata.setFormat('duration', duration);
49
49
  }
50
50
  }
@@ -87,28 +87,24 @@ export class MatroskaParser extends BasicParser {
87
87
  this.metadata.setFormat('numberOfChannels', audioTrack.audio.channels);
88
88
  }
89
89
  if (matroska.segment.tags) {
90
- matroska.segment.tags.tag.forEach(tag => {
90
+ await Promise.all(matroska.segment.tags.tag.map(async (tag) => {
91
91
  const target = tag.target;
92
92
  const targetType = (target === null || target === void 0 ? void 0 : target.targetTypeValue) ? TargetType[target.targetTypeValue] : ((target === null || target === void 0 ? void 0 : target.targetType) ? target.targetType : 'track');
93
- tag.simpleTags.forEach(simpleTag => {
93
+ await Promise.all(tag.simpleTags.map(async (simpleTag) => {
94
94
  const value = simpleTag.string ? simpleTag.string : simpleTag.binary;
95
- this.addTag(`${targetType}:${simpleTag.name}`, value);
96
- });
97
- });
95
+ await this.addTag(`${targetType}:${simpleTag.name}`, value);
96
+ }));
97
+ }));
98
98
  }
99
99
  if (matroska.segment.attachments) {
100
- matroska.segment.attachments.attachedFiles
100
+ await Promise.all(matroska.segment.attachments.attachedFiles
101
101
  .filter(file => file.mimeType.startsWith('image/'))
102
- .map(file => {
103
- return {
104
- data: file.data,
105
- format: file.mimeType,
106
- description: file.description,
107
- name: file.name
108
- };
109
- }).forEach(picture => {
110
- this.addTag('picture', picture);
111
- });
102
+ .map(file => this.addTag('picture', {
103
+ data: file.data,
104
+ format: file.mimeType,
105
+ description: file.description,
106
+ name: file.name
107
+ })));
112
108
  }
113
109
  }
114
110
  }
@@ -220,14 +216,14 @@ export class MatroskaParser extends BasicParser {
220
216
  }
221
217
  async readString(e) {
222
218
  const rawString = await this.tokenizer.readToken(new StringType(e.len, 'utf-8'));
223
- return rawString.replace(/\00.*$/g, '');
219
+ return rawString.replace(/\x00.*$/g, '');
224
220
  }
225
221
  async readBuffer(e) {
226
222
  const buf = Buffer.alloc(e.len);
227
223
  await this.tokenizer.readBuffer(buf);
228
224
  return buf;
229
225
  }
230
- addTag(tagId, value) {
231
- this.metadata.addTag('matroska', tagId, value);
226
+ async addTag(tagId, value) {
227
+ await this.metadata.addTag('matroska', tagId, value);
232
228
  }
233
229
  }
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  export interface IHeader {
3
2
  id: number;
4
3
  len: number;