music-metadata 10.1.0 → 10.3.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 +465 -141
- package/lib/ParseError.d.ts +87 -0
- package/lib/ParseError.js +39 -0
- package/lib/ParserFactory.d.ts +1 -1
- package/lib/ParserFactory.js +9 -8
- package/lib/aiff/AiffParser.js +6 -7
- package/lib/aiff/AiffToken.d.ts +15 -0
- package/lib/aiff/AiffToken.js +5 -2
- package/lib/apev2/APEv2Parser.d.ts +15 -0
- package/lib/apev2/APEv2Parser.js +6 -3
- package/lib/asf/AsfObject.d.ts +15 -0
- package/lib/asf/AsfObject.js +4 -1
- package/lib/asf/AsfParser.js +2 -1
- package/lib/common/CombinedTagMapper.js +2 -1
- package/lib/common/FourCC.js +3 -2
- package/lib/common/MetadataCollector.js +2 -4
- package/lib/common/Util.js +3 -2
- package/lib/core.d.ts +2 -1
- package/lib/core.js +1 -1
- package/lib/default.cjs +5 -0
- package/lib/dsdiff/DsdiffParser.d.ts +15 -0
- package/lib/dsdiff/DsdiffParser.js +6 -3
- package/lib/dsf/DsfParser.d.ts +15 -0
- package/lib/dsf/DsfParser.js +4 -1
- package/lib/ebml/EbmlIterator.d.ts +67 -0
- package/lib/ebml/EbmlIterator.js +223 -0
- package/lib/ebml/types.d.ts +36 -0
- package/lib/ebml/types.js +10 -0
- package/lib/flac/FlacParser.js +5 -2
- package/lib/id3v2/FrameParser.d.ts +14 -0
- package/lib/id3v2/FrameParser.js +7 -1
- package/lib/id3v2/ID3v2Parser.js +10 -8
- 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/mp4/AtomToken.d.ts +14 -0
- package/lib/mp4/AtomToken.js +6 -3
- package/lib/mp4/MP4Parser.js +4 -3
- package/lib/mpeg/MpegParser.d.ts +15 -0
- package/lib/mpeg/MpegParser.js +7 -4
- package/lib/musepack/MusepackConentError.d.ts +15 -0
- package/lib/musepack/MusepackConentError.js +4 -0
- package/lib/musepack/index.js +4 -3
- package/lib/musepack/sv7/MpcSv7Parser.js +2 -1
- package/lib/musepack/sv8/MpcSv8Parser.js +3 -2
- package/lib/node.cjs +5 -0
- package/lib/ogg/OggParser.d.ts +15 -0
- package/lib/ogg/OggParser.js +5 -2
- package/lib/ogg/opus/Opus.d.ts +15 -0
- package/lib/ogg/opus/Opus.js +4 -1
- package/lib/ogg/opus/OpusParser.js +2 -1
- package/lib/ogg/vorbis/VorbisParser.d.ts +15 -0
- package/lib/ogg/vorbis/VorbisParser.js +6 -3
- package/lib/type.d.ts +9 -0
- package/lib/wav/WaveChunk.d.ts +15 -0
- package/lib/wav/WaveChunk.js +5 -2
- package/lib/wav/WaveParser.js +3 -2
- package/lib/wavpack/WavPackParser.d.ts +15 -0
- package/lib/wavpack/WavPackParser.js +6 -3
- package/package.json +20 -11
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { Float32_BE, Float64_BE, StringType, UINT8 } from 'token-types';
|
|
2
1
|
import initDebug from 'debug';
|
|
3
|
-
import { EndOfStreamError } from 'strtok3';
|
|
4
2
|
import { BasicParser } from '../common/BasicParser.js';
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
3
|
+
import { matroskaDtd } from './MatroskaDtd.js';
|
|
4
|
+
import { TargetType, TrackType } from './types.js';
|
|
5
|
+
import { EbmlIterator, ParseAction } from '../ebml/EbmlIterator.js';
|
|
8
6
|
const debug = initDebug('music-metadata:parser:matroska');
|
|
9
7
|
/**
|
|
10
8
|
* Extensible Binary Meta Language (EBML) parser
|
|
@@ -15,17 +13,13 @@ const debug = initDebug('music-metadata:parser:matroska');
|
|
|
15
13
|
*/
|
|
16
14
|
export class MatroskaParser extends BasicParser {
|
|
17
15
|
constructor() {
|
|
18
|
-
super();
|
|
19
|
-
this.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.
|
|
25
|
-
this.parserMap.set(DataType.binary, e => this.readBuffer(e));
|
|
26
|
-
this.parserMap.set(DataType.uid, async (e) => this.readBuffer(e));
|
|
27
|
-
this.parserMap.set(DataType.bool, e => this.readFlag(e));
|
|
28
|
-
this.parserMap.set(DataType.float, e => this.readFloat(e));
|
|
16
|
+
super(...arguments);
|
|
17
|
+
this.seekHeadOffset = 0;
|
|
18
|
+
/**
|
|
19
|
+
* Use index to skip multiple segment/cluster elements at once.
|
|
20
|
+
* Significant performance impact
|
|
21
|
+
*/
|
|
22
|
+
this.flagUseIndexToSkipClusters = false;
|
|
29
23
|
}
|
|
30
24
|
/**
|
|
31
25
|
* Initialize parser with output (metadata), input (tokenizer) & parsing options (options).
|
|
@@ -35,206 +29,127 @@ export class MatroskaParser extends BasicParser {
|
|
|
35
29
|
*/
|
|
36
30
|
init(metadata, tokenizer, options) {
|
|
37
31
|
super.init(metadata, tokenizer, options);
|
|
32
|
+
this.flagUseIndexToSkipClusters = options.mkvUseIndex ?? false;
|
|
38
33
|
return this;
|
|
39
34
|
}
|
|
40
35
|
async parse() {
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
flagDefault: entry.flagDefault,
|
|
62
|
-
flagLacing: entry.flagLacing,
|
|
63
|
-
flagEnabled: entry.flagEnabled,
|
|
64
|
-
language: entry.language,
|
|
65
|
-
name: entry.name,
|
|
66
|
-
type: entry.trackType,
|
|
67
|
-
audio: entry.audio,
|
|
68
|
-
video: entry.video
|
|
69
|
-
};
|
|
70
|
-
this.metadata.addStreamInfo(stream);
|
|
71
|
-
});
|
|
72
|
-
const audioTrack = audioTracks.entries
|
|
73
|
-
.filter(entry => entry.trackType === TrackType.audio)
|
|
74
|
-
.reduce((acc, cur) => {
|
|
75
|
-
if (!acc)
|
|
76
|
-
return cur;
|
|
77
|
-
if (cur.flagDefault && !acc.flagDefault)
|
|
78
|
-
return cur;
|
|
79
|
-
if (cur.trackNumber < acc.trackNumber)
|
|
80
|
-
return cur;
|
|
81
|
-
return acc;
|
|
82
|
-
}, null);
|
|
83
|
-
if (audioTrack) {
|
|
84
|
-
this.metadata.setFormat('codec', audioTrack.codecID.replace('A_', ''));
|
|
85
|
-
this.metadata.setFormat('sampleRate', audioTrack.audio.samplingFrequency);
|
|
86
|
-
this.metadata.setFormat('numberOfChannels', audioTrack.audio.channels);
|
|
87
|
-
}
|
|
88
|
-
if (matroska.segment.tags) {
|
|
89
|
-
await Promise.all(matroska.segment.tags.tag.map(async (tag) => {
|
|
90
|
-
const target = tag.target;
|
|
91
|
-
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');
|
|
92
|
-
await Promise.all(tag.simpleTags.map(async (simpleTag) => {
|
|
93
|
-
const value = simpleTag.string ? simpleTag.string : simpleTag.binary;
|
|
94
|
-
await this.addTag(`${targetType}:${simpleTag.name}`, value);
|
|
95
|
-
}));
|
|
96
|
-
}));
|
|
97
|
-
}
|
|
98
|
-
if (matroska.segment.attachments) {
|
|
99
|
-
await Promise.all(matroska.segment.attachments.attachedFiles
|
|
100
|
-
.filter(file => file.mimeType.startsWith('image/'))
|
|
101
|
-
.map(file => this.addTag('picture', {
|
|
102
|
-
data: file.data,
|
|
103
|
-
format: file.mimeType,
|
|
104
|
-
description: file.description,
|
|
105
|
-
name: file.name
|
|
106
|
-
})));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
async parseContainer(container, posDone, path) {
|
|
112
|
-
const tree = {};
|
|
113
|
-
while (this.tokenizer.position < posDone) {
|
|
114
|
-
let element;
|
|
115
|
-
try {
|
|
116
|
-
element = await this.readElement();
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
if (error instanceof EndOfStreamError) {
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
throw error;
|
|
123
|
-
}
|
|
124
|
-
const type = container[element.id];
|
|
125
|
-
if (type) {
|
|
126
|
-
debug(`Element: name=${type.name}, container=${!!type.container}`);
|
|
127
|
-
if (type.container) {
|
|
128
|
-
const res = await this.parseContainer(type.container, element.len >= 0 ? this.tokenizer.position + element.len : -1, path.concat([type.name]));
|
|
129
|
-
if (type.multiple) {
|
|
130
|
-
if (!tree[type.name]) {
|
|
131
|
-
tree[type.name] = [];
|
|
36
|
+
const containerSize = this.tokenizer.fileInfo.size ?? Number.MAX_SAFE_INTEGER;
|
|
37
|
+
const matroskaIterator = new EbmlIterator(this.tokenizer);
|
|
38
|
+
debug('Initializing DTD end MatroskaIterator');
|
|
39
|
+
await matroskaIterator.iterate(matroskaDtd, containerSize, {
|
|
40
|
+
startNext: (element) => {
|
|
41
|
+
switch (element.id) {
|
|
42
|
+
// case 0x1f43b675: // cluster
|
|
43
|
+
case 0x1c53bb6b: // Cueing Data
|
|
44
|
+
debug(`Skip element: name=${element.name}, id=0x${element.id.toString(16)}`);
|
|
45
|
+
return ParseAction.IgnoreElement;
|
|
46
|
+
case 0x1f43b675: // cluster
|
|
47
|
+
if (this.flagUseIndexToSkipClusters && this.seekHead) {
|
|
48
|
+
const index = this.seekHead.seek.find(index => index.position + this.seekHeadOffset > this.tokenizer.position);
|
|
49
|
+
if (index) {
|
|
50
|
+
// Go to next index position
|
|
51
|
+
const ignoreSize = index.position + this.seekHeadOffset - this.tokenizer.position;
|
|
52
|
+
debug(`Use index to go to next position, ignoring ${ignoreSize} bytes`);
|
|
53
|
+
this.tokenizer.ignore(ignoreSize);
|
|
54
|
+
return ParseAction.SkipElement;
|
|
55
|
+
}
|
|
132
56
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
tree[type.name] = res;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
const parser = this.parserMap.get(type.value);
|
|
141
|
-
if (typeof parser === 'function') {
|
|
142
|
-
tree[type.name] = await parser(element);
|
|
143
|
-
}
|
|
57
|
+
return ParseAction.IgnoreElement;
|
|
58
|
+
default:
|
|
59
|
+
return ParseAction.ReadNext;
|
|
144
60
|
}
|
|
145
|
-
}
|
|
146
|
-
|
|
61
|
+
},
|
|
62
|
+
elementValue: async (element, value, offset) => {
|
|
63
|
+
debug(`Received: name=${element.name}, value=${value}`);
|
|
147
64
|
switch (element.id) {
|
|
148
|
-
case
|
|
149
|
-
this.
|
|
150
|
-
|
|
65
|
+
case 0x4282: // docType
|
|
66
|
+
this.metadata.setFormat('container', `EBML/${value}`);
|
|
67
|
+
break;
|
|
68
|
+
case 0x114d9b74:
|
|
69
|
+
this.seekHead = value;
|
|
70
|
+
this.seekHeadOffset = offset;
|
|
71
|
+
break;
|
|
72
|
+
case 0x1549a966:
|
|
73
|
+
{ // Info (Segment Information)
|
|
74
|
+
const info = value;
|
|
75
|
+
const timecodeScale = info.timecodeScale ? info.timecodeScale : 1000000;
|
|
76
|
+
if (typeof info.duration === 'number') {
|
|
77
|
+
const duration = info.duration * timecodeScale / 1000000000;
|
|
78
|
+
await this.addTag('segment:title', info.title);
|
|
79
|
+
this.metadata.setFormat('duration', Number(duration));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case 0x1654ae6b:
|
|
84
|
+
{ // tracks
|
|
85
|
+
const audioTracks = value;
|
|
86
|
+
if (audioTracks?.entries) {
|
|
87
|
+
audioTracks.entries.forEach(entry => {
|
|
88
|
+
const stream = {
|
|
89
|
+
codecName: entry.codecID.replace('A_', '').replace('V_', ''),
|
|
90
|
+
codecSettings: entry.codecSettings,
|
|
91
|
+
flagDefault: entry.flagDefault,
|
|
92
|
+
flagLacing: entry.flagLacing,
|
|
93
|
+
flagEnabled: entry.flagEnabled,
|
|
94
|
+
language: entry.language,
|
|
95
|
+
name: entry.name,
|
|
96
|
+
type: entry.trackType,
|
|
97
|
+
audio: entry.audio,
|
|
98
|
+
video: entry.video
|
|
99
|
+
};
|
|
100
|
+
this.metadata.addStreamInfo(stream);
|
|
101
|
+
});
|
|
102
|
+
const audioTrack = audioTracks.entries
|
|
103
|
+
.filter(entry => entry.trackType === TrackType.audio)
|
|
104
|
+
.reduce((acc, cur) => {
|
|
105
|
+
if (!acc)
|
|
106
|
+
return cur;
|
|
107
|
+
if (cur.flagDefault && !acc.flagDefault)
|
|
108
|
+
return cur;
|
|
109
|
+
if (cur.trackNumber < acc.trackNumber)
|
|
110
|
+
return cur;
|
|
111
|
+
return acc;
|
|
112
|
+
}, null);
|
|
113
|
+
if (audioTrack) {
|
|
114
|
+
this.metadata.setFormat('codec', audioTrack.codecID.replace('A_', ''));
|
|
115
|
+
this.metadata.setFormat('sampleRate', audioTrack.audio.samplingFrequency);
|
|
116
|
+
this.metadata.setFormat('numberOfChannels', audioTrack.audio.channels);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
case 0x1254c367:
|
|
122
|
+
{ // tags
|
|
123
|
+
const tags = value;
|
|
124
|
+
await Promise.all(tags.tag.map(async (tag) => {
|
|
125
|
+
const target = tag.target;
|
|
126
|
+
const targetType = target?.targetTypeValue ? TargetType[target.targetTypeValue] : (target?.targetType ? target.targetType : 'track');
|
|
127
|
+
await Promise.all(tag.simpleTags.map(async (simpleTag) => {
|
|
128
|
+
const value = simpleTag.string ? simpleTag.string : simpleTag.binary;
|
|
129
|
+
await this.addTag(`${targetType}:${simpleTag.name}`, value);
|
|
130
|
+
}));
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
case 0x1941a469:
|
|
135
|
+
{ // attachments
|
|
136
|
+
const attachments = value;
|
|
137
|
+
await Promise.all(attachments.attachedFiles
|
|
138
|
+
.filter(file => file.mimeType.startsWith('image/'))
|
|
139
|
+
.map(file => this.addTag('picture', {
|
|
140
|
+
data: file.data,
|
|
141
|
+
format: file.mimeType,
|
|
142
|
+
description: file.description,
|
|
143
|
+
name: file.name
|
|
144
|
+
})));
|
|
145
|
+
}
|
|
151
146
|
break;
|
|
152
|
-
default:
|
|
153
|
-
debug(`parseEbml: path=${path.join('/')}, unknown element: id=${element.id.toString(16)}`);
|
|
154
|
-
this.padding += element.len;
|
|
155
|
-
await this.tokenizer.ignore(element.len);
|
|
156
147
|
}
|
|
157
148
|
}
|
|
158
|
-
}
|
|
159
|
-
return tree;
|
|
160
|
-
}
|
|
161
|
-
async readVintData(maxLength) {
|
|
162
|
-
const msb = await this.tokenizer.peekNumber(UINT8);
|
|
163
|
-
let mask = 0x80;
|
|
164
|
-
let oc = 1;
|
|
165
|
-
// Calculate VINT_WIDTH
|
|
166
|
-
while ((msb & mask) === 0) {
|
|
167
|
-
if (oc > maxLength) {
|
|
168
|
-
throw new Error('VINT value exceeding maximum size');
|
|
169
|
-
}
|
|
170
|
-
++oc;
|
|
171
|
-
mask >>= 1;
|
|
172
|
-
}
|
|
173
|
-
const id = new Uint8Array(oc);
|
|
174
|
-
await this.tokenizer.readBuffer(id);
|
|
175
|
-
return id;
|
|
176
|
-
}
|
|
177
|
-
async readElement() {
|
|
178
|
-
const id = await this.readVintData(this.ebmlMaxIDLength);
|
|
179
|
-
const lenField = await this.readVintData(this.ebmlMaxSizeLength);
|
|
180
|
-
lenField[0] ^= 0x80 >> (lenField.length - 1);
|
|
181
|
-
return {
|
|
182
|
-
id: MatroskaParser.readUIntBE(id, id.length),
|
|
183
|
-
len: MatroskaParser.readUIntBE(lenField, lenField.length)
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
async readFloat(e) {
|
|
187
|
-
switch (e.len) {
|
|
188
|
-
case 0:
|
|
189
|
-
return 0.0;
|
|
190
|
-
case 4:
|
|
191
|
-
return this.tokenizer.readNumber(Float32_BE);
|
|
192
|
-
case 8:
|
|
193
|
-
return this.tokenizer.readNumber(Float64_BE);
|
|
194
|
-
case 10:
|
|
195
|
-
return this.tokenizer.readNumber(Float64_BE);
|
|
196
|
-
default:
|
|
197
|
-
throw new Error(`Invalid IEEE-754 float length: ${e.len}`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
async readFlag(e) {
|
|
201
|
-
return (await this.readUint(e)) === 1;
|
|
202
|
-
}
|
|
203
|
-
async readUint(e) {
|
|
204
|
-
const buf = await this.readBuffer(e);
|
|
205
|
-
return MatroskaParser.readUIntBE(buf, e.len);
|
|
206
|
-
}
|
|
207
|
-
async readString(e) {
|
|
208
|
-
const rawString = await this.tokenizer.readToken(new StringType(e.len, 'utf-8'));
|
|
209
|
-
return rawString.replace(/\x00.*$/g, '');
|
|
210
|
-
}
|
|
211
|
-
async readBuffer(e) {
|
|
212
|
-
const buf = new Uint8Array(e.len);
|
|
213
|
-
await this.tokenizer.readBuffer(buf);
|
|
214
|
-
return buf;
|
|
149
|
+
});
|
|
215
150
|
}
|
|
216
151
|
async addTag(tagId, value) {
|
|
217
152
|
await this.metadata.addTag('matroska', tagId, value);
|
|
218
153
|
}
|
|
219
|
-
static readUIntBE(buf, len) {
|
|
220
|
-
return Number(MatroskaParser.readUIntBeAsBigInt(buf, len));
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Reeds an unsigned integer from a big endian buffer of length `len`
|
|
224
|
-
* @param buf Buffer to decode from
|
|
225
|
-
* @param len Number of bytes
|
|
226
|
-
* @private
|
|
227
|
-
*/
|
|
228
|
-
static readUIntBeAsBigInt(buf, len) {
|
|
229
|
-
const normalizedNumber = new Uint8Array(8);
|
|
230
|
-
const cleanNumber = buf.subarray(0, len);
|
|
231
|
-
try {
|
|
232
|
-
normalizedNumber.set(cleanNumber, 8 - len);
|
|
233
|
-
return Token.UINT64_BE.get(normalizedNumber, 0);
|
|
234
|
-
}
|
|
235
|
-
catch (error) {
|
|
236
|
-
return BigInt(-1);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
154
|
}
|
|
240
155
|
//# sourceMappingURL=MatroskaParser.js.map
|
package/lib/matroska/types.d.ts
CHANGED
|
@@ -1,34 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export declare enum DataType {
|
|
6
|
-
'string' = 0,
|
|
7
|
-
uint = 1,
|
|
8
|
-
uid = 2,
|
|
9
|
-
bool = 3,
|
|
10
|
-
binary = 4,
|
|
11
|
-
float = 5
|
|
12
|
-
}
|
|
13
|
-
export interface IElementType<T> {
|
|
14
|
-
readonly name: string;
|
|
15
|
-
readonly value?: DataType;
|
|
16
|
-
readonly container?: IContainerType;
|
|
17
|
-
readonly multiple?: boolean;
|
|
18
|
-
}
|
|
19
|
-
export interface IContainerType {
|
|
20
|
-
[id: number]: IElementType<string | number | boolean | Uint8Array>;
|
|
21
|
-
}
|
|
22
|
-
export interface ITree {
|
|
23
|
-
[name: string]: string | number | boolean | Uint8Array | ITree | ITree[];
|
|
24
|
-
}
|
|
25
|
-
export type ValueType = string | number | Uint8Array | boolean | ITree | ITree[];
|
|
26
|
-
export interface ISeekHead {
|
|
27
|
-
id?: Uint8Array;
|
|
28
|
-
position?: number;
|
|
1
|
+
import type { IEbmlDoc } from '../ebml/types.js';
|
|
2
|
+
export interface ISeek {
|
|
3
|
+
id: Uint8Array;
|
|
4
|
+
position: number;
|
|
29
5
|
}
|
|
30
|
-
export interface
|
|
31
|
-
|
|
6
|
+
export interface ISeekHead {
|
|
7
|
+
seek: ISeek[];
|
|
32
8
|
}
|
|
33
9
|
export interface ISegmentInformation {
|
|
34
10
|
uid?: Uint8Array;
|
|
@@ -151,26 +127,14 @@ export interface IAttachments {
|
|
|
151
127
|
attachedFiles: IAttachmedFile[];
|
|
152
128
|
}
|
|
153
129
|
export interface IMatroskaSegment {
|
|
154
|
-
metaSeekInfo?:
|
|
155
|
-
seekHeads?:
|
|
130
|
+
metaSeekInfo?: ISeekHead;
|
|
131
|
+
seekHeads?: ISeek[];
|
|
156
132
|
info?: ISegmentInformation;
|
|
157
133
|
tracks?: ITrackElement;
|
|
158
134
|
tags?: ITags;
|
|
159
135
|
cues?: ICuePoint[];
|
|
160
136
|
attachments?: IAttachments;
|
|
161
137
|
}
|
|
162
|
-
export interface IEbmlElements {
|
|
163
|
-
version?: number;
|
|
164
|
-
readVersion?: number;
|
|
165
|
-
maxIDWidth?: number;
|
|
166
|
-
maxSizeWidth?: number;
|
|
167
|
-
docType?: string;
|
|
168
|
-
docTypeVersion?: number;
|
|
169
|
-
docTypeReadVersion?: number;
|
|
170
|
-
}
|
|
171
|
-
export interface IEbmlDoc {
|
|
172
|
-
ebml: IEbmlElements;
|
|
173
|
-
}
|
|
174
138
|
export interface IMatroskaDoc extends IEbmlDoc {
|
|
175
139
|
segment: IMatroskaSegment;
|
|
176
140
|
}
|
package/lib/matroska/types.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
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
1
|
export var TargetType;
|
|
11
2
|
(function (TargetType) {
|
|
12
3
|
TargetType[TargetType["shot"] = 10] = "shot";
|
package/lib/mp4/AtomToken.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import type { IToken, IGetToken } from 'strtok3';
|
|
2
|
+
declare const Mp4ContentError_base: {
|
|
3
|
+
new (message: string): {
|
|
4
|
+
readonly fileType: string;
|
|
5
|
+
toString(): string;
|
|
6
|
+
name: "UnexpectedFileContentError";
|
|
7
|
+
message: string;
|
|
8
|
+
stack?: string;
|
|
9
|
+
};
|
|
10
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
11
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
12
|
+
stackTraceLimit: number;
|
|
13
|
+
};
|
|
14
|
+
export declare class Mp4ContentError extends Mp4ContentError_base {
|
|
15
|
+
}
|
|
2
16
|
interface IVersionAndFlags {
|
|
3
17
|
/**
|
|
4
18
|
* A 1-byte specification of the version
|
package/lib/mp4/AtomToken.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import * as Token from 'token-types';
|
|
2
2
|
import initDebug from 'debug';
|
|
3
3
|
import { FourCcToken } from '../common/FourCC.js';
|
|
4
|
+
import { makeUnexpectedFileContentError } from '../ParseError.js';
|
|
4
5
|
const debug = initDebug('music-metadata:parser:MP4:atom');
|
|
6
|
+
export class Mp4ContentError extends makeUnexpectedFileContentError('MP4') {
|
|
7
|
+
}
|
|
5
8
|
export const Header = {
|
|
6
9
|
len: 8,
|
|
7
10
|
get: (buf, off) => {
|
|
8
11
|
const length = Token.UINT32_BE.get(buf, off);
|
|
9
12
|
if (length < 0)
|
|
10
|
-
throw new
|
|
13
|
+
throw new Mp4ContentError('Invalid atom header length');
|
|
11
14
|
return {
|
|
12
15
|
length: BigInt(length),
|
|
13
16
|
name: new Token.StringType(4, 'latin1').get(buf, off + 4)
|
|
@@ -66,7 +69,7 @@ export class FixedLengthAtom {
|
|
|
66
69
|
constructor(len, expLen, atomId) {
|
|
67
70
|
this.len = len;
|
|
68
71
|
if (len < expLen) {
|
|
69
|
-
throw new
|
|
72
|
+
throw new Mp4ContentError(`Atom ${atomId} expected to be ${expLen}, but specifies ${len} bytes long.`);
|
|
70
73
|
}
|
|
71
74
|
if (len > expLen) {
|
|
72
75
|
debug(`Warning: atom ${atomId} expected to be ${expLen}, but was actually ${len} bytes long.`);
|
|
@@ -381,7 +384,7 @@ function readTokenTable(buf, token, off, remainingLen, numberOfEntries) {
|
|
|
381
384
|
if (remainingLen === 0)
|
|
382
385
|
return [];
|
|
383
386
|
if (remainingLen !== numberOfEntries * token.len)
|
|
384
|
-
throw new
|
|
387
|
+
throw new Mp4ContentError('mismatch number-of-entries with remaining atom-length');
|
|
385
388
|
const entries = [];
|
|
386
389
|
// parse offset-table
|
|
387
390
|
for (let n = 0; n < numberOfEntries; ++n) {
|
package/lib/mp4/MP4Parser.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Atom } from './Atom.js';
|
|
|
6
6
|
import * as AtomToken from './AtomToken.js';
|
|
7
7
|
import { TrackType } from '../type.js';
|
|
8
8
|
import { uint8ArrayToHex, uint8ArrayToString } from 'uint8array-extras';
|
|
9
|
+
import { Mp4ContentError } from './AtomToken.js';
|
|
9
10
|
const debug = initDebug('music-metadata:parser:MP4');
|
|
10
11
|
const tagFormat = 'iTunes';
|
|
11
12
|
const encoderDict = {
|
|
@@ -209,7 +210,7 @@ export class MP4Parser extends BasicParser {
|
|
|
209
210
|
const integerType = (signed ? 'INT' : 'UINT') + array.length * 8 + (array.length > 1 ? '_BE' : '');
|
|
210
211
|
const token = Token[integerType];
|
|
211
212
|
if (!token) {
|
|
212
|
-
throw new
|
|
213
|
+
throw new Mp4ContentError(`Token for integer type not found: "${integerType}"`);
|
|
213
214
|
}
|
|
214
215
|
return Number(token.get(array, 0));
|
|
215
216
|
}
|
|
@@ -361,7 +362,7 @@ export class MP4Parser extends BasicParser {
|
|
|
361
362
|
async parseValueAtom(tagKey, metaAtom) {
|
|
362
363
|
const dataAtom = await this.tokenizer.readToken(new AtomToken.DataAtom(Number(metaAtom.header.length) - AtomToken.Header.len));
|
|
363
364
|
if (dataAtom.type.set !== 0) {
|
|
364
|
-
throw new
|
|
365
|
+
throw new Mp4ContentError(`Unsupported type-set != 0: ${dataAtom.type.set}`);
|
|
365
366
|
}
|
|
366
367
|
// Use well-known-type table
|
|
367
368
|
// Ref: https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW35
|
|
@@ -464,7 +465,7 @@ export class MP4Parser extends BasicParser {
|
|
|
464
465
|
const sampleSize = chapterTrack.sampleSize > 0 ? chapterTrack.sampleSize : chapterTrack.sampleSizeTable[i];
|
|
465
466
|
len -= nextChunkLen + sampleSize;
|
|
466
467
|
if (len < 0)
|
|
467
|
-
throw new
|
|
468
|
+
throw new Mp4ContentError('Chapter chunk exceeding token length');
|
|
468
469
|
await this.tokenizer.ignore(nextChunkLen);
|
|
469
470
|
const title = await this.tokenizer.readToken(new AtomToken.ChapterText(sampleSize));
|
|
470
471
|
debug(`Chapter ${i + 1}: ${title}`);
|
package/lib/mpeg/MpegParser.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { AbstractID3Parser } from '../id3v2/AbstractID3Parser.js';
|
|
2
|
+
declare const MpegContentError_base: {
|
|
3
|
+
new (message: string): {
|
|
4
|
+
readonly fileType: string;
|
|
5
|
+
toString(): string;
|
|
6
|
+
name: "UnexpectedFileContentError";
|
|
7
|
+
message: string;
|
|
8
|
+
stack?: string;
|
|
9
|
+
};
|
|
10
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
11
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
12
|
+
stackTraceLimit: number;
|
|
13
|
+
};
|
|
14
|
+
export declare class MpegContentError extends MpegContentError_base {
|
|
15
|
+
}
|
|
2
16
|
export declare class MpegParser extends AbstractID3Parser {
|
|
3
17
|
private frameCount;
|
|
4
18
|
private syncFrameCount;
|
|
@@ -47,3 +61,4 @@ export declare class MpegParser extends AbstractID3Parser {
|
|
|
47
61
|
private skipFrameData;
|
|
48
62
|
private areAllSame;
|
|
49
63
|
}
|
|
64
|
+
export {};
|
package/lib/mpeg/MpegParser.js
CHANGED
|
@@ -4,7 +4,10 @@ import initDebug from 'debug';
|
|
|
4
4
|
import * as common from '../common/Util.js';
|
|
5
5
|
import { AbstractID3Parser } from '../id3v2/AbstractID3Parser.js';
|
|
6
6
|
import { InfoTagHeaderTag, LameEncoderVersion, readXingHeader } from './XingTag.js';
|
|
7
|
+
import { makeUnexpectedFileContentError } from '../ParseError.js';
|
|
7
8
|
const debug = initDebug('music-metadata:parser:mpeg');
|
|
9
|
+
export class MpegContentError extends makeUnexpectedFileContentError('MPEG') {
|
|
10
|
+
}
|
|
8
11
|
/**
|
|
9
12
|
* Cache buffer size used for searching synchronization preabmle
|
|
10
13
|
*/
|
|
@@ -140,13 +143,13 @@ class MpegFrameHeader {
|
|
|
140
143
|
// Calculate bitrate
|
|
141
144
|
const bitrateInKbps = this.calcBitrate();
|
|
142
145
|
if (!bitrateInKbps) {
|
|
143
|
-
throw new
|
|
146
|
+
throw new MpegContentError('Cannot determine bit-rate');
|
|
144
147
|
}
|
|
145
148
|
this.bitrate = bitrateInKbps * 1000;
|
|
146
149
|
// Calculate sampling rate
|
|
147
150
|
this.samplingRate = this.calcSamplingRate();
|
|
148
151
|
if (this.samplingRate == null) {
|
|
149
|
-
throw new
|
|
152
|
+
throw new MpegContentError('Cannot determine sampling-rate');
|
|
150
153
|
}
|
|
151
154
|
}
|
|
152
155
|
parseAdtsHeader(buf, off) {
|
|
@@ -383,7 +386,7 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
383
386
|
}
|
|
384
387
|
const slot_size = header.calcSlotSize();
|
|
385
388
|
if (slot_size === null) {
|
|
386
|
-
throw new
|
|
389
|
+
throw new MpegContentError('invalid slot_size');
|
|
387
390
|
}
|
|
388
391
|
const samples_per_frame = header.calcSamplesPerFrame();
|
|
389
392
|
debug(`samples_per_frame=${samples_per_frame}`);
|
|
@@ -555,7 +558,7 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
555
558
|
}
|
|
556
559
|
async skipFrameData(frameDataLeft) {
|
|
557
560
|
if (frameDataLeft < 0)
|
|
558
|
-
throw new
|
|
561
|
+
throw new MpegContentError('frame-data-left cannot be negative');
|
|
559
562
|
await this.tokenizer.ignore(frameDataLeft);
|
|
560
563
|
this.countSkipFrameData += frameDataLeft;
|
|
561
564
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare const MusepackContentError_base: {
|
|
2
|
+
new (message: string): {
|
|
3
|
+
readonly fileType: string;
|
|
4
|
+
toString(): string;
|
|
5
|
+
name: "UnexpectedFileContentError";
|
|
6
|
+
message: string;
|
|
7
|
+
stack?: string;
|
|
8
|
+
};
|
|
9
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
10
|
+
prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
|
|
11
|
+
stackTraceLimit: number;
|
|
12
|
+
};
|
|
13
|
+
export declare class MusepackContentError extends MusepackContentError_base {
|
|
14
|
+
}
|
|
15
|
+
export {};
|
package/lib/musepack/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as Token from 'token-types';
|
|
|
3
3
|
import { AbstractID3Parser } from '../id3v2/AbstractID3Parser.js';
|
|
4
4
|
import { MpcSv8Parser } from './sv8/MpcSv8Parser.js';
|
|
5
5
|
import { MpcSv7Parser } from './sv7/MpcSv7Parser.js';
|
|
6
|
+
import { MusepackContentError } from './MusepackConentError.js';
|
|
6
7
|
const debug = initDebug('music-metadata:parser:musepack');
|
|
7
8
|
class MusepackParser extends AbstractID3Parser {
|
|
8
9
|
async postId3v2Parse() {
|
|
@@ -10,17 +11,17 @@ class MusepackParser extends AbstractID3Parser {
|
|
|
10
11
|
let mpcParser;
|
|
11
12
|
switch (signature) {
|
|
12
13
|
case 'MP+': {
|
|
13
|
-
debug('
|
|
14
|
+
debug('Stream-version 7');
|
|
14
15
|
mpcParser = new MpcSv7Parser();
|
|
15
16
|
break;
|
|
16
17
|
}
|
|
17
18
|
case 'MPC': {
|
|
18
|
-
debug('
|
|
19
|
+
debug('Stream-version 8');
|
|
19
20
|
mpcParser = new MpcSv8Parser();
|
|
20
21
|
break;
|
|
21
22
|
}
|
|
22
23
|
default: {
|
|
23
|
-
throw new
|
|
24
|
+
throw new MusepackContentError('Invalid signature prefix');
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
mpcParser.init(this.metadata, this.tokenizer, this.options);
|