music-metadata 10.1.0 → 10.2.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 +44 -29
- package/lib/ParserFactory.js +1 -1
- package/lib/aiff/AiffParser.js +2 -3
- package/lib/common/MetadataCollector.js +2 -4
- package/lib/ebml/EbmlIterator.d.ts +52 -0
- package/lib/ebml/EbmlIterator.js +220 -0
- package/lib/ebml/types.d.ts +36 -0
- package/lib/ebml/types.js +10 -0
- package/lib/id3v2/ID3v2Parser.js +2 -3
- package/lib/matroska/MatroskaDtd.d.ts +2 -2
- package/lib/matroska/MatroskaDtd.js +246 -239
- package/lib/matroska/MatroskaParser.d.ts +8 -22
- package/lib/matroska/MatroskaParser.js +119 -204
- package/lib/matroska/types.d.ts +8 -44
- package/lib/matroska/types.js +0 -9
- package/lib/type.d.ts +9 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -11,15 +11,31 @@
|
|
|
11
11
|
|
|
12
12
|
# music-metadata
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
Supports
|
|
14
|
+
Key features:
|
|
15
|
+
* **Comprehensive Format Support**: Supports popular audio formats like MP3, MP4, FLAC, Ogg, WAV, AIFF, and more.
|
|
16
|
+
* **Extensive Metadata Extraction**: Extracts detailed metadata, including ID3v1, ID3v2, APE, Vorbis, and iTunes/MP4 tags.
|
|
17
|
+
* **Streaming Support**: Efficiently handles large audio files by reading metadata from streams, making it suitable for server-side and browser-based applications.
|
|
18
|
+
* **Promise-Based API**: Provides a modern, promise-based API for easy integration into asynchronous workflows.
|
|
19
|
+
* **Cross-Platform**: Works in both [Node.js](https://nodejs.org/) and browser environments with the help of bundlers like [Webpack](https://webpack.js.org/) or [Rollup](https://rollupjs.org/introduction/).
|
|
20
|
+
|
|
21
|
+
The [`music-metadata`](https://github.com/Borewit/music-metadata) module is ideal for developers working on media applications, music players, or any project that requires access to detailed audio file metadata.
|
|
16
22
|
|
|
17
23
|
## Compatibility
|
|
18
24
|
|
|
19
25
|
Module: version 8 migrated from [CommonJS](https://en.wikipedia.org/wiki/CommonJS) to [pure ECMAScript Module (ESM)](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).
|
|
20
|
-
JavaScript is compliant with [ECMAScript
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
The distributed JavaScript codebase is compliant with the [ECMAScript 2020 (11th Edition)](https://en.wikipedia.org/wiki/ECMAScript_version_history#11th_Edition_%E2%80%93_ECMAScript_2020) standard.
|
|
27
|
+
|
|
28
|
+
This module requires a [Node.js ≥ 16](https://nodejs.org/en/about/previous-releases) engine.
|
|
29
|
+
It can also be used in a browser environment when bundled with a module bundler.
|
|
30
|
+
|
|
31
|
+
## Sponsor
|
|
32
|
+
If you appreciate my work and want to support the development of open-source projects like [music-metadata](https://github.com/Borewit/music-metadata), [file-type](https://github.com/sindresorhus/file-type), and [listFix()](https://github.com/Borewit/listFix), consider becoming a sponsor or making a small contribution.
|
|
33
|
+
Your support helps sustain ongoing development and improvements.
|
|
34
|
+
[Become a sponsor to Borewit](https://github.com/sponsors/Borewit)
|
|
35
|
+
|
|
36
|
+
or
|
|
37
|
+
|
|
38
|
+
<a href="https://www.buymeacoffee.com/borewit" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy me A coffee" height="41" width="174"></a>
|
|
23
39
|
|
|
24
40
|
## Features
|
|
25
41
|
|
|
@@ -81,11 +97,6 @@ Support for encoding / format details:
|
|
|
81
97
|
- [<img src="https://cdn.sanity.io/images/3do82whm/next/ba8c847f13a5fa39d88f8bc9b7846b7886531b18-2500x2500.svg" width="40"> Webamp](https://webamp.org/)
|
|
82
98
|
|
|
83
99
|
|
|
84
|
-
### Sponsor
|
|
85
|
-
[Become a sponsor to Borewit](https://github.com/sponsors/Borewit)
|
|
86
|
-
|
|
87
|
-
<a href="https://www.buymeacoffee.com/borewit" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy me A coffee" height="41" width="174"></a>
|
|
88
|
-
|
|
89
100
|
## Dependencies
|
|
90
101
|
|
|
91
102
|
Dependency diagram:
|
|
@@ -192,7 +203,7 @@ An extension (e.g.: `.mp3`), filename or path will also work.
|
|
|
192
203
|
If the MIME-type or filename (via `fileInfo.path`) is not provided, or not understood, music-metadata will try to derive the type from the content.
|
|
193
204
|
|
|
194
205
|
```ts
|
|
195
|
-
parseStream(stream: Stream.Readable, fileInfo?: IFileInfo | string, opts?: IOptions = {}): Promise<IAudioMetadata
|
|
206
|
+
parseStream(stream: Stream.Readable, fileInfo?: IFileInfo | string, opts?: IOptions = {}): Promise<IAudioMetadata>
|
|
196
207
|
```
|
|
197
208
|
|
|
198
209
|
Example:
|
|
@@ -327,7 +338,11 @@ import { parseFile, selectCover } from 'music-metadata';
|
|
|
327
338
|
- `observer: (update: MetadataEvent) => void;`: Will be called after each change to `common` (generic) tag, or `format` properties.
|
|
328
339
|
- `skipCovers`: default: `false`, if set to `true`, it will not return embedded cover-art (images).
|
|
329
340
|
- `skipPostHeaders? boolean` default: `false`, if set to `true`, it will not search all the entire track for additional headers. Only recommenced to use in combination with streams.
|
|
330
|
-
- `
|
|
341
|
+
- `mkvUseIndex` default: `false`, if set to `true`, in Matroska based files, use the _SeekHead_ element index to skip _segment/cluster_ elements..
|
|
342
|
+
_experimental functionality_
|
|
343
|
+
Can have a significant performance impact if enabled.
|
|
344
|
+
Possible side effect can be that certain metadata maybe skipped, depending on the index.
|
|
345
|
+
If there is no _SeekHead_ element present in the Matroska file, this flag has no effect.
|
|
331
346
|
|
|
332
347
|
Although in most cases duration is included, in some cases it requires `music-metadata` parsing the entire file.
|
|
333
348
|
To enforce parsing the entire file if needed you should set `duration` to `true`.
|
|
@@ -362,7 +377,7 @@ Audio format information. Defined in the TypeScript `IFormat` interface:
|
|
|
362
377
|
|
|
363
378
|
#### `metadata.trackInfo`
|
|
364
379
|
|
|
365
|
-
To support advanced containers like [Matroska](https://wikipedia.org/wiki/Matroska) or [MPEG-4](https://en.wikipedia.org/wiki/MPEG-4), which may contain multiple audio and video tracks, the **experimental
|
|
380
|
+
To support advanced containers like [Matroska](https://wikipedia.org/wiki/Matroska) or [MPEG-4](https://en.wikipedia.org/wiki/MPEG-4), which may contain multiple audio and video tracks, the **experimental**- `metadata.trackInfo` has been added,
|
|
366
381
|
|
|
367
382
|
`metadata.trackInfo` is either `undefined` or has an **array** of [trackInfo](#trackinfo)
|
|
368
383
|
|
|
@@ -483,29 +498,29 @@ img.src = `data:${picture.format};base64,${uint8ArrayToBase64(picture.data)}`;
|
|
|
483
498
|
|
|
484
499
|
```
|
|
485
500
|
|
|
486
|
-
|
|
501
|
+
1. Use async/await
|
|
487
502
|
|
|
488
|
-
|
|
503
|
+
Use [async/await](https://javascript.info/async-await)
|
|
489
504
|
|
|
490
|
-
|
|
491
|
-
|
|
505
|
+
```js
|
|
506
|
+
import { parseFile } from 'music-metadata';
|
|
492
507
|
|
|
493
|
-
|
|
494
|
-
|
|
508
|
+
// it is required to declare the function 'async' to allow the use of await
|
|
509
|
+
async function parseFiles(audioFiles) {
|
|
495
510
|
|
|
496
|
-
|
|
511
|
+
for (const audioFile of audioFiles) {
|
|
497
512
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
513
|
+
// await will ensure the metadata parsing is completed before we move on to the next file
|
|
514
|
+
const metadata = await parseFile(audioFile);
|
|
515
|
+
// Do great things with the metadata
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
```
|
|
504
519
|
|
|
505
|
-
|
|
520
|
+
1. Use a specialized module to traverse files
|
|
506
521
|
|
|
507
|
-
|
|
508
|
-
|
|
522
|
+
There are specialized modules to traversing (walking) files and directory,
|
|
523
|
+
like [walk](https://www.npmjs.com/package/walk).
|
|
509
524
|
|
|
510
525
|
## Licence
|
|
511
526
|
|
package/lib/ParserFactory.js
CHANGED
|
@@ -66,7 +66,7 @@ export async function parse(tokenizer, parserId, opts) {
|
|
|
66
66
|
// Parser found, execute parser
|
|
67
67
|
const parser = await loadParser(parserId);
|
|
68
68
|
const metadata = new MetadataCollector(opts);
|
|
69
|
-
await parser.init(metadata, tokenizer, opts
|
|
69
|
+
await parser.init(metadata, tokenizer, opts ?? {}).parse();
|
|
70
70
|
return metadata.toCommonMetadata();
|
|
71
71
|
}
|
|
72
72
|
/**
|
package/lib/aiff/AiffParser.js
CHANGED
|
@@ -57,7 +57,6 @@ export class AIFFParser extends BasicParser {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
async readData(header) {
|
|
60
|
-
var _a;
|
|
61
60
|
switch (header.chunkID) {
|
|
62
61
|
case 'COMM': { // The Common Chunk
|
|
63
62
|
if (this.isCompressed === null) {
|
|
@@ -70,7 +69,7 @@ export class AIFFParser extends BasicParser {
|
|
|
70
69
|
this.metadata.setFormat('numberOfSamples', common.numSampleFrames);
|
|
71
70
|
this.metadata.setFormat('duration', common.numSampleFrames / common.sampleRate);
|
|
72
71
|
if (common.compressionName || common.compressionType) {
|
|
73
|
-
this.metadata.setFormat('codec',
|
|
72
|
+
this.metadata.setFormat('codec', common.compressionName ?? compressionTypes[common.compressionType]);
|
|
74
73
|
}
|
|
75
74
|
return header.chunkSize;
|
|
76
75
|
}
|
|
@@ -97,7 +96,7 @@ export class AIFFParser extends BasicParser {
|
|
|
97
96
|
}
|
|
98
97
|
async readTextChunk(header) {
|
|
99
98
|
const value = await this.tokenizer.readToken(new Token.StringType(header.chunkSize, 'ascii'));
|
|
100
|
-
const values = value.split('\0').map(v => v.trim()).filter(v => v
|
|
99
|
+
const values = value.split('\0').map(v => v.trim()).filter(v => v?.length);
|
|
101
100
|
await Promise.all(values.map(v => this.metadata.addTag('AIFF', header.chunkID, v)));
|
|
102
101
|
return header.chunkSize;
|
|
103
102
|
}
|
|
@@ -54,10 +54,9 @@ export class MetadataCollector {
|
|
|
54
54
|
this.format.trackInfo.push(streamInfo);
|
|
55
55
|
}
|
|
56
56
|
setFormat(key, value) {
|
|
57
|
-
var _a;
|
|
58
57
|
debug(`format: ${key} = ${value}`);
|
|
59
58
|
this.format[key] = value; // as any to override readonly
|
|
60
|
-
if (
|
|
59
|
+
if (this.opts?.observer) {
|
|
61
60
|
this.opts.observer({ metadata: this, tag: { type: 'format', id: key, value } });
|
|
62
61
|
}
|
|
63
62
|
}
|
|
@@ -239,7 +238,6 @@ export class MetadataCollector {
|
|
|
239
238
|
* Set generic tag
|
|
240
239
|
*/
|
|
241
240
|
setGenericTag(tagType, tag) {
|
|
242
|
-
var _a;
|
|
243
241
|
debug(`common.${tag.id} = ${tag.value}`);
|
|
244
242
|
const prio0 = this.commonOrigin[tag.id] || 1000;
|
|
245
243
|
const prio1 = this.originPriority[tagType];
|
|
@@ -270,7 +268,7 @@ export class MetadataCollector {
|
|
|
270
268
|
return debug(`Ignore native tag (list): ${tagType}.${tag.id} = ${tag.value}`);
|
|
271
269
|
}
|
|
272
270
|
}
|
|
273
|
-
if (
|
|
271
|
+
if (this.opts?.observer) {
|
|
274
272
|
this.opts.observer({ metadata: this, tag: { type: 'common', id: tag.id, value: tag.value } });
|
|
275
273
|
}
|
|
276
274
|
// ToDo: trigger metadata event
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type ITokenizer } from 'strtok3';
|
|
2
|
+
import { type IElementType, type ITree, type ValueType } from './types.js';
|
|
3
|
+
export interface ILinkedElementType extends IElementType {
|
|
4
|
+
id: number;
|
|
5
|
+
parent: ILinkedElementType | undefined;
|
|
6
|
+
readonly container?: {
|
|
7
|
+
[id: number]: ILinkedElementType;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export declare enum ParseAction {
|
|
11
|
+
ReadNext = 0,// Continue reading the next elements
|
|
12
|
+
IgnoreElement = 2,// Ignore (do not read) this element
|
|
13
|
+
SkipSiblings = 3,// Skip all remaining elements at the same level
|
|
14
|
+
TerminateParsing = 4,// Terminate the parsing process
|
|
15
|
+
SkipElement = 5
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* @return true, to quit the parser
|
|
19
|
+
*/
|
|
20
|
+
export type IElementListener = {
|
|
21
|
+
startNext: (dtdElement: ILinkedElementType) => ParseAction;
|
|
22
|
+
elementValue: (dtdElement: ILinkedElementType, value: ValueType, offset: number) => Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Extensible Binary Meta Language (EBML) iterator
|
|
26
|
+
* https://en.wikipedia.org/wiki/Extensible_Binary_Meta_Language
|
|
27
|
+
* http://matroska.sourceforge.net/technical/specs/rfc/index.html
|
|
28
|
+
*
|
|
29
|
+
* WEBM VP8 AUDIO FILE
|
|
30
|
+
*/
|
|
31
|
+
export declare class EbmlIterator {
|
|
32
|
+
private tokenizer;
|
|
33
|
+
private padding;
|
|
34
|
+
private parserMap;
|
|
35
|
+
private ebmlMaxIDLength;
|
|
36
|
+
private ebmlMaxSizeLength;
|
|
37
|
+
/**
|
|
38
|
+
* @param {ITokenizer} tokenizer Input
|
|
39
|
+
* @param tokenizer
|
|
40
|
+
*/
|
|
41
|
+
constructor(tokenizer: ITokenizer);
|
|
42
|
+
iterate(dtdElement: IElementType, posDone: number, listener: IElementListener): Promise<ITree>;
|
|
43
|
+
private parseContainer;
|
|
44
|
+
private readVintData;
|
|
45
|
+
private readElement;
|
|
46
|
+
private readFloat;
|
|
47
|
+
private readFlag;
|
|
48
|
+
private readUint;
|
|
49
|
+
private readString;
|
|
50
|
+
private readBuffer;
|
|
51
|
+
}
|
|
52
|
+
export declare function getElementPath(element: ILinkedElementType): string;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { Float32_BE, Float64_BE, StringType, UINT8 } from 'token-types';
|
|
2
|
+
import initDebug from 'debug';
|
|
3
|
+
import { EndOfStreamError } from 'strtok3';
|
|
4
|
+
import { DataType } from './types.js';
|
|
5
|
+
import * as Token from 'token-types';
|
|
6
|
+
const debug = initDebug('music-metadata:parser:ebml');
|
|
7
|
+
export var ParseAction;
|
|
8
|
+
(function (ParseAction) {
|
|
9
|
+
ParseAction[ParseAction["ReadNext"] = 0] = "ReadNext";
|
|
10
|
+
ParseAction[ParseAction["IgnoreElement"] = 2] = "IgnoreElement";
|
|
11
|
+
ParseAction[ParseAction["SkipSiblings"] = 3] = "SkipSiblings";
|
|
12
|
+
ParseAction[ParseAction["TerminateParsing"] = 4] = "TerminateParsing";
|
|
13
|
+
ParseAction[ParseAction["SkipElement"] = 5] = "SkipElement"; // Consider the element has read, assume position is at the next element
|
|
14
|
+
})(ParseAction || (ParseAction = {}));
|
|
15
|
+
/**
|
|
16
|
+
* Extensible Binary Meta Language (EBML) iterator
|
|
17
|
+
* https://en.wikipedia.org/wiki/Extensible_Binary_Meta_Language
|
|
18
|
+
* http://matroska.sourceforge.net/technical/specs/rfc/index.html
|
|
19
|
+
*
|
|
20
|
+
* WEBM VP8 AUDIO FILE
|
|
21
|
+
*/
|
|
22
|
+
export class EbmlIterator {
|
|
23
|
+
/**
|
|
24
|
+
* @param {ITokenizer} tokenizer Input
|
|
25
|
+
* @param tokenizer
|
|
26
|
+
*/
|
|
27
|
+
constructor(tokenizer) {
|
|
28
|
+
this.tokenizer = tokenizer;
|
|
29
|
+
this.padding = 0;
|
|
30
|
+
this.parserMap = new Map();
|
|
31
|
+
this.ebmlMaxIDLength = 4;
|
|
32
|
+
this.ebmlMaxSizeLength = 8;
|
|
33
|
+
this.parserMap.set(DataType.uint, e => this.readUint(e));
|
|
34
|
+
this.parserMap.set(DataType.string, e => this.readString(e));
|
|
35
|
+
this.parserMap.set(DataType.binary, e => this.readBuffer(e));
|
|
36
|
+
this.parserMap.set(DataType.uid, async (e) => this.readBuffer(e));
|
|
37
|
+
this.parserMap.set(DataType.bool, e => this.readFlag(e));
|
|
38
|
+
this.parserMap.set(DataType.float, e => this.readFloat(e));
|
|
39
|
+
}
|
|
40
|
+
async iterate(dtdElement, posDone, listener) {
|
|
41
|
+
return this.parseContainer(linkParents(dtdElement), posDone, listener);
|
|
42
|
+
}
|
|
43
|
+
async parseContainer(dtdElement, posDone, listener) {
|
|
44
|
+
const tree = {};
|
|
45
|
+
while (this.tokenizer.position < posDone) {
|
|
46
|
+
let element;
|
|
47
|
+
const elementPosition = this.tokenizer.position;
|
|
48
|
+
try {
|
|
49
|
+
element = await this.readElement();
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof EndOfStreamError) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
const child = dtdElement.container[element.id];
|
|
58
|
+
if (child) {
|
|
59
|
+
const action = listener.startNext(child);
|
|
60
|
+
switch (action) {
|
|
61
|
+
case ParseAction.ReadNext:
|
|
62
|
+
{
|
|
63
|
+
if (element.id === 0x1F43B675) {
|
|
64
|
+
// Hack to ignore remaining segment, when cluster element received
|
|
65
|
+
// await this.tokenizer.ignore(posDone - this.tokenizer.position);
|
|
66
|
+
// break;
|
|
67
|
+
}
|
|
68
|
+
debug(`Read element: name=${getElementPath(child)}{id=0x${element.id.toString(16)}, container=${!!child.container}} at position=${elementPosition}`);
|
|
69
|
+
if (child.container) {
|
|
70
|
+
const res = await this.parseContainer(child, element.len >= 0 ? this.tokenizer.position + element.len : -1, listener);
|
|
71
|
+
if (child.multiple) {
|
|
72
|
+
if (!tree[child.name]) {
|
|
73
|
+
tree[child.name] = [];
|
|
74
|
+
}
|
|
75
|
+
tree[child.name].push(res);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
tree[child.name] = res;
|
|
79
|
+
}
|
|
80
|
+
await listener.elementValue(child, res, elementPosition);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const parser = this.parserMap.get(child.value);
|
|
84
|
+
if (typeof parser === 'function') {
|
|
85
|
+
const value = await parser(element);
|
|
86
|
+
tree[child.name] = value;
|
|
87
|
+
await listener.elementValue(child, value, elementPosition);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
case ParseAction.SkipElement:
|
|
93
|
+
debug(`Go to next element: name=${getElementPath(child)}, element.id=0x${element.id}, container=${!!child.container} at position=${elementPosition}`);
|
|
94
|
+
break;
|
|
95
|
+
case ParseAction.IgnoreElement:
|
|
96
|
+
debug(`Ignore element: name=${getElementPath(child)}, element.id=0x${element.id}, container=${!!child.container} at position=${elementPosition}`);
|
|
97
|
+
await this.tokenizer.ignore(element.len);
|
|
98
|
+
break;
|
|
99
|
+
case ParseAction.SkipSiblings:
|
|
100
|
+
debug(`Ignore remaining container, at: name=${getElementPath(child)}, element.id=0x${element.id}, container=${!!child.container} at position=${elementPosition}`);
|
|
101
|
+
await this.tokenizer.ignore(posDone - this.tokenizer.position);
|
|
102
|
+
break;
|
|
103
|
+
case ParseAction.TerminateParsing:
|
|
104
|
+
debug(`Terminate parsing at element: name=${getElementPath(child)}, element.id=0x${element.id}, container=${!!child.container} at position=${elementPosition}`);
|
|
105
|
+
return tree;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
switch (element.id) {
|
|
110
|
+
case 0xec: // void
|
|
111
|
+
this.padding += element.len;
|
|
112
|
+
await this.tokenizer.ignore(element.len);
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
debug(`parseEbml: parent=${getElementPath(dtdElement)}, unknown child: id=${element.id.toString(16)} at position=${elementPosition}`);
|
|
116
|
+
this.padding += element.len;
|
|
117
|
+
await this.tokenizer.ignore(element.len);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return tree;
|
|
122
|
+
}
|
|
123
|
+
async readVintData(maxLength) {
|
|
124
|
+
const msb = await this.tokenizer.peekNumber(UINT8);
|
|
125
|
+
let mask = 0x80;
|
|
126
|
+
let oc = 1;
|
|
127
|
+
// Calculate VINT_WIDTH
|
|
128
|
+
while ((msb & mask) === 0) {
|
|
129
|
+
if (oc > maxLength) {
|
|
130
|
+
throw new Error('VINT value exceeding maximum size');
|
|
131
|
+
}
|
|
132
|
+
++oc;
|
|
133
|
+
mask >>= 1;
|
|
134
|
+
}
|
|
135
|
+
const id = new Uint8Array(oc);
|
|
136
|
+
await this.tokenizer.readBuffer(id);
|
|
137
|
+
return id;
|
|
138
|
+
}
|
|
139
|
+
async readElement() {
|
|
140
|
+
const id = await this.readVintData(this.ebmlMaxIDLength);
|
|
141
|
+
const lenField = await this.readVintData(this.ebmlMaxSizeLength);
|
|
142
|
+
lenField[0] ^= 0x80 >> (lenField.length - 1);
|
|
143
|
+
return {
|
|
144
|
+
id: readUIntBE(id, id.length),
|
|
145
|
+
len: readUIntBE(lenField, lenField.length)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
async readFloat(e) {
|
|
149
|
+
switch (e.len) {
|
|
150
|
+
case 0:
|
|
151
|
+
return 0.0;
|
|
152
|
+
case 4:
|
|
153
|
+
return this.tokenizer.readNumber(Float32_BE);
|
|
154
|
+
case 8:
|
|
155
|
+
return this.tokenizer.readNumber(Float64_BE);
|
|
156
|
+
case 10:
|
|
157
|
+
return this.tokenizer.readNumber(Float64_BE);
|
|
158
|
+
default:
|
|
159
|
+
throw new Error(`Invalid IEEE-754 float length: ${e.len}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async readFlag(e) {
|
|
163
|
+
return (await this.readUint(e)) === 1;
|
|
164
|
+
}
|
|
165
|
+
async readUint(e) {
|
|
166
|
+
const buf = await this.readBuffer(e);
|
|
167
|
+
return readUIntBE(buf, e.len);
|
|
168
|
+
}
|
|
169
|
+
async readString(e) {
|
|
170
|
+
const rawString = await this.tokenizer.readToken(new StringType(e.len, 'utf-8'));
|
|
171
|
+
return rawString.replace(/\x00.*$/g, '');
|
|
172
|
+
}
|
|
173
|
+
async readBuffer(e) {
|
|
174
|
+
const buf = new Uint8Array(e.len);
|
|
175
|
+
await this.tokenizer.readBuffer(buf);
|
|
176
|
+
return buf;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function readUIntBE(buf, len) {
|
|
180
|
+
return Number(readUIntBeAsBigInt(buf, len));
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Reeds an unsigned integer from a big endian buffer of length `len`
|
|
184
|
+
* @param buf Buffer to decode from
|
|
185
|
+
* @param len Number of bytes
|
|
186
|
+
* @private
|
|
187
|
+
*/
|
|
188
|
+
function readUIntBeAsBigInt(buf, len) {
|
|
189
|
+
const normalizedNumber = new Uint8Array(8);
|
|
190
|
+
const cleanNumber = buf.subarray(0, len);
|
|
191
|
+
try {
|
|
192
|
+
normalizedNumber.set(cleanNumber, 8 - len);
|
|
193
|
+
return Token.UINT64_BE.get(normalizedNumber, 0);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return BigInt(-1);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function linkParents(element) {
|
|
200
|
+
if (element.container) {
|
|
201
|
+
Object.keys(element.container)
|
|
202
|
+
.map(id => {
|
|
203
|
+
const child = element.container[id];
|
|
204
|
+
child.id = Number.parseInt(id);
|
|
205
|
+
return child;
|
|
206
|
+
}).forEach(child => {
|
|
207
|
+
child.parent = element;
|
|
208
|
+
linkParents(child);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return element;
|
|
212
|
+
}
|
|
213
|
+
export function getElementPath(element) {
|
|
214
|
+
let path = '';
|
|
215
|
+
if (element.parent && element.parent.name !== 'dtd') {
|
|
216
|
+
path += `${getElementPath(element.parent)}/`;
|
|
217
|
+
}
|
|
218
|
+
return path + element.name;
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=EbmlIterator.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface ITree {
|
|
2
|
+
[name: string]: string | number | boolean | Uint8Array | ITree | ITree[];
|
|
3
|
+
}
|
|
4
|
+
export declare enum DataType {
|
|
5
|
+
'string' = 0,
|
|
6
|
+
uint = 1,
|
|
7
|
+
uid = 2,
|
|
8
|
+
bool = 3,
|
|
9
|
+
binary = 4,
|
|
10
|
+
float = 5
|
|
11
|
+
}
|
|
12
|
+
export type ValueType = string | number | Uint8Array | boolean | ITree | ITree[];
|
|
13
|
+
export interface IHeader {
|
|
14
|
+
id: number;
|
|
15
|
+
len: number;
|
|
16
|
+
}
|
|
17
|
+
export interface IEbmlElements {
|
|
18
|
+
version?: number;
|
|
19
|
+
readVersion?: number;
|
|
20
|
+
maxIDWidth?: number;
|
|
21
|
+
maxSizeWidth?: number;
|
|
22
|
+
docType?: string;
|
|
23
|
+
docTypeVersion?: number;
|
|
24
|
+
docTypeReadVersion?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface IElementType {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly value?: DataType;
|
|
29
|
+
readonly container?: {
|
|
30
|
+
[id: number]: IElementType;
|
|
31
|
+
};
|
|
32
|
+
readonly multiple?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface IEbmlDoc {
|
|
35
|
+
ebml: IEbmlElements;
|
|
36
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export var DataType;
|
|
2
|
+
(function (DataType) {
|
|
3
|
+
DataType[DataType["string"] = 0] = "string";
|
|
4
|
+
DataType[DataType["uint"] = 1] = "uint";
|
|
5
|
+
DataType[DataType["uid"] = 2] = "uid";
|
|
6
|
+
DataType[DataType["bool"] = 3] = "bool";
|
|
7
|
+
DataType[DataType["binary"] = 4] = "binary";
|
|
8
|
+
DataType[DataType["float"] = 5] = "float";
|
|
9
|
+
})(DataType || (DataType = {}));
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
package/lib/id3v2/ID3v2Parser.js
CHANGED
|
@@ -54,17 +54,16 @@ export class ID3v2Parser {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
static readFrameData(uint8Array, frameHeader, majorVer, includeCovers, warningCollector) {
|
|
57
|
-
var _a, _b;
|
|
58
57
|
const frameParser = new FrameParser(majorVer, warningCollector);
|
|
59
58
|
switch (majorVer) {
|
|
60
59
|
case 2:
|
|
61
60
|
return frameParser.readData(uint8Array, frameHeader.id, includeCovers);
|
|
62
61
|
case 3:
|
|
63
62
|
case 4:
|
|
64
|
-
if (
|
|
63
|
+
if (frameHeader.flags?.format.unsynchronisation) {
|
|
65
64
|
uint8Array = ID3v2Parser.removeUnsyncBytes(uint8Array);
|
|
66
65
|
}
|
|
67
|
-
if (
|
|
66
|
+
if (frameHeader.flags?.format.data_length_indicator) {
|
|
68
67
|
uint8Array = uint8Array.slice(4, uint8Array.length);
|
|
69
68
|
}
|
|
70
69
|
return frameParser.readData(uint8Array, frameHeader.id, includeCovers);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type IElementType } from '../ebml/types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Elements of document type description
|
|
4
4
|
* Derived from https://github.com/tungol/EBML/blob/master/doctypes/matroska.dtd
|
|
5
5
|
* Extended with:
|
|
6
6
|
* - https://www.matroska.org/technical/specs/index.html
|
|
7
7
|
*/
|
|
8
|
-
export declare const
|
|
8
|
+
export declare const matroskaDtd: IElementType;
|