music-metadata 10.0.0 → 10.1.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 +4 -3
- package/lib/ParserFactory.d.ts +20 -28
- package/lib/ParserFactory.js +204 -207
- package/lib/aiff/AiffParser.js +20 -18
- package/lib/aiff/AiffToken.d.ts +14 -2
- package/lib/aiff/AiffToken.js +12 -0
- package/lib/apev2/APEv2Parser.d.ts +4 -4
- package/lib/apev2/APEv2Parser.js +20 -12
- package/lib/apev2/APEv2Token.d.ts +2 -4
- package/lib/apev2/APEv2Token.js +1 -4
- package/lib/asf/AsfObject.d.ts +7 -16
- package/lib/asf/AsfObject.js +8 -34
- package/lib/asf/AsfParser.js +17 -10
- package/lib/asf/AsfTagMapper.d.ts +1 -1
- package/lib/asf/AsfTagMapper.js +3 -2
- package/lib/asf/AsfUtil.d.ts +3 -11
- package/lib/asf/AsfUtil.js +29 -30
- package/lib/asf/GUID.js +6 -9
- package/lib/common/BasicParser.d.ts +4 -4
- package/lib/common/BasicParser.js +6 -0
- package/lib/common/CaseInsensitiveTagMap.d.ts +1 -1
- package/lib/common/CombinedTagMapper.d.ts +5 -5
- package/lib/common/CombinedTagMapper.js +1 -1
- package/lib/common/FourCC.d.ts +1 -1
- package/lib/common/GenericTagMapper.d.ts +8 -8
- package/lib/common/GenericTagMapper.js +4 -4
- package/lib/common/GenericTagTypes.d.ts +5 -4
- package/lib/common/GenericTagTypes.js +2 -2
- package/lib/common/MetadataCollector.d.ts +9 -9
- package/lib/common/MetadataCollector.js +24 -17
- package/lib/common/RandomFileReader.d.ts +1 -1
- package/lib/common/RandomFileReader.js +1 -1
- package/lib/common/RandomUint8ArrayReader.d.ts +1 -1
- package/lib/common/Util.d.ts +2 -6
- package/lib/common/Util.js +9 -11
- package/lib/core.d.ts +7 -9
- package/lib/core.js +10 -7
- package/lib/dsdiff/DsdiffParser.js +26 -14
- package/lib/dsdiff/DsdiffToken.d.ts +2 -2
- package/lib/dsdiff/DsdiffToken.js +1 -0
- package/lib/dsf/DsfChunk.js +1 -0
- package/lib/dsf/DsfParser.js +4 -2
- package/lib/flac/FlacParser.d.ts +3 -3
- package/lib/flac/FlacParser.js +9 -12
- package/lib/id3v1/ID3v1Parser.d.ts +1 -1
- package/lib/id3v1/ID3v1Parser.js +7 -4
- package/lib/id3v2/AbstractID3Parser.d.ts +1 -1
- package/lib/id3v2/AbstractID3Parser.js +2 -1
- package/lib/id3v2/FrameParser.d.ts +28 -3
- package/lib/id3v2/FrameParser.js +50 -28
- package/lib/id3v2/ID3v22TagMapper.d.ts +1 -1
- package/lib/id3v2/ID3v24TagMapper.d.ts +2 -1
- package/lib/id3v2/ID3v24TagMapper.js +23 -16
- package/lib/id3v2/ID3v2Parser.d.ts +2 -2
- package/lib/id3v2/ID3v2Parser.js +19 -8
- package/lib/iff/index.js +1 -0
- package/lib/index.d.ts +5 -6
- package/lib/index.js +7 -8
- package/lib/lyrics3/Lyrics3.d.ts +1 -1
- package/lib/lyrics3/Lyrics3.js +1 -1
- package/lib/matroska/MatroskaDtd.d.ts +1 -1
- package/lib/matroska/MatroskaDtd.js +139 -138
- package/lib/matroska/MatroskaParser.d.ts +4 -4
- package/lib/matroska/MatroskaParser.js +12 -12
- package/lib/matroska/types.d.ts +6 -4
- package/lib/mp4/Atom.d.ts +3 -3
- package/lib/mp4/Atom.js +4 -7
- package/lib/mp4/AtomToken.js +2 -1
- package/lib/mp4/MP4Parser.js +29 -20
- package/lib/mp4/MP4TagMapper.d.ts +2 -2
- package/lib/mp4/MP4TagMapper.js +1 -1
- package/lib/mpeg/ExtendedLameHeader.d.ts +4 -4
- package/lib/mpeg/ExtendedLameHeader.js +2 -1
- package/lib/mpeg/MpegParser.d.ts +1 -1
- package/lib/mpeg/MpegParser.js +145 -96
- package/lib/mpeg/ReplayGainDataFormat.d.ts +1 -1
- package/lib/mpeg/ReplayGainDataFormat.js +1 -0
- package/lib/mpeg/XingTag.d.ts +4 -4
- package/lib/mpeg/XingTag.js +5 -4
- package/lib/musepack/index.js +1 -0
- package/lib/musepack/sv7/BitReader.js +14 -17
- package/lib/musepack/sv7/MpcSv7Parser.js +6 -1
- package/lib/musepack/sv7/StreamVersion7.js +1 -0
- package/lib/musepack/sv8/MpcSv8Parser.js +6 -2
- package/lib/musepack/sv8/StreamVersion8.d.ts +1 -1
- package/lib/musepack/sv8/StreamVersion8.js +1 -0
- package/lib/ogg/Ogg.d.ts +1 -1
- package/lib/ogg/Ogg.js +1 -0
- package/lib/ogg/OggParser.d.ts +3 -3
- package/lib/ogg/OggParser.js +25 -16
- package/lib/ogg/opus/Opus.d.ts +1 -1
- package/lib/ogg/opus/Opus.js +1 -0
- package/lib/ogg/opus/OpusParser.d.ts +4 -4
- package/lib/ogg/opus/OpusParser.js +2 -0
- package/lib/ogg/speex/Speex.js +1 -0
- package/lib/ogg/speex/SpeexParser.d.ts +4 -4
- package/lib/ogg/speex/SpeexParser.js +1 -0
- package/lib/ogg/theora/Theora.js +1 -0
- package/lib/ogg/theora/TheoraParser.d.ts +4 -4
- package/lib/ogg/theora/TheoraParser.js +1 -0
- package/lib/ogg/vorbis/Vorbis.d.ts +3 -3
- package/lib/ogg/vorbis/Vorbis.js +22 -11
- package/lib/ogg/vorbis/VorbisDecoder.d.ts +1 -1
- package/lib/ogg/vorbis/VorbisDecoder.js +1 -0
- package/lib/ogg/vorbis/VorbisParser.d.ts +4 -4
- package/lib/ogg/vorbis/VorbisParser.js +3 -2
- package/lib/ogg/vorbis/VorbisTagMapper.d.ts +2 -2
- package/lib/ogg/vorbis/VorbisTagMapper.js +2 -2
- package/lib/riff/RiffChunk.d.ts +3 -3
- package/lib/riff/RiffChunk.js +1 -0
- package/lib/riff/RiffInfoTagMap.d.ts +1 -1
- package/lib/type.d.ts +35 -25
- package/lib/wav/BwfChunk.js +1 -0
- package/lib/wav/WaveChunk.d.ts +1 -1
- package/lib/wav/WaveChunk.js +1 -0
- package/lib/wav/WaveParser.js +27 -17
- package/lib/wavpack/WavPackParser.d.ts +1 -1
- package/lib/wavpack/WavPackParser.js +22 -13
- package/lib/wavpack/WavPackToken.d.ts +13 -17
- package/lib/wavpack/WavPackToken.js +22 -23
- package/package.json +15 -28
package/lib/mpeg/MpegParser.js
CHANGED
|
@@ -28,7 +28,7 @@ const MPEG4 = {
|
|
|
28
28
|
* https://wiki.multimedia.cx/index.php/MPEG-4_Audio#Sampling_Frequencies
|
|
29
29
|
*/
|
|
30
30
|
SamplingFrequencies: [
|
|
31
|
-
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
|
|
31
|
+
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, null, null, -1
|
|
32
32
|
]
|
|
33
33
|
/**
|
|
34
34
|
* Channel Configurations
|
|
@@ -52,6 +52,24 @@ const MPEG4_ChannelConfigurations = [
|
|
|
52
52
|
*/
|
|
53
53
|
class MpegFrameHeader {
|
|
54
54
|
constructor(buf, off) {
|
|
55
|
+
// E(15,12): Bitrate index
|
|
56
|
+
this.bitrateIndex = null;
|
|
57
|
+
// F(11,10): Sampling rate frequency index
|
|
58
|
+
this.sampRateFreqIndex = null;
|
|
59
|
+
// G(9): Padding bit
|
|
60
|
+
this.padding = null;
|
|
61
|
+
// H(8): Private bit
|
|
62
|
+
this.privateBit = null;
|
|
63
|
+
// I(7,6): Channel Mode
|
|
64
|
+
this.channelModeIndex = null;
|
|
65
|
+
// J(5,4): Mode extension (Only used in Joint stereo)
|
|
66
|
+
this.modeExtension = null;
|
|
67
|
+
// L(2): Original
|
|
68
|
+
this.isOriginalMedia = null;
|
|
69
|
+
this.version = null;
|
|
70
|
+
this.bitrate = null;
|
|
71
|
+
this.samplingRate = null;
|
|
72
|
+
this.frameLength = 0;
|
|
55
73
|
// B(20,19): MPEG Audio versionIndex ID
|
|
56
74
|
this.versionIndex = common.getBitAllignedNumber(buf, off + 1, 3, 2);
|
|
57
75
|
// C(18,17): Layer description
|
|
@@ -66,7 +84,7 @@ class MpegFrameHeader {
|
|
|
66
84
|
this.isProtectedByCRC = !common.isBitSet(buf, off + 1, 7);
|
|
67
85
|
}
|
|
68
86
|
calcDuration(numFrames) {
|
|
69
|
-
return numFrames * this.calcSamplesPerFrame() / this.samplingRate;
|
|
87
|
+
return this.samplingRate == null ? null : (numFrames * this.calcSamplesPerFrame() / this.samplingRate);
|
|
70
88
|
}
|
|
71
89
|
calcSamplesPerFrame() {
|
|
72
90
|
return MpegFrameHeader.samplesInFrameTable[this.version === 1 ? 0 : 1][this.layer];
|
|
@@ -79,7 +97,7 @@ class MpegFrameHeader {
|
|
|
79
97
|
if (this.version === 1) {
|
|
80
98
|
return 17;
|
|
81
99
|
}
|
|
82
|
-
|
|
100
|
+
if (this.version === 2 || this.version === 2.5) {
|
|
83
101
|
return 9;
|
|
84
102
|
}
|
|
85
103
|
}
|
|
@@ -87,10 +105,11 @@ class MpegFrameHeader {
|
|
|
87
105
|
if (this.version === 1) {
|
|
88
106
|
return 32;
|
|
89
107
|
}
|
|
90
|
-
|
|
108
|
+
if (this.version === 2 || this.version === 2.5) {
|
|
91
109
|
return 17;
|
|
92
110
|
}
|
|
93
111
|
}
|
|
112
|
+
return null;
|
|
94
113
|
}
|
|
95
114
|
calcSlotSize() {
|
|
96
115
|
return [null, 4, 1, 1][this.layer];
|
|
@@ -131,9 +150,9 @@ class MpegFrameHeader {
|
|
|
131
150
|
}
|
|
132
151
|
}
|
|
133
152
|
parseAdtsHeader(buf, off) {
|
|
134
|
-
debug(
|
|
153
|
+
debug("layer=0 => ADTS");
|
|
135
154
|
this.version = this.versionIndex === 2 ? 4 : 2;
|
|
136
|
-
this.container =
|
|
155
|
+
this.container = `ADTS/MPEG-${this.version}`;
|
|
137
156
|
const profileIndex = common.getBitAllignedNumber(buf, off + 2, 0, 2);
|
|
138
157
|
this.codec = 'AAC';
|
|
139
158
|
this.codecProfile = MPEG4.AudioObjectTypes[profileIndex];
|
|
@@ -143,19 +162,22 @@ class MpegFrameHeader {
|
|
|
143
162
|
debug(`sampling-rate=${this.samplingRate}`);
|
|
144
163
|
const channelIndex = common.getBitAllignedNumber(buf, off + 2, 7, 3);
|
|
145
164
|
this.mp4ChannelConfig = MPEG4_ChannelConfigurations[channelIndex];
|
|
146
|
-
debug(`channel-config=${this.mp4ChannelConfig.join('+')}`);
|
|
165
|
+
debug(`channel-config=${this.mp4ChannelConfig ? this.mp4ChannelConfig.join('+') : '?'}`);
|
|
147
166
|
this.frameLength = common.getBitAllignedNumber(buf, off + 3, 6, 2) << 11;
|
|
148
167
|
}
|
|
149
168
|
calcBitrate() {
|
|
150
169
|
if (this.bitrateIndex === 0x00 || // free
|
|
151
170
|
this.bitrateIndex === 0x0F) { // reserved
|
|
152
|
-
return;
|
|
171
|
+
return null;
|
|
153
172
|
}
|
|
154
|
-
|
|
155
|
-
|
|
173
|
+
if (this.version && this.bitrateIndex) {
|
|
174
|
+
const codecIndex = 10 * Math.floor(this.version) + this.layer;
|
|
175
|
+
return MpegFrameHeader.bitrate_index[this.bitrateIndex][codecIndex];
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
156
178
|
}
|
|
157
179
|
calcSamplingRate() {
|
|
158
|
-
if (this.sampRateFreqIndex === 0x03)
|
|
180
|
+
if (this.sampRateFreqIndex === 0x03 || this.version === null || this.sampRateFreqIndex == null)
|
|
159
181
|
return null; // 'reserved'
|
|
160
182
|
return MpegFrameHeader.sampling_rate_freq_index[this.version][this.sampRateFreqIndex];
|
|
161
183
|
}
|
|
@@ -166,25 +188,25 @@ MpegFrameHeader.VersionID = [2.5, null, 2, 1];
|
|
|
166
188
|
MpegFrameHeader.LayerDescription = [0, 3, 2, 1];
|
|
167
189
|
MpegFrameHeader.ChannelMode = ['stereo', 'joint_stereo', 'dual_channel', 'mono'];
|
|
168
190
|
MpegFrameHeader.bitrate_index = {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
191
|
+
1: { 11: 32, 12: 32, 13: 32, 21: 32, 22: 8, 23: 8 },
|
|
192
|
+
2: { 11: 64, 12: 48, 13: 40, 21: 48, 22: 16, 23: 16 },
|
|
193
|
+
3: { 11: 96, 12: 56, 13: 48, 21: 56, 22: 24, 23: 24 },
|
|
194
|
+
4: { 11: 128, 12: 64, 13: 56, 21: 64, 22: 32, 23: 32 },
|
|
195
|
+
5: { 11: 160, 12: 80, 13: 64, 21: 80, 22: 40, 23: 40 },
|
|
196
|
+
6: { 11: 192, 12: 96, 13: 80, 21: 96, 22: 48, 23: 48 },
|
|
197
|
+
7: { 11: 224, 12: 112, 13: 96, 21: 112, 22: 56, 23: 56 },
|
|
198
|
+
8: { 11: 256, 12: 128, 13: 112, 21: 128, 22: 64, 23: 64 },
|
|
199
|
+
9: { 11: 288, 12: 160, 13: 128, 21: 144, 22: 80, 23: 80 },
|
|
200
|
+
10: { 11: 320, 12: 192, 13: 160, 21: 160, 22: 96, 23: 96 },
|
|
201
|
+
11: { 11: 352, 12: 224, 13: 192, 21: 176, 22: 112, 23: 112 },
|
|
202
|
+
12: { 11: 384, 12: 256, 13: 224, 21: 192, 22: 128, 23: 128 },
|
|
203
|
+
13: { 11: 416, 12: 320, 13: 256, 21: 224, 22: 144, 23: 144 },
|
|
204
|
+
14: { 11: 448, 12: 384, 13: 320, 21: 256, 22: 160, 23: 160 }
|
|
183
205
|
};
|
|
184
206
|
MpegFrameHeader.sampling_rate_freq_index = {
|
|
185
|
-
1: {
|
|
186
|
-
2: {
|
|
187
|
-
2.5: {
|
|
207
|
+
1: { 0: 44100, 1: 48000, 2: 32000 },
|
|
208
|
+
2: { 0: 22050, 1: 24000, 2: 16000 },
|
|
209
|
+
2.5: { 0: 11025, 1: 12000, 2: 8000 }
|
|
188
210
|
};
|
|
189
211
|
MpegFrameHeader.samplesInFrameTable = [
|
|
190
212
|
/* Layer I II III */
|
|
@@ -201,7 +223,7 @@ const FrameHeader = {
|
|
|
201
223
|
}
|
|
202
224
|
};
|
|
203
225
|
function getVbrCodecProfile(vbrScale) {
|
|
204
|
-
return
|
|
226
|
+
return `V${Math.floor((100 - vbrScale) / 10)}`;
|
|
205
227
|
}
|
|
206
228
|
export class MpegParser extends AbstractID3Parser {
|
|
207
229
|
constructor() {
|
|
@@ -211,8 +233,16 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
211
233
|
this.countSkipFrameData = 0;
|
|
212
234
|
this.totalDataLength = 0;
|
|
213
235
|
this.bitrates = [];
|
|
236
|
+
this.offset = 0;
|
|
237
|
+
this.frame_size = 0;
|
|
238
|
+
this.crc = null;
|
|
214
239
|
this.calculateEofDuration = false;
|
|
240
|
+
this.samplesPerFrame = null;
|
|
215
241
|
this.buf_frame_header = new Uint8Array(4);
|
|
242
|
+
/**
|
|
243
|
+
* Number of bytes already parsed since beginning of stream / file
|
|
244
|
+
*/
|
|
245
|
+
this.mpegOffset = null;
|
|
216
246
|
this.syncPeek = {
|
|
217
247
|
buf: new Uint8Array(maxPeekLen),
|
|
218
248
|
len: 0
|
|
@@ -232,13 +262,17 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
232
262
|
}
|
|
233
263
|
catch (err) {
|
|
234
264
|
if (err instanceof EndOfStreamError) {
|
|
235
|
-
debug(
|
|
265
|
+
debug("End-of-stream");
|
|
236
266
|
if (this.calculateEofDuration) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
267
|
+
if (this.samplesPerFrame !== null) {
|
|
268
|
+
const numberOfSamples = this.frameCount * this.samplesPerFrame;
|
|
269
|
+
this.metadata.setFormat('numberOfSamples', numberOfSamples);
|
|
270
|
+
if (this.metadata.format.sampleRate) {
|
|
271
|
+
const duration = numberOfSamples / this.metadata.format.sampleRate;
|
|
272
|
+
debug(`Calculate duration at EOF: ${duration} sec.`, duration);
|
|
273
|
+
this.metadata.setFormat('duration', duration);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
242
276
|
}
|
|
243
277
|
}
|
|
244
278
|
else {
|
|
@@ -251,20 +285,26 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
251
285
|
*/
|
|
252
286
|
finalize() {
|
|
253
287
|
const format = this.metadata.format;
|
|
254
|
-
const hasID3v1 = this.metadata.native.
|
|
255
|
-
if (
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
288
|
+
const hasID3v1 = !!this.metadata.native.ID3v1;
|
|
289
|
+
if (this.mpegOffset !== null) {
|
|
290
|
+
if (format.duration && this.tokenizer.fileInfo.size) {
|
|
291
|
+
const mpegSize = this.tokenizer.fileInfo.size - this.mpegOffset - (hasID3v1 ? 128 : 0);
|
|
292
|
+
if (format.codecProfile && format.codecProfile[0] === 'V') {
|
|
293
|
+
this.metadata.setFormat('bitrate', mpegSize * 8 / format.duration);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else if (this.tokenizer.fileInfo.size && format.codecProfile === 'CBR') {
|
|
297
|
+
const mpegSize = this.tokenizer.fileInfo.size - this.mpegOffset - (hasID3v1 ? 128 : 0);
|
|
298
|
+
if (this.frame_size !== null && this.samplesPerFrame !== null) {
|
|
299
|
+
const numberOfSamples = Math.round(mpegSize / this.frame_size) * this.samplesPerFrame;
|
|
300
|
+
this.metadata.setFormat('numberOfSamples', numberOfSamples);
|
|
301
|
+
if (format.sampleRate) {
|
|
302
|
+
const duration = numberOfSamples / format.sampleRate;
|
|
303
|
+
debug("Calculate CBR duration based on file size: %s", duration);
|
|
304
|
+
this.metadata.setFormat('duration', duration);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
259
307
|
}
|
|
260
|
-
}
|
|
261
|
-
else if (this.tokenizer.fileInfo.size && format.codecProfile === 'CBR') {
|
|
262
|
-
const mpegSize = this.tokenizer.fileInfo.size - this.mpegOffset - (hasID3v1 ? 128 : 0);
|
|
263
|
-
const numberOfSamples = Math.round(mpegSize / this.frame_size) * this.samplesPerFrame;
|
|
264
|
-
this.metadata.setFormat('numberOfSamples', numberOfSamples);
|
|
265
|
-
const duration = numberOfSamples / format.sampleRate;
|
|
266
|
-
debug("Calculate CBR duration based on file size: %s", duration);
|
|
267
|
-
this.metadata.setFormat('duration', duration);
|
|
268
308
|
}
|
|
269
309
|
}
|
|
270
310
|
async sync() {
|
|
@@ -289,21 +329,17 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
289
329
|
this.syncFrameCount = this.frameCount;
|
|
290
330
|
return; // sync
|
|
291
331
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
throw new EndOfStreamError();
|
|
298
|
-
}
|
|
299
|
-
await this.tokenizer.ignore(this.syncPeek.len);
|
|
300
|
-
break; // continue with next buffer
|
|
301
|
-
}
|
|
302
|
-
else {
|
|
303
|
-
++bo;
|
|
304
|
-
gotFirstSync = true;
|
|
332
|
+
gotFirstSync = false;
|
|
333
|
+
bo = this.syncPeek.buf.indexOf(MpegFrameHeader.SyncByte1, bo);
|
|
334
|
+
if (bo === -1) {
|
|
335
|
+
if (this.syncPeek.len < this.syncPeek.buf.length) {
|
|
336
|
+
throw new EndOfStreamError();
|
|
305
337
|
}
|
|
338
|
+
await this.tokenizer.ignore(this.syncPeek.len);
|
|
339
|
+
break; // continue with next buffer
|
|
306
340
|
}
|
|
341
|
+
++bo;
|
|
342
|
+
gotFirstSync = true;
|
|
307
343
|
}
|
|
308
344
|
}
|
|
309
345
|
}
|
|
@@ -322,8 +358,11 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
322
358
|
}
|
|
323
359
|
catch (err) {
|
|
324
360
|
await this.tokenizer.ignore(1);
|
|
325
|
-
|
|
326
|
-
|
|
361
|
+
if (err instanceof Error) {
|
|
362
|
+
this.metadata.addWarning(`Parse error: ${err.message}`);
|
|
363
|
+
return false; // sync
|
|
364
|
+
}
|
|
365
|
+
throw err;
|
|
327
366
|
}
|
|
328
367
|
await this.tokenizer.ignore(3);
|
|
329
368
|
this.metadata.setFormat('container', header.container);
|
|
@@ -331,7 +370,7 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
331
370
|
this.metadata.setFormat('lossless', false);
|
|
332
371
|
this.metadata.setFormat('sampleRate', header.samplingRate);
|
|
333
372
|
this.frameCount++;
|
|
334
|
-
return header.version >= 2 && header.layer === 0 ? this.parseAdts(header) : this.parseAudioFrameHeader(header);
|
|
373
|
+
return header.version !== null && header.version >= 2 && header.layer === 0 ? this.parseAdts(header) : this.parseAudioFrameHeader(header);
|
|
335
374
|
}
|
|
336
375
|
/**
|
|
337
376
|
* @return {Promise<boolean>} true if parser should quit
|
|
@@ -349,11 +388,14 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
349
388
|
const samples_per_frame = header.calcSamplesPerFrame();
|
|
350
389
|
debug(`samples_per_frame=${samples_per_frame}`);
|
|
351
390
|
const bps = samples_per_frame / 8.0;
|
|
352
|
-
|
|
353
|
-
((header.padding) ? slot_size : 0);
|
|
354
|
-
|
|
391
|
+
if (header.bitrate !== null && header.samplingRate != null) {
|
|
392
|
+
const fsize = (bps * header.bitrate / header.samplingRate) + ((header.padding) ? slot_size : 0);
|
|
393
|
+
this.frame_size = Math.floor(fsize);
|
|
394
|
+
}
|
|
355
395
|
this.audioFrameHeader = header;
|
|
356
|
-
|
|
396
|
+
if (header.bitrate !== null) {
|
|
397
|
+
this.bitrates.push(header.bitrate);
|
|
398
|
+
}
|
|
357
399
|
// xtra header only exists in first frame
|
|
358
400
|
if (this.frameCount === 1) {
|
|
359
401
|
this.offset = FrameHeader.len;
|
|
@@ -388,10 +430,8 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
388
430
|
await this.parseCrc();
|
|
389
431
|
return false;
|
|
390
432
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return false;
|
|
394
|
-
}
|
|
433
|
+
await this.skipSideInformation();
|
|
434
|
+
return false;
|
|
395
435
|
}
|
|
396
436
|
async parseAdts(header) {
|
|
397
437
|
const buf = new Uint8Array(3);
|
|
@@ -399,11 +439,13 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
399
439
|
header.frameLength += common.getBitAllignedNumber(buf, 0, 0, 11);
|
|
400
440
|
this.totalDataLength += header.frameLength;
|
|
401
441
|
this.samplesPerFrame = 1024;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
442
|
+
if (header.samplingRate !== null) {
|
|
443
|
+
const framesPerSec = header.samplingRate / this.samplesPerFrame;
|
|
444
|
+
const bytesPerFrame = this.frameCount === 0 ? 0 : this.totalDataLength / this.frameCount;
|
|
445
|
+
const bitrate = 8 * bytesPerFrame * framesPerSec + 0.5;
|
|
446
|
+
this.metadata.setFormat('bitrate', bitrate);
|
|
447
|
+
debug(`frame-count=${this.frameCount}, size=${header.frameLength} bytes, bit-rate=${bitrate}`);
|
|
448
|
+
}
|
|
407
449
|
await this.tokenizer.ignore(header.frameLength > 7 ? header.frameLength - 7 : 1);
|
|
408
450
|
// Consume remaining header and frame data
|
|
409
451
|
if (this.frameCount === 3) {
|
|
@@ -426,12 +468,16 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
426
468
|
return this.skipSideInformation();
|
|
427
469
|
}
|
|
428
470
|
async skipSideInformation() {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
471
|
+
if (this.audioFrameHeader) {
|
|
472
|
+
const sideinfo_length = this.audioFrameHeader.calculateSideInfoLength();
|
|
473
|
+
if (sideinfo_length !== null) {
|
|
474
|
+
await this.tokenizer.readToken(new Token.Uint8ArrayType(sideinfo_length));
|
|
475
|
+
// side information
|
|
476
|
+
this.offset += sideinfo_length;
|
|
477
|
+
await this.readXtraInfoHeader();
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
435
481
|
}
|
|
436
482
|
async readXtraInfoHeader() {
|
|
437
483
|
const headerTag = await this.tokenizer.readToken(InfoTagHeaderTag);
|
|
@@ -440,32 +486,34 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
440
486
|
case 'Info':
|
|
441
487
|
this.metadata.setFormat('codecProfile', 'CBR');
|
|
442
488
|
return this.readXingInfoHeader();
|
|
443
|
-
case 'Xing':
|
|
489
|
+
case 'Xing': {
|
|
444
490
|
const infoTag = await this.readXingInfoHeader();
|
|
445
|
-
|
|
446
|
-
|
|
491
|
+
if (infoTag.vbrScale !== null) {
|
|
492
|
+
const codecProfile = getVbrCodecProfile(infoTag.vbrScale);
|
|
493
|
+
this.metadata.setFormat('codecProfile', codecProfile);
|
|
494
|
+
}
|
|
447
495
|
return null;
|
|
496
|
+
}
|
|
448
497
|
case 'Xtra':
|
|
449
498
|
// ToDo: ???
|
|
450
499
|
break;
|
|
451
|
-
case 'LAME':
|
|
500
|
+
case 'LAME': {
|
|
452
501
|
const version = await this.tokenizer.readToken(LameEncoderVersion);
|
|
453
|
-
if (this.frame_size >= this.offset + LameEncoderVersion.len) {
|
|
502
|
+
if (this.frame_size !== null && this.frame_size >= this.offset + LameEncoderVersion.len) {
|
|
454
503
|
this.offset += LameEncoderVersion.len;
|
|
455
|
-
this.metadata.setFormat('tool',
|
|
504
|
+
this.metadata.setFormat('tool', `LAME ${version}`);
|
|
456
505
|
await this.skipFrameData(this.frame_size - this.offset);
|
|
457
506
|
return null;
|
|
458
507
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
508
|
+
this.metadata.addWarning('Corrupt LAME header');
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
463
511
|
// ToDo: ???
|
|
464
512
|
}
|
|
465
513
|
// ToDo: promise duration???
|
|
466
514
|
const frameDataLeft = this.frame_size - this.offset;
|
|
467
515
|
if (frameDataLeft < 0) {
|
|
468
|
-
this.metadata.addWarning(
|
|
516
|
+
this.metadata.addWarning(`Frame ${this.frameCount}corrupt: negative frameDataLeft`);
|
|
469
517
|
}
|
|
470
518
|
else {
|
|
471
519
|
await this.skipFrameData(frameDataLeft);
|
|
@@ -481,7 +529,7 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
481
529
|
const infoTag = await readXingHeader(this.tokenizer);
|
|
482
530
|
this.offset += this.tokenizer.position - offset;
|
|
483
531
|
if (infoTag.lame) {
|
|
484
|
-
this.metadata.setFormat('tool',
|
|
532
|
+
this.metadata.setFormat('tool', `LAME ${common.stripNulls(infoTag.lame.version)}`);
|
|
485
533
|
if (infoTag.lame.extended) {
|
|
486
534
|
// this.metadata.setFormat('trackGain', infoTag.lame.extended.track_gain);
|
|
487
535
|
this.metadata.setFormat('trackPeakLevel', infoTag.lame.extended.track_peak);
|
|
@@ -494,7 +542,7 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
494
542
|
this.metadata.setFormat('duration', infoTag.lame.extended.music_length / 1000);
|
|
495
543
|
}
|
|
496
544
|
}
|
|
497
|
-
if (infoTag.streamSize) {
|
|
545
|
+
if (infoTag.streamSize && this.audioFrameHeader && infoTag.numFrames !== null) {
|
|
498
546
|
const duration = this.audioFrameHeader.calcDuration(infoTag.numFrames);
|
|
499
547
|
this.metadata.setFormat('duration', duration);
|
|
500
548
|
debug('Get duration from Xing header: %s', this.metadata.format.duration);
|
|
@@ -518,3 +566,4 @@ export class MpegParser extends AbstractID3Parser {
|
|
|
518
566
|
});
|
|
519
567
|
}
|
|
520
568
|
}
|
|
569
|
+
//# sourceMappingURL=MpegParser.js.map
|
|
@@ -51,5 +51,5 @@ declare enum ReplayGainOriginator {
|
|
|
51
51
|
*
|
|
52
52
|
* https://github.com/Borewit/music-metadata/wiki/Replay-Gain-Data-Format
|
|
53
53
|
*/
|
|
54
|
-
export declare const ReplayGain: IGetToken<IReplayGain>;
|
|
54
|
+
export declare const ReplayGain: IGetToken<IReplayGain | undefined>;
|
|
55
55
|
export {};
|
package/lib/mpeg/XingTag.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Token from 'token-types';
|
|
2
2
|
import type { IGetToken, ITokenizer } from 'strtok3';
|
|
3
|
-
import { IExtendedLameHeader } from './ExtendedLameHeader.js';
|
|
3
|
+
import { type IExtendedLameHeader } from './ExtendedLameHeader.js';
|
|
4
4
|
export interface IXingHeaderFlags {
|
|
5
5
|
frames: boolean;
|
|
6
6
|
bytes: boolean;
|
|
@@ -21,16 +21,16 @@ export interface IXingInfoTag {
|
|
|
21
21
|
/**
|
|
22
22
|
* total bit stream frames from Vbr header data
|
|
23
23
|
*/
|
|
24
|
-
numFrames
|
|
24
|
+
numFrames: number | null;
|
|
25
25
|
/**
|
|
26
26
|
* Actual stream size = file size - header(s) size [bytes]
|
|
27
27
|
*/
|
|
28
|
-
streamSize
|
|
28
|
+
streamSize: number | null;
|
|
29
29
|
toc?: Uint8Array;
|
|
30
30
|
/**
|
|
31
31
|
* the number of header data bytes (from original file)
|
|
32
32
|
*/
|
|
33
|
-
vbrScale
|
|
33
|
+
vbrScale: number | null;
|
|
34
34
|
lame?: {
|
|
35
35
|
version: string;
|
|
36
36
|
extended?: IExtendedLameHeader;
|
package/lib/mpeg/XingTag.js
CHANGED
|
@@ -32,7 +32,7 @@ export const XingHeaderFlags = {
|
|
|
32
32
|
// */
|
|
33
33
|
export async function readXingHeader(tokenizer) {
|
|
34
34
|
const flags = await tokenizer.readToken(XingHeaderFlags);
|
|
35
|
-
const xingInfoTag = {};
|
|
35
|
+
const xingInfoTag = { numFrames: null, streamSize: null, vbrScale: null };
|
|
36
36
|
if (flags.frames) {
|
|
37
37
|
xingInfoTag.numFrames = await tokenizer.readToken(Token.UINT32_BE);
|
|
38
38
|
}
|
|
@@ -53,9 +53,9 @@ export async function readXingHeader(tokenizer) {
|
|
|
53
53
|
version: await tokenizer.readToken(new Token.StringType(5, 'ascii'))
|
|
54
54
|
};
|
|
55
55
|
const match = xingInfoTag.lame.version.match(/\d+.\d+/g);
|
|
56
|
-
if (match) {
|
|
57
|
-
const majorMinorVersion =
|
|
58
|
-
const version = majorMinorVersion.split('.').map(n => parseInt(n, 10));
|
|
56
|
+
if (match !== null) {
|
|
57
|
+
const majorMinorVersion = match[0]; // e.g. 3.97
|
|
58
|
+
const version = majorMinorVersion.split('.').map(n => Number.parseInt(n, 10));
|
|
59
59
|
if (version[0] >= 3 && version[1] >= 90) {
|
|
60
60
|
xingInfoTag.lame.extended = await tokenizer.readToken(ExtendedLameHeader);
|
|
61
61
|
}
|
|
@@ -63,3 +63,4 @@ export async function readXingHeader(tokenizer) {
|
|
|
63
63
|
}
|
|
64
64
|
return xingInfoTag;
|
|
65
65
|
}
|
|
66
|
+
//# sourceMappingURL=XingTag.js.map
|
package/lib/musepack/index.js
CHANGED
|
@@ -3,14 +3,14 @@ export class BitReader {
|
|
|
3
3
|
constructor(tokenizer) {
|
|
4
4
|
this.tokenizer = tokenizer;
|
|
5
5
|
this.pos = 0;
|
|
6
|
-
this.dword =
|
|
6
|
+
this.dword = null;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
*
|
|
10
10
|
* @param bits 1..30 bits
|
|
11
11
|
*/
|
|
12
12
|
async read(bits) {
|
|
13
|
-
while (this.dword ===
|
|
13
|
+
while (this.dword === null) {
|
|
14
14
|
this.dword = await this.tokenizer.readToken(Token.UINT32_LE);
|
|
15
15
|
}
|
|
16
16
|
let out = this.dword;
|
|
@@ -19,26 +19,22 @@ export class BitReader {
|
|
|
19
19
|
out >>>= (32 - this.pos);
|
|
20
20
|
return out & ((1 << bits) - 1);
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
out <<= this.pos;
|
|
32
|
-
out |= this.dword >>> (32 - this.pos);
|
|
33
|
-
}
|
|
34
|
-
return out & ((1 << bits) - 1);
|
|
35
|
-
}
|
|
22
|
+
this.pos -= 32;
|
|
23
|
+
if (this.pos === 0) {
|
|
24
|
+
this.dword = null;
|
|
25
|
+
return out & ((1 << bits) - 1);
|
|
26
|
+
}
|
|
27
|
+
this.dword = await this.tokenizer.readToken(Token.UINT32_LE);
|
|
28
|
+
if (this.pos) {
|
|
29
|
+
out <<= this.pos;
|
|
30
|
+
out |= this.dword >>> (32 - this.pos);
|
|
36
31
|
}
|
|
32
|
+
return out & ((1 << bits) - 1);
|
|
37
33
|
}
|
|
38
34
|
async ignore(bits) {
|
|
39
35
|
if (this.pos > 0) {
|
|
40
36
|
const remaining = 32 - this.pos;
|
|
41
|
-
this.dword =
|
|
37
|
+
this.dword = null;
|
|
42
38
|
bits -= remaining;
|
|
43
39
|
this.pos = 0;
|
|
44
40
|
}
|
|
@@ -48,3 +44,4 @@ export class BitReader {
|
|
|
48
44
|
return this.read(remainder);
|
|
49
45
|
}
|
|
50
46
|
}
|
|
47
|
+
//# sourceMappingURL=BitReader.js.map
|
|
@@ -7,7 +7,9 @@ const debug = initDebug('music-metadata:parser:musepack');
|
|
|
7
7
|
export class MpcSv7Parser extends BasicParser {
|
|
8
8
|
constructor() {
|
|
9
9
|
super(...arguments);
|
|
10
|
+
this.bitreader = null;
|
|
10
11
|
this.audioLength = 0;
|
|
12
|
+
this.duration = null;
|
|
11
13
|
}
|
|
12
14
|
async parse() {
|
|
13
15
|
const header = await this.tokenizer.readToken(SV7.Header);
|
|
@@ -37,6 +39,9 @@ export class MpcSv7Parser extends BasicParser {
|
|
|
37
39
|
// last frame
|
|
38
40
|
const lastFrameLength = await this.bitreader.read(11);
|
|
39
41
|
this.audioLength += lastFrameLength;
|
|
40
|
-
|
|
42
|
+
if (this.duration !== null) {
|
|
43
|
+
this.metadata.setFormat('bitrate', this.audioLength / this.duration);
|
|
44
|
+
}
|
|
41
45
|
}
|
|
42
46
|
}
|
|
47
|
+
//# sourceMappingURL=MpcSv7Parser.js.map
|
|
@@ -22,13 +22,14 @@ export class MpcSv8Parser extends BasicParser {
|
|
|
22
22
|
const header = await sv8reader.readPacketHeader();
|
|
23
23
|
debug(`packet-header key=${header.key}, payloadLength=${header.payloadLength}`);
|
|
24
24
|
switch (header.key) {
|
|
25
|
-
case 'SH': // Stream Header
|
|
25
|
+
case 'SH': { // Stream Header
|
|
26
26
|
const sh = await sv8reader.readStreamHeader(header.payloadLength);
|
|
27
27
|
this.metadata.setFormat('numberOfSamples', sh.sampleCount);
|
|
28
28
|
this.metadata.setFormat('sampleRate', sh.sampleFrequency);
|
|
29
29
|
this.metadata.setFormat('duration', sh.sampleCount / sh.sampleFrequency);
|
|
30
30
|
this.metadata.setFormat('numberOfChannels', sh.channelCount);
|
|
31
31
|
break;
|
|
32
|
+
}
|
|
32
33
|
case 'AP': // Audio Packet
|
|
33
34
|
this.audioLength += header.payloadLength;
|
|
34
35
|
await this.tokenizer.ignore(header.payloadLength);
|
|
@@ -41,7 +42,9 @@ export class MpcSv8Parser extends BasicParser {
|
|
|
41
42
|
await this.tokenizer.ignore(header.payloadLength);
|
|
42
43
|
break;
|
|
43
44
|
case 'SE': // Stream End
|
|
44
|
-
|
|
45
|
+
if (this.metadata.format.duration) {
|
|
46
|
+
this.metadata.setFormat('bitrate', this.audioLength * 8 / this.metadata.format.duration);
|
|
47
|
+
}
|
|
45
48
|
return APEv2Parser.tryParseApeHeader(this.metadata, this.tokenizer, this.options);
|
|
46
49
|
default:
|
|
47
50
|
throw new Error(`Unexpected header: ${header.key}`);
|
|
@@ -49,3 +52,4 @@ export class MpcSv8Parser extends BasicParser {
|
|
|
49
52
|
} while (true);
|
|
50
53
|
}
|
|
51
54
|
}
|
|
55
|
+
//# sourceMappingURL=MpcSv8Parser.js.map
|
package/lib/ogg/Ogg.d.ts
CHANGED
package/lib/ogg/Ogg.js
CHANGED