music-metadata 7.12.6 → 8.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.
- package/README.md +47 -31
- package/lib/ParserFactory.js +37 -41
- package/lib/aiff/AiffParser.js +12 -16
- package/lib/aiff/AiffToken.js +4 -8
- package/lib/apev2/APEv2Parser.js +29 -32
- package/lib/apev2/APEv2TagMapper.js +2 -6
- package/lib/apev2/APEv2Token.js +13 -19
- package/lib/asf/AsfObject.js +42 -56
- package/lib/asf/AsfParser.js +15 -19
- package/lib/asf/AsfTagMapper.js +2 -6
- package/lib/asf/AsfUtil.js +4 -7
- package/lib/asf/GUID.js +1 -4
- package/lib/common/BasicParser.js +1 -5
- package/lib/common/CaseInsensitiveTagMap.js +2 -6
- package/lib/common/CombinedTagMapper.js +20 -24
- package/lib/common/FourCC.js +3 -6
- package/lib/common/GenericTagMapper.js +2 -6
- package/lib/common/GenericTagTypes.js +5 -10
- package/lib/common/MetadataCollector.js +20 -25
- package/lib/common/RandomFileReader.js +2 -6
- package/lib/common/RandomUint8ArrayReader.js +1 -5
- package/lib/common/Util.js +11 -25
- package/lib/core.js +18 -28
- package/lib/dsdiff/DsdiffParser.js +24 -28
- package/lib/dsdiff/DsdiffToken.js +4 -7
- package/lib/dsf/DsfChunk.js +8 -11
- package/lib/dsf/DsfParser.js +13 -17
- package/lib/flac/FlacParser.js +22 -26
- package/lib/id3v1/ID3v1Parser.js +16 -21
- package/lib/id3v1/ID3v1TagMap.js +2 -6
- package/lib/id3v2/AbstractID3Parser.js +13 -17
- package/lib/id3v2/FrameParser.js +12 -17
- package/lib/id3v2/ID3v22TagMapper.js +4 -8
- package/lib/id3v2/ID3v24TagMapper.js +5 -9
- package/lib/id3v2/ID3v2Parser.js +10 -14
- package/lib/id3v2/ID3v2Token.js +9 -12
- package/lib/iff/index.js +4 -7
- package/lib/index.js +14 -44
- package/lib/lyrics3/Lyrics3.js +3 -7
- package/lib/matroska/MatroskaDtd.js +111 -114
- package/lib/matroska/MatroskaParser.js +20 -24
- package/lib/matroska/MatroskaTagMapper.js +2 -6
- package/lib/matroska/types.js +6 -9
- package/lib/mp4/Atom.js +4 -8
- package/lib/mp4/AtomToken.js +29 -44
- package/lib/mp4/MP4Parser.js +12 -16
- package/lib/mp4/MP4TagMapper.js +4 -8
- package/lib/mpeg/ExtendedLameHeader.js +6 -9
- package/lib/mpeg/MpegParser.js +17 -21
- package/lib/mpeg/ReplayGainDataFormat.js +2 -5
- package/lib/mpeg/XingTag.js +9 -13
- package/lib/musepack/index.js +10 -12
- package/lib/musepack/sv7/BitReader.js +2 -6
- package/lib/musepack/sv7/MpcSv7Parser.js +9 -13
- package/lib/musepack/sv7/StreamVersion7.js +3 -6
- package/lib/musepack/sv8/MpcSv8Parser.js +9 -13
- package/lib/musepack/sv8/StreamVersion8.js +5 -9
- package/lib/ogg/Ogg.js +1 -2
- package/lib/ogg/OggParser.js +19 -24
- package/lib/ogg/opus/Opus.js +2 -6
- package/lib/ogg/opus/OpusParser.js +4 -8
- package/lib/ogg/speex/Speex.js +3 -6
- package/lib/ogg/speex/SpeexParser.js +5 -9
- package/lib/ogg/theora/Theora.js +2 -5
- package/lib/ogg/theora/TheoraParser.js +5 -9
- package/lib/ogg/vorbis/Vorbis.js +6 -10
- package/lib/ogg/vorbis/VorbisDecoder.js +2 -6
- package/lib/ogg/vorbis/VorbisParser.js +18 -22
- package/lib/ogg/vorbis/VorbisTagMapper.js +3 -7
- package/lib/riff/RiffChunk.js +3 -7
- package/lib/riff/RiffInfoTagMap.js +4 -8
- package/lib/type.js +1 -5
- package/lib/wav/BwfChunk.js +8 -11
- package/lib/wav/WaveChunk.js +4 -9
- package/lib/wav/WaveParser.js +17 -21
- package/lib/wavpack/WavPackParser.js +17 -21
- package/lib/wavpack/WavPackToken.js +4 -8
- package/package.json +23 -33
- package/lib/ParserFactory.d.ts +0 -48
- package/lib/aiff/AiffParser.d.ts +0 -14
- package/lib/aiff/AiffToken.d.ts +0 -22
- package/lib/apev2/APEv2Parser.d.ts +0 -30
- package/lib/apev2/APEv2TagMapper.d.ts +0 -4
- package/lib/apev2/APEv2Token.d.ts +0 -100
- package/lib/asf/AsfObject.d.ts +0 -319
- package/lib/asf/AsfParser.d.ts +0 -17
- package/lib/asf/AsfTagMapper.d.ts +0 -7
- package/lib/asf/AsfUtil.d.ts +0 -13
- package/lib/asf/GUID.d.ts +0 -84
- package/lib/common/BasicParser.d.ts +0 -17
- package/lib/common/CaseInsensitiveTagMap.d.ts +0 -10
- package/lib/common/CombinedTagMapper.d.ts +0 -19
- package/lib/common/FourCC.d.ts +0 -6
- package/lib/common/GenericTagMapper.d.ts +0 -51
- package/lib/common/GenericTagTypes.d.ts +0 -33
- package/lib/common/MetadataCollector.d.ts +0 -76
- package/lib/common/RandomFileReader.d.ts +0 -22
- package/lib/common/RandomUint8ArrayReader.d.ts +0 -18
- package/lib/common/Util.d.ts +0 -57
- package/lib/core.d.ts +0 -48
- package/lib/dsdiff/DsdiffParser.d.ts +0 -14
- package/lib/dsdiff/DsdiffToken.d.ts +0 -9
- package/lib/dsf/DsfChunk.d.ts +0 -86
- package/lib/dsf/DsfParser.d.ts +0 -9
- package/lib/flac/FlacParser.d.ts +0 -28
- package/lib/id3v1/ID3v1Parser.d.ts +0 -13
- package/lib/id3v1/ID3v1TagMap.d.ts +0 -4
- package/lib/id3v2/AbstractID3Parser.d.ts +0 -17
- package/lib/id3v2/FrameParser.d.ts +0 -31
- package/lib/id3v2/ID3v22TagMapper.d.ts +0 -9
- package/lib/id3v2/ID3v24TagMapper.d.ts +0 -14
- package/lib/id3v2/ID3v2Parser.d.ts +0 -28
- package/lib/id3v2/ID3v2Token.d.ts +0 -73
- package/lib/iff/index.d.ts +0 -33
- package/lib/index.d.ts +0 -45
- package/lib/lyrics3/Lyrics3.d.ts +0 -3
- package/lib/matroska/MatroskaDtd.d.ts +0 -8
- package/lib/matroska/MatroskaParser.d.ts +0 -37
- package/lib/matroska/MatroskaTagMapper.d.ts +0 -4
- package/lib/matroska/types.d.ts +0 -175
- package/lib/mp4/Atom.d.ts +0 -16
- package/lib/mp4/AtomToken.d.ts +0 -395
- package/lib/mp4/MP4Parser.d.ts +0 -30
- package/lib/mp4/MP4TagMapper.d.ts +0 -5
- package/lib/mpeg/ExtendedLameHeader.d.ts +0 -27
- package/lib/mpeg/MpegParser.d.ts +0 -49
- package/lib/mpeg/ReplayGainDataFormat.d.ts +0 -55
- package/lib/mpeg/XingTag.d.ts +0 -45
- package/lib/musepack/index.d.ts +0 -5
- package/lib/musepack/sv7/BitReader.d.ts +0 -13
- package/lib/musepack/sv7/MpcSv7Parser.d.ts +0 -8
- package/lib/musepack/sv7/StreamVersion7.d.ts +0 -28
- package/lib/musepack/sv8/MpcSv8Parser.d.ts +0 -6
- package/lib/musepack/sv8/StreamVersion8.d.ts +0 -40
- package/lib/ogg/Ogg.d.ts +0 -72
- package/lib/ogg/OggParser.d.ts +0 -23
- package/lib/ogg/opus/Opus.d.ts +0 -48
- package/lib/ogg/opus/OpusParser.d.ts +0 -25
- package/lib/ogg/speex/Speex.d.ts +0 -36
- package/lib/ogg/speex/SpeexParser.d.ts +0 -22
- package/lib/ogg/theora/Theora.d.ts +0 -20
- package/lib/ogg/theora/TheoraParser.d.ts +0 -28
- package/lib/ogg/vorbis/Vorbis.d.ts +0 -69
- package/lib/ogg/vorbis/VorbisDecoder.d.ts +0 -12
- package/lib/ogg/vorbis/VorbisParser.d.ts +0 -36
- package/lib/ogg/vorbis/VorbisTagMapper.d.ts +0 -7
- package/lib/riff/RiffChunk.d.ts +0 -16
- package/lib/riff/RiffInfoTagMap.d.ts +0 -10
- package/lib/type.d.ts +0 -592
- package/lib/wav/BwfChunk.d.ts +0 -17
- package/lib/wav/WaveChunk.d.ts +0 -64
- package/lib/wav/WaveParser.d.ts +0 -24
- package/lib/wavpack/WavPackParser.d.ts +0 -14
- package/lib/wavpack/WavPackToken.d.ts +0 -64
package/README.md
CHANGED
|
@@ -77,8 +77,9 @@ Support for encoding / format details:
|
|
|
77
77
|
|
|
78
78
|
## Compatibility
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
Module: version 8 migrated from [CommonJS](https://en.wikipedia.org/wiki/CommonJS) to [pure ECMAScript Module (ESM)](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).
|
|
81
|
+
JavaScript is compliant with [ECMAScript 2019 (ES10)](https://en.wikipedia.org/wiki/ECMAScript#10th_Edition_%E2%80%93_ECMAScript_2019).
|
|
82
|
+
Requires Node.js ≥ 12.20 engine.
|
|
82
83
|
|
|
83
84
|
### Browser Support
|
|
84
85
|
|
|
@@ -114,15 +115,11 @@ yarn add music-metadata
|
|
|
114
115
|
|
|
115
116
|
### Import music-metadata
|
|
116
117
|
|
|
117
|
-
Import music-metadata
|
|
118
|
+
Import music-metadata:
|
|
118
119
|
```JavaScript
|
|
119
|
-
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
This is how it's done in TypeScript:
|
|
123
|
-
```ts
|
|
124
|
-
import * as mm from 'music-metadata';
|
|
120
|
+
import { parseFile } from 'music-metadata';
|
|
125
121
|
```
|
|
122
|
+
Import the methods you need, like `parseFile` in this example.
|
|
126
123
|
|
|
127
124
|
### Module Functions
|
|
128
125
|
|
|
@@ -136,19 +133,19 @@ Direct file access tends to be a little faster, because it can 'jump' to various
|
|
|
136
133
|
|
|
137
134
|
Parses the specified file (`filePath`) and returns a promise with the metadata result (`IAudioMetadata`).
|
|
138
135
|
|
|
139
|
-
```
|
|
136
|
+
```ts
|
|
140
137
|
parseFile(filePath: string, opts: IOptions = {}): Promise<IAudioMetadata>`
|
|
141
138
|
```
|
|
142
139
|
|
|
143
140
|
Example:
|
|
144
141
|
```js
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
import { parseFile } from 'music-metadata';
|
|
143
|
+
import { inspect } from 'util';
|
|
147
144
|
|
|
148
145
|
(async () => {
|
|
149
146
|
try {
|
|
150
|
-
const metadata = await
|
|
151
|
-
console.log(
|
|
147
|
+
const metadata = await parseFile('../music-metadata/test/samples/MusicBrainz - Beth Hart - Sinner\'s Prayer [id3v2.3].V2.mp3');
|
|
148
|
+
console.log(inspect(metadata, { showHidden: false, depth: null }));
|
|
152
149
|
} catch (error) {
|
|
153
150
|
console.error(error.message);
|
|
154
151
|
}
|
|
@@ -168,11 +165,11 @@ parseStream(stream: Stream.Readable, fileInfo?: IFileInfo | string, opts?: IOpti
|
|
|
168
165
|
|
|
169
166
|
Example:
|
|
170
167
|
```js
|
|
171
|
-
|
|
168
|
+
import { parseStream } from 'music-metadata';
|
|
172
169
|
|
|
173
170
|
(async () => {
|
|
174
171
|
try {
|
|
175
|
-
const metadata = await
|
|
172
|
+
const metadata = await parseStream(someReadStream, {mimeType: 'audio/mpeg', size: 26838});
|
|
176
173
|
console.log(metadata);
|
|
177
174
|
} catch (error) {
|
|
178
175
|
console.error(error.message);
|
|
@@ -190,9 +187,11 @@ parseBuffer(buffer: Buffer, fileInfo?: IFileInfo | string, opts?: IOptions = {})
|
|
|
190
187
|
|
|
191
188
|
Example:
|
|
192
189
|
```js
|
|
190
|
+
import { parseBuffer } from 'music-metadata';
|
|
191
|
+
|
|
193
192
|
(async () => {
|
|
194
193
|
try {
|
|
195
|
-
const metadata =
|
|
194
|
+
const metadata = parseBuffer(someBuffer, 'audio/mpeg');
|
|
196
195
|
console.log(metadata);
|
|
197
196
|
} catch (error) {
|
|
198
197
|
console.error(error.message);
|
|
@@ -215,6 +214,21 @@ Utility to Converts the native tags to a dictionary index on the tag identifier
|
|
|
215
214
|
orderTags(nativeTags: ITag[]): [tagId: string]: any[]
|
|
216
215
|
```
|
|
217
216
|
|
|
217
|
+
```js
|
|
218
|
+
import { parseFile, orderTags } from 'music-metadata';
|
|
219
|
+
import { inspect } from 'util';
|
|
220
|
+
|
|
221
|
+
(async () => {
|
|
222
|
+
try {
|
|
223
|
+
const metadata = await parseFile('../test/samples/MusicBrainz - Beth Hart - Sinner\'s Prayer [id3v2.3].V2.mp3');
|
|
224
|
+
const orderedTags = orderTags(metadata.native['ID3v2.3']);
|
|
225
|
+
console.log(inspect(orderedTags, { showHidden: false, depth: null }));
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.error(error.message);
|
|
228
|
+
}
|
|
229
|
+
})();
|
|
230
|
+
```
|
|
231
|
+
|
|
218
232
|
#### ratingToStars function
|
|
219
233
|
|
|
220
234
|
Can be used to convert the normalized rating value to the 0..5 stars, where 0 an undefined rating, 1 the star the lowest rating and 5 the highest rating.
|
|
@@ -231,11 +245,11 @@ export function selectCover(pictures?: IPicture[]): IPicture | null
|
|
|
231
245
|
```
|
|
232
246
|
|
|
233
247
|
```js
|
|
234
|
-
import
|
|
248
|
+
import { parseFile, selectCover } from 'music-metadata';
|
|
235
249
|
|
|
236
250
|
(async () => {
|
|
237
|
-
const {common} = await
|
|
238
|
-
const cover =
|
|
251
|
+
const {common} = await parseFile(filePath);
|
|
252
|
+
const cover = selectCover(common.picture); // pick the cover image
|
|
239
253
|
}
|
|
240
254
|
)();
|
|
241
255
|
```
|
|
@@ -327,11 +341,15 @@ Audio format information. Defined in the TypeScript `IFormat` interface:
|
|
|
327
341
|
|
|
328
342
|
In order to read the duration of a stream (with the exception of file streams), in some cases you should pass the size of the file in bytes.
|
|
329
343
|
```js
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
344
|
+
import { parseStream } from 'music-metadata';
|
|
345
|
+
import { inspect } from 'util';
|
|
346
|
+
|
|
347
|
+
(async () => {
|
|
348
|
+
const metadata = await parseStream(someReadStream, {mimeType: 'audio/mpeg', size: 26838}, {duration: true});
|
|
349
|
+
console.log(inspect(metadata, {showHidden: false, depth: null}));
|
|
350
|
+
someReadStream.close();
|
|
351
|
+
}
|
|
352
|
+
)();
|
|
335
353
|
```
|
|
336
354
|
|
|
337
355
|
### Access cover art
|
|
@@ -379,20 +397,18 @@ img.src = `data:${picture.format};base64,${picture.data.toString('base64')}`;
|
|
|
379
397
|
1. Using recursion
|
|
380
398
|
|
|
381
399
|
```js
|
|
382
|
-
|
|
400
|
+
import { parseFile } from 'music-metadata';
|
|
383
401
|
|
|
384
402
|
function parseFiles(audioFiles) {
|
|
385
403
|
|
|
386
404
|
const audioFile = audioFiles.shift();
|
|
387
405
|
|
|
388
406
|
if (audioFile) {
|
|
389
|
-
return
|
|
407
|
+
return parseFile(audioFile).then(metadata => {
|
|
390
408
|
// Do great things with the metadata
|
|
391
409
|
return parseFiles(audioFiles); // process rest of the files AFTER we are finished
|
|
392
410
|
})
|
|
393
411
|
}
|
|
394
|
-
|
|
395
|
-
return Promise.resolve();
|
|
396
412
|
}
|
|
397
413
|
|
|
398
414
|
```
|
|
@@ -402,7 +418,7 @@ img.src = `data:${picture.format};base64,${picture.data.toString('base64')}`;
|
|
|
402
418
|
Use [async/await](https://javascript.info/async-await)
|
|
403
419
|
|
|
404
420
|
```js
|
|
405
|
-
|
|
421
|
+
import { parseFile } from 'music-metadata';
|
|
406
422
|
|
|
407
423
|
// it is required to declare the function 'async' to allow the use of await
|
|
408
424
|
async function parseFiles(audioFiles) {
|
|
@@ -410,7 +426,7 @@ img.src = `data:${picture.format};base64,${picture.data.toString('base64')}`;
|
|
|
410
426
|
for (const audioFile of audioFiles) {
|
|
411
427
|
|
|
412
428
|
// await will ensure the metadata parsing is completed before we move on to the next file
|
|
413
|
-
const metadata = await
|
|
429
|
+
const metadata = await parseFile(audioFile);
|
|
414
430
|
// Do great things with the metadata
|
|
415
431
|
}
|
|
416
432
|
}
|
package/lib/ParserFactory.js
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const debug = (0, debug_1.default)('music-metadata:parser:factory');
|
|
23
|
-
function parseHttpContentType(contentType) {
|
|
1
|
+
import { fileTypeFromBuffer } from 'file-type';
|
|
2
|
+
import ContentType from 'content-type';
|
|
3
|
+
import MimeType from 'media-typer';
|
|
4
|
+
import initDebug from 'debug';
|
|
5
|
+
import { Buffer } from 'node:buffer';
|
|
6
|
+
import { MetadataCollector } from './common/MetadataCollector.js';
|
|
7
|
+
import { AIFFParser } from './aiff/AiffParser.js';
|
|
8
|
+
import { APEv2Parser } from './apev2/APEv2Parser.js';
|
|
9
|
+
import { AsfParser } from './asf/AsfParser.js';
|
|
10
|
+
import { FlacParser } from './flac/FlacParser.js';
|
|
11
|
+
import { MP4Parser } from './mp4/MP4Parser.js';
|
|
12
|
+
import { MpegParser } from './mpeg/MpegParser.js';
|
|
13
|
+
import MusepackParser from './musepack/index.js';
|
|
14
|
+
import { OggParser } from './ogg/OggParser.js';
|
|
15
|
+
import { WaveParser } from './wav/WaveParser.js';
|
|
16
|
+
import { WavPackParser } from './wavpack/WavPackParser.js';
|
|
17
|
+
import { DsfParser } from './dsf/DsfParser.js';
|
|
18
|
+
import { DsdiffParser } from './dsdiff/DsdiffParser.js';
|
|
19
|
+
import { MatroskaParser } from './matroska/MatroskaParser.js';
|
|
20
|
+
const debug = initDebug('music-metadata:parser:factory');
|
|
21
|
+
export function parseHttpContentType(contentType) {
|
|
24
22
|
const type = ContentType.parse(contentType);
|
|
25
23
|
const mime = MimeType.parse(type.type);
|
|
26
24
|
return {
|
|
@@ -30,15 +28,14 @@ function parseHttpContentType(contentType) {
|
|
|
30
28
|
parameters: type.parameters
|
|
31
29
|
};
|
|
32
30
|
}
|
|
33
|
-
exports.parseHttpContentType = parseHttpContentType;
|
|
34
31
|
async function parse(tokenizer, parserId, opts = {}) {
|
|
35
32
|
// Parser found, execute parser
|
|
36
33
|
const parser = await ParserFactory.loadParser(parserId);
|
|
37
|
-
const metadata = new
|
|
34
|
+
const metadata = new MetadataCollector(opts);
|
|
38
35
|
await parser.init(metadata, tokenizer, opts).parse();
|
|
39
36
|
return metadata.toCommonMetadata();
|
|
40
37
|
}
|
|
41
|
-
class ParserFactory {
|
|
38
|
+
export class ParserFactory {
|
|
42
39
|
/**
|
|
43
40
|
* Parse metadata from tokenizer
|
|
44
41
|
* @param tokenizer - Tokenizer
|
|
@@ -64,7 +61,7 @@ class ParserFactory {
|
|
|
64
61
|
parserId = this.getParserIdForExtension(tokenizer.fileInfo.path);
|
|
65
62
|
}
|
|
66
63
|
if (!parserId) {
|
|
67
|
-
const guessedType = await
|
|
64
|
+
const guessedType = await fileTypeFromBuffer(buf);
|
|
68
65
|
if (!guessedType) {
|
|
69
66
|
throw new Error('Failed to determine audio format');
|
|
70
67
|
}
|
|
@@ -142,21 +139,21 @@ class ParserFactory {
|
|
|
142
139
|
}
|
|
143
140
|
static async loadParser(moduleName) {
|
|
144
141
|
switch (moduleName) {
|
|
145
|
-
case 'aiff': return new
|
|
142
|
+
case 'aiff': return new AIFFParser();
|
|
146
143
|
case 'adts':
|
|
147
144
|
case 'mpeg':
|
|
148
|
-
return new
|
|
149
|
-
case 'apev2': return new
|
|
150
|
-
case 'asf': return new
|
|
151
|
-
case 'dsf': return new
|
|
152
|
-
case 'dsdiff': return new
|
|
153
|
-
case 'flac': return new
|
|
154
|
-
case 'mp4': return new
|
|
155
|
-
case 'musepack': return new
|
|
156
|
-
case 'ogg': return new
|
|
157
|
-
case 'riff': return new
|
|
158
|
-
case 'wavpack': return new
|
|
159
|
-
case 'matroska': return new
|
|
145
|
+
return new MpegParser();
|
|
146
|
+
case 'apev2': return new APEv2Parser();
|
|
147
|
+
case 'asf': return new AsfParser();
|
|
148
|
+
case 'dsf': return new DsfParser();
|
|
149
|
+
case 'dsdiff': return new DsdiffParser();
|
|
150
|
+
case 'flac': return new FlacParser();
|
|
151
|
+
case 'mp4': return new MP4Parser();
|
|
152
|
+
case 'musepack': return new MusepackParser();
|
|
153
|
+
case 'ogg': return new OggParser();
|
|
154
|
+
case 'riff': return new WaveParser();
|
|
155
|
+
case 'wavpack': return new WavPackParser();
|
|
156
|
+
case 'matroska': return new MatroskaParser();
|
|
160
157
|
default:
|
|
161
158
|
throw new Error(`Unknown parser type: ${moduleName}`);
|
|
162
159
|
}
|
|
@@ -249,4 +246,3 @@ class ParserFactory {
|
|
|
249
246
|
}
|
|
250
247
|
}
|
|
251
248
|
}
|
|
252
|
-
exports.ParserFactory = ParserFactory;
|
package/lib/aiff/AiffParser.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const AiffToken = require("./AiffToken");
|
|
11
|
-
const iff = require("../iff");
|
|
12
|
-
const debug = (0, debug_1.default)('music-metadata:parser:aiff');
|
|
1
|
+
import * as Token from 'token-types';
|
|
2
|
+
import initDebug from 'debug';
|
|
3
|
+
import * as strtok3 from 'strtok3/core';
|
|
4
|
+
import { ID3v2Parser } from '../id3v2/ID3v2Parser.js';
|
|
5
|
+
import { FourCcToken } from '../common/FourCC.js';
|
|
6
|
+
import { BasicParser } from '../common/BasicParser.js';
|
|
7
|
+
import * as AiffToken from './AiffToken.js';
|
|
8
|
+
import * as iff from '../iff/index.js';
|
|
9
|
+
const debug = initDebug('music-metadata:parser:aiff');
|
|
13
10
|
const compressionTypes = {
|
|
14
11
|
NONE: 'not compressed PCM Apple Computer',
|
|
15
12
|
sowt: 'PCM (byte swapped)',
|
|
@@ -28,12 +25,12 @@ const compressionTypes = {
|
|
|
28
25
|
* - http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/AIFF.html
|
|
29
26
|
* - http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/Docs/AIFF-1.3.pdf
|
|
30
27
|
*/
|
|
31
|
-
class AIFFParser extends
|
|
28
|
+
export class AIFFParser extends BasicParser {
|
|
32
29
|
async parse() {
|
|
33
30
|
const header = await this.tokenizer.readToken(iff.Header);
|
|
34
31
|
if (header.chunkID !== 'FORM')
|
|
35
32
|
throw new Error('Invalid Chunk-ID, expected \'FORM\''); // Not AIFF format
|
|
36
|
-
const type = await this.tokenizer.readToken(
|
|
33
|
+
const type = await this.tokenizer.readToken(FourCcToken);
|
|
37
34
|
switch (type) {
|
|
38
35
|
case 'AIFF':
|
|
39
36
|
this.metadata.setFormat('container', type);
|
|
@@ -81,7 +78,7 @@ class AIFFParser extends BasicParser_1.BasicParser {
|
|
|
81
78
|
case 'ID3 ': // ID3-meta-data
|
|
82
79
|
const id3_data = await this.tokenizer.readToken(new Token.Uint8ArrayType(header.chunkSize));
|
|
83
80
|
const rst = strtok3.fromBuffer(id3_data);
|
|
84
|
-
await new
|
|
81
|
+
await new ID3v2Parser().parse(this.metadata, rst, this.options);
|
|
85
82
|
return header.chunkSize;
|
|
86
83
|
case 'SSND': // Sound Data Chunk
|
|
87
84
|
if (this.metadata.format.duration) {
|
|
@@ -93,4 +90,3 @@ class AIFFParser extends BasicParser_1.BasicParser {
|
|
|
93
90
|
}
|
|
94
91
|
}
|
|
95
92
|
}
|
|
96
|
-
exports.AIFFParser = AIFFParser;
|
package/lib/aiff/AiffToken.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const Token = require("token-types");
|
|
5
|
-
const FourCC_1 = require("../common/FourCC");
|
|
6
|
-
class Common {
|
|
1
|
+
import * as Token from 'token-types';
|
|
2
|
+
import { FourCcToken } from '../common/FourCC.js';
|
|
3
|
+
export class Common {
|
|
7
4
|
constructor(header, isAifc) {
|
|
8
5
|
this.isAifc = isAifc;
|
|
9
6
|
const minimumChunkSize = isAifc ? 22 : 18;
|
|
@@ -22,7 +19,7 @@ class Common {
|
|
|
22
19
|
sampleRate: shift < 0 ? baseSampleRate >> Math.abs(shift) : baseSampleRate << shift
|
|
23
20
|
};
|
|
24
21
|
if (this.isAifc) {
|
|
25
|
-
res.compressionType =
|
|
22
|
+
res.compressionType = FourCcToken.get(buf, off + 18);
|
|
26
23
|
if (this.len > 22) {
|
|
27
24
|
const strLen = buf.readInt8(off + 22);
|
|
28
25
|
if (strLen > 0) {
|
|
@@ -45,4 +42,3 @@ class Common {
|
|
|
45
42
|
return res;
|
|
46
43
|
}
|
|
47
44
|
}
|
|
48
|
-
exports.Common = Common;
|
package/lib/apev2/APEv2Parser.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const APEv2Token_1 = require("./APEv2Token");
|
|
10
|
-
const debug = (0, debug_1.default)('music-metadata:parser:APEv2');
|
|
1
|
+
import initDebug from 'debug';
|
|
2
|
+
import * as strtok3 from 'strtok3/core';
|
|
3
|
+
import { StringType } from 'token-types';
|
|
4
|
+
import { Buffer } from 'node:buffer';
|
|
5
|
+
import * as util from '../common/Util.js';
|
|
6
|
+
import { BasicParser } from '../common/BasicParser.js';
|
|
7
|
+
import { DataType, DescriptorParser, Header, TagFooter, TagItemHeader } from './APEv2Token.js';
|
|
8
|
+
const debug = initDebug('music-metadata:parser:APEv2');
|
|
11
9
|
const tagFormat = 'APEv2';
|
|
12
10
|
const preamble = 'APETAGEX';
|
|
13
|
-
class APEv2Parser extends
|
|
11
|
+
export class APEv2Parser extends BasicParser {
|
|
14
12
|
constructor() {
|
|
15
13
|
super(...arguments);
|
|
16
14
|
this.ape = {};
|
|
@@ -37,16 +35,16 @@ class APEv2Parser extends BasicParser_1.BasicParser {
|
|
|
37
35
|
*/
|
|
38
36
|
static async findApeFooterOffset(reader, offset) {
|
|
39
37
|
// Search for APE footer header at the end of the file
|
|
40
|
-
const apeBuf = Buffer.alloc(
|
|
41
|
-
await reader.randomRead(apeBuf, 0,
|
|
42
|
-
const tagFooter =
|
|
38
|
+
const apeBuf = Buffer.alloc(TagFooter.len);
|
|
39
|
+
await reader.randomRead(apeBuf, 0, TagFooter.len, offset - TagFooter.len);
|
|
40
|
+
const tagFooter = TagFooter.get(apeBuf, 0);
|
|
43
41
|
if (tagFooter.ID === 'APETAGEX') {
|
|
44
42
|
debug(`APE footer header at offset=${offset}`);
|
|
45
43
|
return { footer: tagFooter, offset: offset - tagFooter.size };
|
|
46
44
|
}
|
|
47
45
|
}
|
|
48
46
|
static parseTagFooter(metadata, buffer, options) {
|
|
49
|
-
const footer =
|
|
47
|
+
const footer = TagFooter.get(buffer, buffer.length - TagFooter.len);
|
|
50
48
|
if (footer.ID !== preamble)
|
|
51
49
|
throw new Error('Unexpected APEv2 Footer ID preamble value.');
|
|
52
50
|
strtok3.fromBuffer(buffer);
|
|
@@ -58,13 +56,13 @@ class APEv2Parser extends BasicParser_1.BasicParser {
|
|
|
58
56
|
* Parse APEv1 / APEv2 header if header signature found
|
|
59
57
|
*/
|
|
60
58
|
async tryParseApeHeader() {
|
|
61
|
-
if (this.tokenizer.fileInfo.size && this.tokenizer.fileInfo.size - this.tokenizer.position <
|
|
59
|
+
if (this.tokenizer.fileInfo.size && this.tokenizer.fileInfo.size - this.tokenizer.position < TagFooter.len) {
|
|
62
60
|
debug(`No APEv2 header found, end-of-file reached`);
|
|
63
61
|
return;
|
|
64
62
|
}
|
|
65
|
-
const footer = await this.tokenizer.peekToken(
|
|
63
|
+
const footer = await this.tokenizer.peekToken(TagFooter);
|
|
66
64
|
if (footer.ID === preamble) {
|
|
67
|
-
await this.tokenizer.ignore(
|
|
65
|
+
await this.tokenizer.ignore(TagFooter.len);
|
|
68
66
|
return this.parseTags(footer);
|
|
69
67
|
}
|
|
70
68
|
else {
|
|
@@ -79,42 +77,42 @@ class APEv2Parser extends BasicParser_1.BasicParser {
|
|
|
79
77
|
}
|
|
80
78
|
}
|
|
81
79
|
async parse() {
|
|
82
|
-
const descriptor = await this.tokenizer.readToken(
|
|
80
|
+
const descriptor = await this.tokenizer.readToken(DescriptorParser);
|
|
83
81
|
if (descriptor.ID !== 'MAC ')
|
|
84
82
|
throw new Error('Unexpected descriptor ID');
|
|
85
83
|
this.ape.descriptor = descriptor;
|
|
86
|
-
const lenExp = descriptor.descriptorBytes -
|
|
84
|
+
const lenExp = descriptor.descriptorBytes - DescriptorParser.len;
|
|
87
85
|
const header = await (lenExp > 0 ? this.parseDescriptorExpansion(lenExp) : this.parseHeader());
|
|
88
86
|
await this.tokenizer.ignore(header.forwardBytes);
|
|
89
87
|
return this.tryParseApeHeader();
|
|
90
88
|
}
|
|
91
89
|
async parseTags(footer) {
|
|
92
90
|
const keyBuffer = Buffer.alloc(256); // maximum tag key length
|
|
93
|
-
let bytesRemaining = footer.size -
|
|
91
|
+
let bytesRemaining = footer.size - TagFooter.len;
|
|
94
92
|
debug(`Parse APE tags at offset=${this.tokenizer.position}, size=${bytesRemaining}`);
|
|
95
93
|
for (let i = 0; i < footer.fields; i++) {
|
|
96
|
-
if (bytesRemaining <
|
|
94
|
+
if (bytesRemaining < TagItemHeader.len) {
|
|
97
95
|
this.metadata.addWarning(`APEv2 Tag-header: ${footer.fields - i} items remaining, but no more tag data to read.`);
|
|
98
96
|
break;
|
|
99
97
|
}
|
|
100
98
|
// Only APEv2 tag has tag item headers
|
|
101
|
-
const tagItemHeader = await this.tokenizer.readToken(
|
|
102
|
-
bytesRemaining -=
|
|
99
|
+
const tagItemHeader = await this.tokenizer.readToken(TagItemHeader);
|
|
100
|
+
bytesRemaining -= TagItemHeader.len + tagItemHeader.size;
|
|
103
101
|
await this.tokenizer.peekBuffer(keyBuffer, { length: Math.min(keyBuffer.length, bytesRemaining) });
|
|
104
102
|
let zero = util.findZero(keyBuffer, 0, keyBuffer.length);
|
|
105
|
-
const key = await this.tokenizer.readToken(new
|
|
103
|
+
const key = await this.tokenizer.readToken(new StringType(zero, 'ascii'));
|
|
106
104
|
await this.tokenizer.ignore(1);
|
|
107
105
|
bytesRemaining -= key.length + 1;
|
|
108
106
|
switch (tagItemHeader.flags.dataType) {
|
|
109
|
-
case
|
|
110
|
-
const value = await this.tokenizer.readToken(new
|
|
107
|
+
case DataType.text_utf8: { // utf-8 text-string
|
|
108
|
+
const value = await this.tokenizer.readToken(new StringType(tagItemHeader.size, 'utf8'));
|
|
111
109
|
const values = value.split(/\x00/g);
|
|
112
110
|
for (const val of values) {
|
|
113
111
|
this.metadata.addTag(tagFormat, key, val);
|
|
114
112
|
}
|
|
115
113
|
break;
|
|
116
114
|
}
|
|
117
|
-
case
|
|
115
|
+
case DataType.binary: // binary (probably artwork)
|
|
118
116
|
if (this.options.skipCovers) {
|
|
119
117
|
await this.tokenizer.ignore(tagItemHeader.size);
|
|
120
118
|
}
|
|
@@ -130,11 +128,11 @@ class APEv2Parser extends BasicParser_1.BasicParser {
|
|
|
130
128
|
});
|
|
131
129
|
}
|
|
132
130
|
break;
|
|
133
|
-
case
|
|
131
|
+
case DataType.external_info:
|
|
134
132
|
debug(`Ignore external info ${key}`);
|
|
135
133
|
await this.tokenizer.ignore(tagItemHeader.size);
|
|
136
134
|
break;
|
|
137
|
-
case
|
|
135
|
+
case DataType.reserved:
|
|
138
136
|
debug(`Ignore external info ${key}`);
|
|
139
137
|
this.metadata.addWarning(`APEv2 header declares a reserved datatype for "${key}"`);
|
|
140
138
|
await this.tokenizer.ignore(tagItemHeader.size);
|
|
@@ -147,7 +145,7 @@ class APEv2Parser extends BasicParser_1.BasicParser {
|
|
|
147
145
|
return this.parseHeader();
|
|
148
146
|
}
|
|
149
147
|
async parseHeader() {
|
|
150
|
-
const header = await this.tokenizer.readToken(
|
|
148
|
+
const header = await this.tokenizer.readToken(Header);
|
|
151
149
|
// ToDo before
|
|
152
150
|
this.metadata.setFormat('lossless', true);
|
|
153
151
|
this.metadata.setFormat('container', 'Monkey\'s Audio');
|
|
@@ -161,4 +159,3 @@ class APEv2Parser extends BasicParser_1.BasicParser {
|
|
|
161
159
|
};
|
|
162
160
|
}
|
|
163
161
|
}
|
|
164
|
-
exports.APEv2Parser = APEv2Parser;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.APEv2TagMapper = void 0;
|
|
4
|
-
const CaseInsensitiveTagMap_1 = require("../common/CaseInsensitiveTagMap");
|
|
1
|
+
import { CaseInsensitiveTagMap } from '../common/CaseInsensitiveTagMap.js';
|
|
5
2
|
/**
|
|
6
3
|
* ID3v2.2 tag mappings
|
|
7
4
|
*/
|
|
@@ -78,10 +75,9 @@ const apev2TagMap = {
|
|
|
78
75
|
MP3GAIN_MINMAX: 'replaygain_track_minmax',
|
|
79
76
|
MP3GAIN_UNDO: 'replaygain_undo'
|
|
80
77
|
};
|
|
81
|
-
class APEv2TagMapper extends
|
|
78
|
+
export class APEv2TagMapper extends CaseInsensitiveTagMap {
|
|
82
79
|
constructor() {
|
|
83
80
|
super(['APEv2'], apev2TagMap);
|
|
84
81
|
}
|
|
85
82
|
}
|
|
86
|
-
exports.APEv2TagMapper = APEv2TagMapper;
|
|
87
83
|
//# sourceMappingURL=APEv2TagMapper.js.map
|
package/lib/apev2/APEv2Token.js
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const Token = require("token-types");
|
|
5
|
-
const FourCC_1 = require("../common/FourCC");
|
|
6
|
-
var DataType;
|
|
1
|
+
import * as Token from 'token-types';
|
|
2
|
+
import { FourCcToken } from '../common/FourCC.js';
|
|
3
|
+
export var DataType;
|
|
7
4
|
(function (DataType) {
|
|
8
5
|
DataType[DataType["text_utf8"] = 0] = "text_utf8";
|
|
9
6
|
DataType[DataType["binary"] = 1] = "binary";
|
|
10
7
|
DataType[DataType["external_info"] = 2] = "external_info";
|
|
11
8
|
DataType[DataType["reserved"] = 3] = "reserved";
|
|
12
|
-
})(DataType =
|
|
9
|
+
})(DataType = DataType || (DataType = {}));
|
|
13
10
|
/**
|
|
14
11
|
* APE_DESCRIPTOR: defines the sizes (and offsets) of all the pieces, as well as the MD5 checksum
|
|
15
12
|
*/
|
|
16
|
-
|
|
13
|
+
export const DescriptorParser = {
|
|
17
14
|
len: 52,
|
|
18
15
|
get: (buf, off) => {
|
|
19
16
|
return {
|
|
20
17
|
// should equal 'MAC '
|
|
21
|
-
ID:
|
|
18
|
+
ID: FourCcToken.get(buf, off),
|
|
22
19
|
// versionIndex number * 1000 (3.81 = 3810) (remember that 4-byte alignment causes this to take 4-bytes)
|
|
23
20
|
version: Token.UINT32_LE.get(buf, off + 4) / 1000,
|
|
24
21
|
// the number of descriptor bytes (allows later expansion of this header)
|
|
@@ -43,7 +40,7 @@ exports.DescriptorParser = {
|
|
|
43
40
|
/**
|
|
44
41
|
* APE_HEADER: describes all of the necessary information about the APE file
|
|
45
42
|
*/
|
|
46
|
-
|
|
43
|
+
export const Header = {
|
|
47
44
|
len: 24,
|
|
48
45
|
get: (buf, off) => {
|
|
49
46
|
return {
|
|
@@ -70,7 +67,7 @@ exports.Header = {
|
|
|
70
67
|
* APE Tag Header/Footer Version 2.0
|
|
71
68
|
* TAG: describes all the properties of the file [optional]
|
|
72
69
|
*/
|
|
73
|
-
|
|
70
|
+
export const TagFooter = {
|
|
74
71
|
len: 32,
|
|
75
72
|
get: (buf, off) => {
|
|
76
73
|
return {
|
|
@@ -90,7 +87,7 @@ exports.TagFooter = {
|
|
|
90
87
|
/**
|
|
91
88
|
* APE Tag v2.0 Item Header
|
|
92
89
|
*/
|
|
93
|
-
|
|
90
|
+
export const TagItemHeader = {
|
|
94
91
|
len: 8,
|
|
95
92
|
get: (buf, off) => {
|
|
96
93
|
return {
|
|
@@ -101,11 +98,10 @@ exports.TagItemHeader = {
|
|
|
101
98
|
};
|
|
102
99
|
}
|
|
103
100
|
};
|
|
104
|
-
const TagField = footer => {
|
|
105
|
-
return new Token.Uint8ArrayType(footer.size -
|
|
101
|
+
export const TagField = footer => {
|
|
102
|
+
return new Token.Uint8ArrayType(footer.size - TagFooter.len);
|
|
106
103
|
};
|
|
107
|
-
|
|
108
|
-
function parseTagFlags(flags) {
|
|
104
|
+
export function parseTagFlags(flags) {
|
|
109
105
|
return {
|
|
110
106
|
containsHeader: isBitSet(flags, 31),
|
|
111
107
|
containsFooter: isBitSet(flags, 30),
|
|
@@ -114,14 +110,12 @@ function parseTagFlags(flags) {
|
|
|
114
110
|
dataType: (flags & 6) >> 1
|
|
115
111
|
};
|
|
116
112
|
}
|
|
117
|
-
exports.parseTagFlags = parseTagFlags;
|
|
118
113
|
/**
|
|
119
114
|
* @param num {number}
|
|
120
115
|
* @param bit 0 is least significant bit (LSB)
|
|
121
116
|
* @return {boolean} true if bit is 1; otherwise false
|
|
122
117
|
*/
|
|
123
|
-
function isBitSet(num, bit) {
|
|
118
|
+
export function isBitSet(num, bit) {
|
|
124
119
|
return (num & 1 << bit) !== 0;
|
|
125
120
|
}
|
|
126
|
-
exports.isBitSet = isBitSet;
|
|
127
121
|
//# sourceMappingURL=APEv2Token.js.map
|