music-metadata 7.12.3 → 7.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/LICENSE.txt +9 -9
  2. package/README.md +434 -434
  3. package/lib/ParserFactory.d.ts +48 -48
  4. package/lib/ParserFactory.js +252 -252
  5. package/lib/aiff/AiffParser.d.ts +14 -14
  6. package/lib/aiff/AiffParser.js +84 -84
  7. package/lib/aiff/AiffToken.d.ts +22 -22
  8. package/lib/aiff/AiffToken.js +43 -43
  9. package/lib/apev2/APEv2Parser.d.ts +30 -30
  10. package/lib/apev2/APEv2Parser.js +164 -164
  11. package/lib/apev2/APEv2TagMapper.d.ts +4 -4
  12. package/lib/apev2/APEv2TagMapper.js +86 -86
  13. package/lib/apev2/APEv2Token.d.ts +100 -100
  14. package/lib/apev2/APEv2Token.js +126 -126
  15. package/lib/asf/AsfObject.d.ts +319 -319
  16. package/lib/asf/AsfObject.js +381 -381
  17. package/lib/asf/AsfParser.d.ts +17 -17
  18. package/lib/asf/AsfParser.js +135 -135
  19. package/lib/asf/AsfTagMapper.d.ts +7 -7
  20. package/lib/asf/AsfTagMapper.js +95 -95
  21. package/lib/asf/AsfUtil.d.ts +13 -13
  22. package/lib/asf/AsfUtil.js +38 -38
  23. package/lib/asf/GUID.d.ts +84 -84
  24. package/lib/asf/GUID.js +121 -121
  25. package/lib/common/BasicParser.d.ts +17 -17
  26. package/lib/common/BasicParser.js +18 -18
  27. package/lib/common/CaseInsensitiveTagMap.d.ts +10 -10
  28. package/lib/common/CaseInsensitiveTagMap.js +21 -21
  29. package/lib/common/CombinedTagMapper.d.ts +19 -19
  30. package/lib/common/CombinedTagMapper.js +51 -51
  31. package/lib/common/FourCC.d.ts +6 -6
  32. package/lib/common/FourCC.js +28 -28
  33. package/lib/common/GenericTagMapper.d.ts +51 -51
  34. package/lib/common/GenericTagMapper.js +55 -55
  35. package/lib/common/GenericTagTypes.d.ts +33 -33
  36. package/lib/common/GenericTagTypes.js +131 -131
  37. package/lib/common/MetadataCollector.d.ts +76 -76
  38. package/lib/common/MetadataCollector.js +275 -275
  39. package/lib/common/RandomFileReader.d.ts +22 -22
  40. package/lib/common/RandomFileReader.js +34 -34
  41. package/lib/common/RandomUint8ArrayReader.d.ts +18 -18
  42. package/lib/common/RandomUint8ArrayReader.js +25 -25
  43. package/lib/common/Util.d.ts +57 -57
  44. package/lib/common/Util.js +157 -157
  45. package/lib/core.d.ts +48 -48
  46. package/lib/core.js +90 -90
  47. package/lib/dsdiff/DsdiffParser.d.ts +14 -14
  48. package/lib/dsdiff/DsdiffParser.js +143 -143
  49. package/lib/dsdiff/DsdiffToken.d.ts +9 -9
  50. package/lib/dsdiff/DsdiffToken.js +21 -21
  51. package/lib/dsf/DsfChunk.d.ts +86 -86
  52. package/lib/dsf/DsfChunk.js +54 -54
  53. package/lib/dsf/DsfParser.d.ts +9 -9
  54. package/lib/dsf/DsfParser.js +56 -56
  55. package/lib/flac/FlacParser.d.ts +28 -28
  56. package/lib/flac/FlacParser.js +175 -175
  57. package/lib/id3v1/ID3v1Parser.d.ts +13 -13
  58. package/lib/id3v1/ID3v1Parser.js +134 -134
  59. package/lib/id3v1/ID3v1TagMap.d.ts +4 -4
  60. package/lib/id3v1/ID3v1TagMap.js +22 -22
  61. package/lib/id3v2/AbstractID3Parser.d.ts +17 -17
  62. package/lib/id3v2/AbstractID3Parser.js +60 -60
  63. package/lib/id3v2/FrameParser.d.ts +31 -31
  64. package/lib/id3v2/FrameParser.js +329 -329
  65. package/lib/id3v2/ID3v22TagMapper.d.ts +9 -9
  66. package/lib/id3v2/ID3v22TagMapper.js +55 -55
  67. package/lib/id3v2/ID3v24TagMapper.d.ts +14 -14
  68. package/lib/id3v2/ID3v24TagMapper.js +193 -193
  69. package/lib/id3v2/ID3v2Parser.d.ts +28 -28
  70. package/lib/id3v2/ID3v2Parser.js +182 -182
  71. package/lib/id3v2/ID3v2Token.d.ts +73 -73
  72. package/lib/id3v2/ID3v2Token.js +106 -106
  73. package/lib/iff/index.d.ts +33 -33
  74. package/lib/iff/index.js +19 -19
  75. package/lib/index.d.ts +45 -45
  76. package/lib/index.js +74 -74
  77. package/lib/lyrics3/Lyrics3.d.ts +3 -3
  78. package/lib/lyrics3/Lyrics3.js +17 -17
  79. package/lib/matroska/MatroskaDtd.d.ts +8 -8
  80. package/lib/matroska/MatroskaDtd.js +279 -279
  81. package/lib/matroska/MatroskaParser.d.ts +37 -37
  82. package/lib/matroska/MatroskaParser.js +235 -235
  83. package/lib/matroska/MatroskaTagMapper.d.ts +4 -4
  84. package/lib/matroska/MatroskaTagMapper.js +35 -35
  85. package/lib/matroska/types.d.ts +175 -175
  86. package/lib/matroska/types.js +32 -32
  87. package/lib/mp4/Atom.d.ts +16 -16
  88. package/lib/mp4/Atom.js +70 -70
  89. package/lib/mp4/AtomToken.d.ts +395 -395
  90. package/lib/mp4/AtomToken.js +406 -406
  91. package/lib/mp4/MP4Parser.d.ts +30 -30
  92. package/lib/mp4/MP4Parser.js +511 -511
  93. package/lib/mp4/MP4TagMapper.d.ts +5 -5
  94. package/lib/mp4/MP4TagMapper.js +115 -115
  95. package/lib/mpeg/ExtendedLameHeader.d.ts +27 -27
  96. package/lib/mpeg/ExtendedLameHeader.js +31 -31
  97. package/lib/mpeg/MpegParser.d.ts +49 -49
  98. package/lib/mpeg/MpegParser.js +524 -524
  99. package/lib/mpeg/ReplayGainDataFormat.d.ts +55 -55
  100. package/lib/mpeg/ReplayGainDataFormat.js +69 -69
  101. package/lib/mpeg/XingTag.d.ts +45 -45
  102. package/lib/mpeg/XingTag.js +69 -69
  103. package/lib/musepack/index.d.ts +5 -5
  104. package/lib/musepack/index.js +32 -32
  105. package/lib/musepack/sv7/BitReader.d.ts +13 -13
  106. package/lib/musepack/sv7/BitReader.js +54 -54
  107. package/lib/musepack/sv7/MpcSv7Parser.d.ts +8 -8
  108. package/lib/musepack/sv7/MpcSv7Parser.js +46 -46
  109. package/lib/musepack/sv7/StreamVersion7.d.ts +28 -28
  110. package/lib/musepack/sv7/StreamVersion7.js +41 -41
  111. package/lib/musepack/sv8/MpcSv8Parser.d.ts +6 -6
  112. package/lib/musepack/sv8/MpcSv8Parser.js +55 -55
  113. package/lib/musepack/sv8/StreamVersion8.d.ts +40 -40
  114. package/lib/musepack/sv8/StreamVersion8.js +80 -80
  115. package/lib/ogg/Ogg.d.ts +72 -72
  116. package/lib/ogg/Ogg.js +2 -2
  117. package/lib/ogg/OggParser.d.ts +23 -23
  118. package/lib/ogg/OggParser.js +126 -126
  119. package/lib/ogg/opus/Opus.d.ts +48 -48
  120. package/lib/ogg/opus/Opus.js +28 -28
  121. package/lib/ogg/opus/OpusParser.d.ts +25 -25
  122. package/lib/ogg/opus/OpusParser.js +56 -56
  123. package/lib/ogg/speex/Speex.d.ts +36 -36
  124. package/lib/ogg/speex/Speex.js +31 -31
  125. package/lib/ogg/speex/SpeexParser.d.ts +22 -22
  126. package/lib/ogg/speex/SpeexParser.js +35 -35
  127. package/lib/ogg/theora/Theora.d.ts +20 -20
  128. package/lib/ogg/theora/Theora.js +23 -23
  129. package/lib/ogg/theora/TheoraParser.d.ts +28 -28
  130. package/lib/ogg/theora/TheoraParser.js +44 -44
  131. package/lib/ogg/vorbis/Vorbis.d.ts +69 -69
  132. package/lib/ogg/vorbis/Vorbis.js +78 -78
  133. package/lib/ogg/vorbis/VorbisDecoder.d.ts +12 -12
  134. package/lib/ogg/vorbis/VorbisDecoder.js +32 -32
  135. package/lib/ogg/vorbis/VorbisParser.d.ts +36 -36
  136. package/lib/ogg/vorbis/VorbisParser.js +128 -128
  137. package/lib/ogg/vorbis/VorbisTagMapper.d.ts +7 -7
  138. package/lib/ogg/vorbis/VorbisTagMapper.js +132 -132
  139. package/lib/riff/RiffChunk.d.ts +16 -16
  140. package/lib/riff/RiffChunk.js +32 -32
  141. package/lib/riff/RiffInfoTagMap.d.ts +10 -10
  142. package/lib/riff/RiffInfoTagMap.js +37 -37
  143. package/lib/type.d.ts +592 -592
  144. package/lib/type.js +5 -5
  145. package/lib/wav/BwfChunk.d.ts +17 -17
  146. package/lib/wav/BwfChunk.js +29 -28
  147. package/lib/wav/WaveChunk.d.ts +64 -64
  148. package/lib/wav/WaveChunk.js +65 -65
  149. package/lib/wav/WaveParser.d.ts +24 -24
  150. package/lib/wav/WaveParser.js +158 -156
  151. package/lib/wavpack/WavPackParser.d.ts +14 -14
  152. package/lib/wavpack/WavPackParser.js +99 -99
  153. package/lib/wavpack/WavPackToken.d.ts +64 -64
  154. package/lib/wavpack/WavPackToken.js +76 -76
  155. package/package.json +150 -150
@@ -1,524 +1,524 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MpegParser = void 0;
4
- const Token = require("token-types");
5
- const core_1 = require("strtok3/lib/core");
6
- const debug_1 = require("debug");
7
- const common = require("../common/Util");
8
- const AbstractID3Parser_1 = require("../id3v2/AbstractID3Parser");
9
- const XingTag_1 = require("./XingTag");
10
- const debug = (0, debug_1.default)('music-metadata:parser:mpeg');
11
- /**
12
- * Cache buffer size used for searching synchronization preabmle
13
- */
14
- const maxPeekLen = 1024;
15
- /**
16
- * MPEG-4 Audio definitions
17
- * Ref: https://wiki.multimedia.cx/index.php/MPEG-4_Audio
18
- */
19
- const MPEG4 = {
20
- /**
21
- * Audio Object Types
22
- */
23
- AudioObjectTypes: [
24
- 'AAC Main',
25
- 'AAC LC',
26
- 'AAC SSR',
27
- 'AAC LTP' // Long Term Prediction
28
- ],
29
- /**
30
- * Sampling Frequencies
31
- * https://wiki.multimedia.cx/index.php/MPEG-4_Audio#Sampling_Frequencies
32
- */
33
- SamplingFrequencies: [
34
- 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, undefined, undefined, -1
35
- ]
36
- /**
37
- * Channel Configurations
38
- */
39
- };
40
- const MPEG4_ChannelConfigurations = [
41
- undefined,
42
- ['front-center'],
43
- ['front-left', 'front-right'],
44
- ['front-center', 'front-left', 'front-right'],
45
- ['front-center', 'front-left', 'front-right', 'back-center'],
46
- ['front-center', 'front-left', 'front-right', 'back-left', 'back-right'],
47
- ['front-center', 'front-left', 'front-right', 'back-left', 'back-right', 'LFE-channel'],
48
- ['front-center', 'front-left', 'front-right', 'side-left', 'side-right', 'back-left', 'back-right', 'LFE-channel']
49
- ];
50
- /**
51
- * MPEG Audio Layer I/II/III frame header
52
- * Ref: https://www.mp3-tech.org/programmer/frame_header.html
53
- * Bit layout: AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
54
- * Ref: https://wiki.multimedia.cx/index.php/ADTS
55
- */
56
- class MpegFrameHeader {
57
- constructor(buf, off) {
58
- // B(20,19): MPEG Audio versionIndex ID
59
- this.versionIndex = common.getBitAllignedNumber(buf, off + 1, 3, 2);
60
- // C(18,17): Layer description
61
- this.layer = MpegFrameHeader.LayerDescription[common.getBitAllignedNumber(buf, off + 1, 5, 2)];
62
- if (this.versionIndex > 1 && this.layer === 0) {
63
- this.parseAdtsHeader(buf, off); // Audio Data Transport Stream (ADTS)
64
- }
65
- else {
66
- this.parseMpegHeader(buf, off); // Conventional MPEG header
67
- }
68
- // D(16): Protection bit (if true 16-bit CRC follows header)
69
- this.isProtectedByCRC = !common.isBitSet(buf, off + 1, 7);
70
- }
71
- calcDuration(numFrames) {
72
- return numFrames * this.calcSamplesPerFrame() / this.samplingRate;
73
- }
74
- calcSamplesPerFrame() {
75
- return MpegFrameHeader.samplesInFrameTable[this.version === 1 ? 0 : 1][this.layer];
76
- }
77
- calculateSideInfoLength() {
78
- if (this.layer !== 3)
79
- return 2;
80
- if (this.channelModeIndex === 3) {
81
- // mono
82
- if (this.version === 1) {
83
- return 17;
84
- }
85
- else if (this.version === 2 || this.version === 2.5) {
86
- return 9;
87
- }
88
- }
89
- else {
90
- if (this.version === 1) {
91
- return 32;
92
- }
93
- else if (this.version === 2 || this.version === 2.5) {
94
- return 17;
95
- }
96
- }
97
- }
98
- calcSlotSize() {
99
- return [null, 4, 1, 1][this.layer];
100
- }
101
- parseMpegHeader(buf, off) {
102
- this.container = 'MPEG';
103
- // E(15,12): Bitrate index
104
- this.bitrateIndex = common.getBitAllignedNumber(buf, off + 2, 0, 4);
105
- // F(11,10): Sampling rate frequency index
106
- this.sampRateFreqIndex = common.getBitAllignedNumber(buf, off + 2, 4, 2);
107
- // G(9): Padding bit
108
- this.padding = common.isBitSet(buf, off + 2, 6);
109
- // H(8): Private bit
110
- this.privateBit = common.isBitSet(buf, off + 2, 7);
111
- // I(7,6): Channel Mode
112
- this.channelModeIndex = common.getBitAllignedNumber(buf, off + 3, 0, 2);
113
- // J(5,4): Mode extension (Only used in Joint stereo)
114
- this.modeExtension = common.getBitAllignedNumber(buf, off + 3, 2, 2);
115
- // K(3): Copyright
116
- this.isCopyrighted = common.isBitSet(buf, off + 3, 4);
117
- // L(2): Original
118
- this.isOriginalMedia = common.isBitSet(buf, off + 3, 5);
119
- // M(3): The original bit indicates, if it is set, that the frame is located on its original media.
120
- this.emphasis = common.getBitAllignedNumber(buf, off + 3, 7, 2);
121
- this.version = MpegFrameHeader.VersionID[this.versionIndex];
122
- this.channelMode = MpegFrameHeader.ChannelMode[this.channelModeIndex];
123
- this.codec = `MPEG ${this.version} Layer ${this.layer}`;
124
- // Calculate bitrate
125
- const bitrateInKbps = this.calcBitrate();
126
- if (!bitrateInKbps) {
127
- throw new Error('Cannot determine bit-rate');
128
- }
129
- this.bitrate = bitrateInKbps * 1000;
130
- // Calculate sampling rate
131
- this.samplingRate = this.calcSamplingRate();
132
- if (this.samplingRate == null) {
133
- throw new Error('Cannot determine sampling-rate');
134
- }
135
- }
136
- parseAdtsHeader(buf, off) {
137
- debug(`layer=0 => ADTS`);
138
- this.version = this.versionIndex === 2 ? 4 : 2;
139
- this.container = 'ADTS/MPEG-' + this.version;
140
- const profileIndex = common.getBitAllignedNumber(buf, off + 2, 0, 2);
141
- this.codec = 'AAC';
142
- this.codecProfile = MPEG4.AudioObjectTypes[profileIndex];
143
- debug(`MPEG-4 audio-codec=${this.codec}`);
144
- const samplingFrequencyIndex = common.getBitAllignedNumber(buf, off + 2, 2, 4);
145
- this.samplingRate = MPEG4.SamplingFrequencies[samplingFrequencyIndex];
146
- debug(`sampling-rate=${this.samplingRate}`);
147
- const channelIndex = common.getBitAllignedNumber(buf, off + 2, 7, 3);
148
- this.mp4ChannelConfig = MPEG4_ChannelConfigurations[channelIndex];
149
- debug(`channel-config=${this.mp4ChannelConfig.join('+')}`);
150
- this.frameLength = common.getBitAllignedNumber(buf, off + 3, 6, 2) << 11;
151
- }
152
- calcBitrate() {
153
- if (this.bitrateIndex === 0x00 || // free
154
- this.bitrateIndex === 0x0F) { // reserved
155
- return;
156
- }
157
- const codecIndex = `${Math.floor(this.version)}${this.layer}`;
158
- return MpegFrameHeader.bitrate_index[this.bitrateIndex][codecIndex];
159
- }
160
- calcSamplingRate() {
161
- if (this.sampRateFreqIndex === 0x03)
162
- return null; // 'reserved'
163
- return MpegFrameHeader.sampling_rate_freq_index[this.version][this.sampRateFreqIndex];
164
- }
165
- }
166
- MpegFrameHeader.SyncByte1 = 0xFF;
167
- MpegFrameHeader.SyncByte2 = 0xE0;
168
- MpegFrameHeader.VersionID = [2.5, null, 2, 1];
169
- MpegFrameHeader.LayerDescription = [0, 3, 2, 1];
170
- MpegFrameHeader.ChannelMode = ['stereo', 'joint_stereo', 'dual_channel', 'mono'];
171
- MpegFrameHeader.bitrate_index = {
172
- 0x01: { 11: 32, 12: 32, 13: 32, 21: 32, 22: 8, 23: 8 },
173
- 0x02: { 11: 64, 12: 48, 13: 40, 21: 48, 22: 16, 23: 16 },
174
- 0x03: { 11: 96, 12: 56, 13: 48, 21: 56, 22: 24, 23: 24 },
175
- 0x04: { 11: 128, 12: 64, 13: 56, 21: 64, 22: 32, 23: 32 },
176
- 0x05: { 11: 160, 12: 80, 13: 64, 21: 80, 22: 40, 23: 40 },
177
- 0x06: { 11: 192, 12: 96, 13: 80, 21: 96, 22: 48, 23: 48 },
178
- 0x07: { 11: 224, 12: 112, 13: 96, 21: 112, 22: 56, 23: 56 },
179
- 0x08: { 11: 256, 12: 128, 13: 112, 21: 128, 22: 64, 23: 64 },
180
- 0x09: { 11: 288, 12: 160, 13: 128, 21: 144, 22: 80, 23: 80 },
181
- 0x0A: { 11: 320, 12: 192, 13: 160, 21: 160, 22: 96, 23: 96 },
182
- 0x0B: { 11: 352, 12: 224, 13: 192, 21: 176, 22: 112, 23: 112 },
183
- 0x0C: { 11: 384, 12: 256, 13: 224, 21: 192, 22: 128, 23: 128 },
184
- 0x0D: { 11: 416, 12: 320, 13: 256, 21: 224, 22: 144, 23: 144 },
185
- 0x0E: { 11: 448, 12: 384, 13: 320, 21: 256, 22: 160, 23: 160 }
186
- };
187
- MpegFrameHeader.sampling_rate_freq_index = {
188
- 1: { 0x00: 44100, 0x01: 48000, 0x02: 32000 },
189
- 2: { 0x00: 22050, 0x01: 24000, 0x02: 16000 },
190
- 2.5: { 0x00: 11025, 0x01: 12000, 0x02: 8000 }
191
- };
192
- MpegFrameHeader.samplesInFrameTable = [
193
- /* Layer I II III */
194
- [0, 384, 1152, 1152],
195
- [0, 384, 1152, 576] // MPEG-2(.5
196
- ];
197
- /**
198
- * MPEG Audio Layer I/II/III
199
- */
200
- const FrameHeader = {
201
- len: 4,
202
- get: (buf, off) => {
203
- return new MpegFrameHeader(buf, off);
204
- }
205
- };
206
- function getVbrCodecProfile(vbrScale) {
207
- return 'V' + Math.floor((100 - vbrScale) / 10);
208
- }
209
- class MpegParser extends AbstractID3Parser_1.AbstractID3Parser {
210
- constructor() {
211
- super(...arguments);
212
- this.frameCount = 0;
213
- this.syncFrameCount = -1;
214
- this.countSkipFrameData = 0;
215
- this.totalDataLength = 0;
216
- this.bitrates = [];
217
- this.calculateEofDuration = false;
218
- this.buf_frame_header = Buffer.alloc(4);
219
- this.syncPeek = {
220
- buf: Buffer.alloc(maxPeekLen),
221
- len: 0
222
- };
223
- }
224
- /**
225
- * Called after ID3 headers have been parsed
226
- */
227
- async postId3v2Parse() {
228
- this.metadata.setFormat('lossless', false);
229
- try {
230
- let quit = false;
231
- while (!quit) {
232
- await this.sync();
233
- quit = await this.parseCommonMpegHeader();
234
- }
235
- }
236
- catch (err) {
237
- if (err instanceof core_1.EndOfStreamError) {
238
- debug(`End-of-stream`);
239
- if (this.calculateEofDuration) {
240
- const numberOfSamples = this.frameCount * this.samplesPerFrame;
241
- this.metadata.setFormat('numberOfSamples', numberOfSamples);
242
- const duration = numberOfSamples / this.metadata.format.sampleRate;
243
- debug(`Calculate duration at EOF: ${duration} sec.`, duration);
244
- this.metadata.setFormat('duration', duration);
245
- }
246
- }
247
- else {
248
- throw err;
249
- }
250
- }
251
- }
252
- /**
253
- * Called after file has been fully parsed, this allows, if present, to exclude the ID3v1.1 header length
254
- */
255
- finalize() {
256
- const format = this.metadata.format;
257
- const hasID3v1 = this.metadata.native.hasOwnProperty('ID3v1');
258
- if (format.duration && this.tokenizer.fileInfo.size) {
259
- const mpegSize = this.tokenizer.fileInfo.size - this.mpegOffset - (hasID3v1 ? 128 : 0);
260
- if (format.codecProfile && format.codecProfile[0] === 'V') {
261
- this.metadata.setFormat('bitrate', mpegSize * 8 / format.duration);
262
- }
263
- }
264
- else if (this.tokenizer.fileInfo.size && format.codecProfile === 'CBR') {
265
- const mpegSize = this.tokenizer.fileInfo.size - this.mpegOffset - (hasID3v1 ? 128 : 0);
266
- const numberOfSamples = Math.round(mpegSize / this.frame_size) * this.samplesPerFrame;
267
- this.metadata.setFormat('numberOfSamples', numberOfSamples);
268
- const duration = numberOfSamples / format.sampleRate;
269
- debug("Calculate CBR duration based on file size: %s", duration);
270
- this.metadata.setFormat('duration', duration);
271
- }
272
- }
273
- async sync() {
274
- let gotFirstSync = false;
275
- while (true) {
276
- let bo = 0;
277
- this.syncPeek.len = await this.tokenizer.peekBuffer(this.syncPeek.buf, { length: maxPeekLen, mayBeLess: true });
278
- if (this.syncPeek.len <= 163) {
279
- throw new core_1.EndOfStreamError();
280
- }
281
- while (true) {
282
- if (gotFirstSync && (this.syncPeek.buf[bo] & 0xE0) === 0xE0) {
283
- this.buf_frame_header[0] = MpegFrameHeader.SyncByte1;
284
- this.buf_frame_header[1] = this.syncPeek.buf[bo];
285
- await this.tokenizer.ignore(bo);
286
- debug(`Sync at offset=${this.tokenizer.position - 1}, frameCount=${this.frameCount}`);
287
- if (this.syncFrameCount === this.frameCount) {
288
- debug(`Re-synced MPEG stream, frameCount=${this.frameCount}`);
289
- this.frameCount = 0;
290
- this.frame_size = 0;
291
- }
292
- this.syncFrameCount = this.frameCount;
293
- return; // sync
294
- }
295
- else {
296
- gotFirstSync = false;
297
- bo = this.syncPeek.buf.indexOf(MpegFrameHeader.SyncByte1, bo);
298
- if (bo === -1) {
299
- if (this.syncPeek.len < this.syncPeek.buf.length) {
300
- throw new core_1.EndOfStreamError();
301
- }
302
- await this.tokenizer.ignore(this.syncPeek.len);
303
- break; // continue with next buffer
304
- }
305
- else {
306
- ++bo;
307
- gotFirstSync = true;
308
- }
309
- }
310
- }
311
- }
312
- }
313
- /**
314
- * Combined ADTS & MPEG (MP2 & MP3) header handling
315
- * @return {Promise<boolean>} true if parser should quit
316
- */
317
- async parseCommonMpegHeader() {
318
- if (this.frameCount === 0) {
319
- this.mpegOffset = this.tokenizer.position - 1;
320
- }
321
- await this.tokenizer.peekBuffer(this.buf_frame_header, { offset: 1, length: 3 });
322
- let header;
323
- try {
324
- header = FrameHeader.get(this.buf_frame_header, 0);
325
- }
326
- catch (err) {
327
- await this.tokenizer.ignore(1);
328
- this.metadata.addWarning('Parse error: ' + err.message);
329
- return false; // sync
330
- }
331
- await this.tokenizer.ignore(3);
332
- this.metadata.setFormat('container', header.container);
333
- this.metadata.setFormat('codec', header.codec);
334
- this.metadata.setFormat('lossless', false);
335
- this.metadata.setFormat('sampleRate', header.samplingRate);
336
- this.frameCount++;
337
- return header.version >= 2 && header.layer === 0 ? this.parseAdts(header) : this.parseAudioFrameHeader(header);
338
- }
339
- /**
340
- * @return {Promise<boolean>} true if parser should quit
341
- */
342
- async parseAudioFrameHeader(header) {
343
- this.metadata.setFormat('numberOfChannels', header.channelMode === 'mono' ? 1 : 2);
344
- this.metadata.setFormat('bitrate', header.bitrate);
345
- if (this.frameCount < 20 * 10000) {
346
- debug('offset=%s MP%s bitrate=%s sample-rate=%s', this.tokenizer.position - 4, header.layer, header.bitrate, header.samplingRate);
347
- }
348
- const slot_size = header.calcSlotSize();
349
- if (slot_size === null) {
350
- throw new Error('invalid slot_size');
351
- }
352
- const samples_per_frame = header.calcSamplesPerFrame();
353
- debug(`samples_per_frame=${samples_per_frame}`);
354
- const bps = samples_per_frame / 8.0;
355
- const fsize = (bps * header.bitrate / header.samplingRate) +
356
- ((header.padding) ? slot_size : 0);
357
- this.frame_size = Math.floor(fsize);
358
- this.audioFrameHeader = header;
359
- this.bitrates.push(header.bitrate);
360
- // xtra header only exists in first frame
361
- if (this.frameCount === 1) {
362
- this.offset = FrameHeader.len;
363
- await this.skipSideInformation();
364
- return false;
365
- }
366
- if (this.frameCount === 3) {
367
- // the stream is CBR if the first 3 frame bitrates are the same
368
- if (this.areAllSame(this.bitrates)) {
369
- // Actual calculation will be done in finalize
370
- this.samplesPerFrame = samples_per_frame;
371
- this.metadata.setFormat('codecProfile', 'CBR');
372
- if (this.tokenizer.fileInfo.size)
373
- return true; // Will calculate duration based on the file size
374
- }
375
- else if (this.metadata.format.duration) {
376
- return true; // We already got the duration, stop processing MPEG stream any further
377
- }
378
- if (!this.options.duration) {
379
- return true; // Enforce duration not enabled, stop processing entire stream
380
- }
381
- }
382
- // once we know the file is VBR attach listener to end of
383
- // stream so we can do the duration calculation when we
384
- // have counted all the frames
385
- if (this.options.duration && this.frameCount === 4) {
386
- this.samplesPerFrame = samples_per_frame;
387
- this.calculateEofDuration = true;
388
- }
389
- this.offset = 4;
390
- if (header.isProtectedByCRC) {
391
- await this.parseCrc();
392
- return false;
393
- }
394
- else {
395
- await this.skipSideInformation();
396
- return false;
397
- }
398
- }
399
- async parseAdts(header) {
400
- const buf = Buffer.alloc(3);
401
- await this.tokenizer.readBuffer(buf);
402
- header.frameLength += common.getBitAllignedNumber(buf, 0, 0, 11);
403
- this.totalDataLength += header.frameLength;
404
- this.samplesPerFrame = 1024;
405
- const framesPerSec = header.samplingRate / this.samplesPerFrame;
406
- const bytesPerFrame = this.frameCount === 0 ? 0 : this.totalDataLength / this.frameCount;
407
- const bitrate = 8 * bytesPerFrame * framesPerSec + 0.5;
408
- this.metadata.setFormat('bitrate', bitrate);
409
- debug(`frame-count=${this.frameCount}, size=${header.frameLength} bytes, bit-rate=${bitrate}`);
410
- await this.tokenizer.ignore(header.frameLength > 7 ? header.frameLength - 7 : 1);
411
- // Consume remaining header and frame data
412
- if (this.frameCount === 3) {
413
- this.metadata.setFormat('codecProfile', header.codecProfile);
414
- if (header.mp4ChannelConfig) {
415
- this.metadata.setFormat('numberOfChannels', header.mp4ChannelConfig.length);
416
- }
417
- if (this.options.duration) {
418
- this.calculateEofDuration = true;
419
- }
420
- else {
421
- return true; // Stop parsing after the third frame
422
- }
423
- }
424
- return false;
425
- }
426
- async parseCrc() {
427
- this.crc = await this.tokenizer.readNumber(Token.INT16_BE);
428
- this.offset += 2;
429
- return this.skipSideInformation();
430
- }
431
- async skipSideInformation() {
432
- const sideinfo_length = this.audioFrameHeader.calculateSideInfoLength();
433
- // side information
434
- await this.tokenizer.readToken(new Token.Uint8ArrayType(sideinfo_length));
435
- this.offset += sideinfo_length;
436
- await this.readXtraInfoHeader();
437
- return;
438
- }
439
- async readXtraInfoHeader() {
440
- const headerTag = await this.tokenizer.readToken(XingTag_1.InfoTagHeaderTag);
441
- this.offset += XingTag_1.InfoTagHeaderTag.len; // 12
442
- switch (headerTag) {
443
- case 'Info':
444
- this.metadata.setFormat('codecProfile', 'CBR');
445
- return this.readXingInfoHeader();
446
- case 'Xing':
447
- const infoTag = await this.readXingInfoHeader();
448
- const codecProfile = getVbrCodecProfile(infoTag.vbrScale);
449
- this.metadata.setFormat('codecProfile', codecProfile);
450
- return null;
451
- case 'Xtra':
452
- // ToDo: ???
453
- break;
454
- case 'LAME':
455
- const version = await this.tokenizer.readToken(XingTag_1.LameEncoderVersion);
456
- if (this.frame_size >= this.offset + XingTag_1.LameEncoderVersion.len) {
457
- this.offset += XingTag_1.LameEncoderVersion.len;
458
- this.metadata.setFormat('tool', 'LAME ' + version);
459
- await this.skipFrameData(this.frame_size - this.offset);
460
- return null;
461
- }
462
- else {
463
- this.metadata.addWarning('Corrupt LAME header');
464
- break;
465
- }
466
- // ToDo: ???
467
- }
468
- // ToDo: promise duration???
469
- const frameDataLeft = this.frame_size - this.offset;
470
- if (frameDataLeft < 0) {
471
- this.metadata.addWarning('Frame ' + this.frameCount + 'corrupt: negative frameDataLeft');
472
- }
473
- else {
474
- await this.skipFrameData(frameDataLeft);
475
- }
476
- return null;
477
- }
478
- /**
479
- * Ref: http://gabriel.mp3-tech.org/mp3infotag.html
480
- * @returns {Promise<string>}
481
- */
482
- async readXingInfoHeader() {
483
- const offset = this.tokenizer.position;
484
- const infoTag = await (0, XingTag_1.readXingHeader)(this.tokenizer);
485
- this.offset += this.tokenizer.position - offset;
486
- if (infoTag.lame) {
487
- this.metadata.setFormat('tool', 'LAME ' + common.stripNulls(infoTag.lame.version));
488
- if (infoTag.lame.extended) {
489
- // this.metadata.setFormat('trackGain', infoTag.lame.extended.track_gain);
490
- this.metadata.setFormat('trackPeakLevel', infoTag.lame.extended.track_peak);
491
- if (infoTag.lame.extended.track_gain) {
492
- this.metadata.setFormat('trackGain', infoTag.lame.extended.track_gain.adjustment);
493
- }
494
- if (infoTag.lame.extended.album_gain) {
495
- this.metadata.setFormat('albumGain', infoTag.lame.extended.album_gain.adjustment);
496
- }
497
- this.metadata.setFormat('duration', infoTag.lame.extended.music_length / 1000);
498
- }
499
- }
500
- if (infoTag.streamSize) {
501
- const duration = this.audioFrameHeader.calcDuration(infoTag.numFrames);
502
- this.metadata.setFormat('duration', duration);
503
- debug('Get duration from Xing header: %s', this.metadata.format.duration);
504
- return infoTag;
505
- }
506
- // frames field is not present
507
- const frameDataLeft = this.frame_size - this.offset;
508
- await this.skipFrameData(frameDataLeft);
509
- return infoTag;
510
- }
511
- async skipFrameData(frameDataLeft) {
512
- if (frameDataLeft < 0)
513
- throw new Error('frame-data-left cannot be negative');
514
- await this.tokenizer.ignore(frameDataLeft);
515
- this.countSkipFrameData += frameDataLeft;
516
- }
517
- areAllSame(array) {
518
- const first = array[0];
519
- return array.every(element => {
520
- return element === first;
521
- });
522
- }
523
- }
524
- exports.MpegParser = MpegParser;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MpegParser = void 0;
4
+ const Token = require("token-types");
5
+ const core_1 = require("strtok3/lib/core");
6
+ const debug_1 = require("debug");
7
+ const common = require("../common/Util");
8
+ const AbstractID3Parser_1 = require("../id3v2/AbstractID3Parser");
9
+ const XingTag_1 = require("./XingTag");
10
+ const debug = (0, debug_1.default)('music-metadata:parser:mpeg');
11
+ /**
12
+ * Cache buffer size used for searching synchronization preabmle
13
+ */
14
+ const maxPeekLen = 1024;
15
+ /**
16
+ * MPEG-4 Audio definitions
17
+ * Ref: https://wiki.multimedia.cx/index.php/MPEG-4_Audio
18
+ */
19
+ const MPEG4 = {
20
+ /**
21
+ * Audio Object Types
22
+ */
23
+ AudioObjectTypes: [
24
+ 'AAC Main',
25
+ 'AAC LC',
26
+ 'AAC SSR',
27
+ 'AAC LTP' // Long Term Prediction
28
+ ],
29
+ /**
30
+ * Sampling Frequencies
31
+ * https://wiki.multimedia.cx/index.php/MPEG-4_Audio#Sampling_Frequencies
32
+ */
33
+ SamplingFrequencies: [
34
+ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, undefined, undefined, -1
35
+ ]
36
+ /**
37
+ * Channel Configurations
38
+ */
39
+ };
40
+ const MPEG4_ChannelConfigurations = [
41
+ undefined,
42
+ ['front-center'],
43
+ ['front-left', 'front-right'],
44
+ ['front-center', 'front-left', 'front-right'],
45
+ ['front-center', 'front-left', 'front-right', 'back-center'],
46
+ ['front-center', 'front-left', 'front-right', 'back-left', 'back-right'],
47
+ ['front-center', 'front-left', 'front-right', 'back-left', 'back-right', 'LFE-channel'],
48
+ ['front-center', 'front-left', 'front-right', 'side-left', 'side-right', 'back-left', 'back-right', 'LFE-channel']
49
+ ];
50
+ /**
51
+ * MPEG Audio Layer I/II/III frame header
52
+ * Ref: https://www.mp3-tech.org/programmer/frame_header.html
53
+ * Bit layout: AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
54
+ * Ref: https://wiki.multimedia.cx/index.php/ADTS
55
+ */
56
+ class MpegFrameHeader {
57
+ constructor(buf, off) {
58
+ // B(20,19): MPEG Audio versionIndex ID
59
+ this.versionIndex = common.getBitAllignedNumber(buf, off + 1, 3, 2);
60
+ // C(18,17): Layer description
61
+ this.layer = MpegFrameHeader.LayerDescription[common.getBitAllignedNumber(buf, off + 1, 5, 2)];
62
+ if (this.versionIndex > 1 && this.layer === 0) {
63
+ this.parseAdtsHeader(buf, off); // Audio Data Transport Stream (ADTS)
64
+ }
65
+ else {
66
+ this.parseMpegHeader(buf, off); // Conventional MPEG header
67
+ }
68
+ // D(16): Protection bit (if true 16-bit CRC follows header)
69
+ this.isProtectedByCRC = !common.isBitSet(buf, off + 1, 7);
70
+ }
71
+ calcDuration(numFrames) {
72
+ return numFrames * this.calcSamplesPerFrame() / this.samplingRate;
73
+ }
74
+ calcSamplesPerFrame() {
75
+ return MpegFrameHeader.samplesInFrameTable[this.version === 1 ? 0 : 1][this.layer];
76
+ }
77
+ calculateSideInfoLength() {
78
+ if (this.layer !== 3)
79
+ return 2;
80
+ if (this.channelModeIndex === 3) {
81
+ // mono
82
+ if (this.version === 1) {
83
+ return 17;
84
+ }
85
+ else if (this.version === 2 || this.version === 2.5) {
86
+ return 9;
87
+ }
88
+ }
89
+ else {
90
+ if (this.version === 1) {
91
+ return 32;
92
+ }
93
+ else if (this.version === 2 || this.version === 2.5) {
94
+ return 17;
95
+ }
96
+ }
97
+ }
98
+ calcSlotSize() {
99
+ return [null, 4, 1, 1][this.layer];
100
+ }
101
+ parseMpegHeader(buf, off) {
102
+ this.container = 'MPEG';
103
+ // E(15,12): Bitrate index
104
+ this.bitrateIndex = common.getBitAllignedNumber(buf, off + 2, 0, 4);
105
+ // F(11,10): Sampling rate frequency index
106
+ this.sampRateFreqIndex = common.getBitAllignedNumber(buf, off + 2, 4, 2);
107
+ // G(9): Padding bit
108
+ this.padding = common.isBitSet(buf, off + 2, 6);
109
+ // H(8): Private bit
110
+ this.privateBit = common.isBitSet(buf, off + 2, 7);
111
+ // I(7,6): Channel Mode
112
+ this.channelModeIndex = common.getBitAllignedNumber(buf, off + 3, 0, 2);
113
+ // J(5,4): Mode extension (Only used in Joint stereo)
114
+ this.modeExtension = common.getBitAllignedNumber(buf, off + 3, 2, 2);
115
+ // K(3): Copyright
116
+ this.isCopyrighted = common.isBitSet(buf, off + 3, 4);
117
+ // L(2): Original
118
+ this.isOriginalMedia = common.isBitSet(buf, off + 3, 5);
119
+ // M(3): The original bit indicates, if it is set, that the frame is located on its original media.
120
+ this.emphasis = common.getBitAllignedNumber(buf, off + 3, 7, 2);
121
+ this.version = MpegFrameHeader.VersionID[this.versionIndex];
122
+ this.channelMode = MpegFrameHeader.ChannelMode[this.channelModeIndex];
123
+ this.codec = `MPEG ${this.version} Layer ${this.layer}`;
124
+ // Calculate bitrate
125
+ const bitrateInKbps = this.calcBitrate();
126
+ if (!bitrateInKbps) {
127
+ throw new Error('Cannot determine bit-rate');
128
+ }
129
+ this.bitrate = bitrateInKbps * 1000;
130
+ // Calculate sampling rate
131
+ this.samplingRate = this.calcSamplingRate();
132
+ if (this.samplingRate == null) {
133
+ throw new Error('Cannot determine sampling-rate');
134
+ }
135
+ }
136
+ parseAdtsHeader(buf, off) {
137
+ debug(`layer=0 => ADTS`);
138
+ this.version = this.versionIndex === 2 ? 4 : 2;
139
+ this.container = 'ADTS/MPEG-' + this.version;
140
+ const profileIndex = common.getBitAllignedNumber(buf, off + 2, 0, 2);
141
+ this.codec = 'AAC';
142
+ this.codecProfile = MPEG4.AudioObjectTypes[profileIndex];
143
+ debug(`MPEG-4 audio-codec=${this.codec}`);
144
+ const samplingFrequencyIndex = common.getBitAllignedNumber(buf, off + 2, 2, 4);
145
+ this.samplingRate = MPEG4.SamplingFrequencies[samplingFrequencyIndex];
146
+ debug(`sampling-rate=${this.samplingRate}`);
147
+ const channelIndex = common.getBitAllignedNumber(buf, off + 2, 7, 3);
148
+ this.mp4ChannelConfig = MPEG4_ChannelConfigurations[channelIndex];
149
+ debug(`channel-config=${this.mp4ChannelConfig.join('+')}`);
150
+ this.frameLength = common.getBitAllignedNumber(buf, off + 3, 6, 2) << 11;
151
+ }
152
+ calcBitrate() {
153
+ if (this.bitrateIndex === 0x00 || // free
154
+ this.bitrateIndex === 0x0F) { // reserved
155
+ return;
156
+ }
157
+ const codecIndex = `${Math.floor(this.version)}${this.layer}`;
158
+ return MpegFrameHeader.bitrate_index[this.bitrateIndex][codecIndex];
159
+ }
160
+ calcSamplingRate() {
161
+ if (this.sampRateFreqIndex === 0x03)
162
+ return null; // 'reserved'
163
+ return MpegFrameHeader.sampling_rate_freq_index[this.version][this.sampRateFreqIndex];
164
+ }
165
+ }
166
+ MpegFrameHeader.SyncByte1 = 0xFF;
167
+ MpegFrameHeader.SyncByte2 = 0xE0;
168
+ MpegFrameHeader.VersionID = [2.5, null, 2, 1];
169
+ MpegFrameHeader.LayerDescription = [0, 3, 2, 1];
170
+ MpegFrameHeader.ChannelMode = ['stereo', 'joint_stereo', 'dual_channel', 'mono'];
171
+ MpegFrameHeader.bitrate_index = {
172
+ 0x01: { 11: 32, 12: 32, 13: 32, 21: 32, 22: 8, 23: 8 },
173
+ 0x02: { 11: 64, 12: 48, 13: 40, 21: 48, 22: 16, 23: 16 },
174
+ 0x03: { 11: 96, 12: 56, 13: 48, 21: 56, 22: 24, 23: 24 },
175
+ 0x04: { 11: 128, 12: 64, 13: 56, 21: 64, 22: 32, 23: 32 },
176
+ 0x05: { 11: 160, 12: 80, 13: 64, 21: 80, 22: 40, 23: 40 },
177
+ 0x06: { 11: 192, 12: 96, 13: 80, 21: 96, 22: 48, 23: 48 },
178
+ 0x07: { 11: 224, 12: 112, 13: 96, 21: 112, 22: 56, 23: 56 },
179
+ 0x08: { 11: 256, 12: 128, 13: 112, 21: 128, 22: 64, 23: 64 },
180
+ 0x09: { 11: 288, 12: 160, 13: 128, 21: 144, 22: 80, 23: 80 },
181
+ 0x0A: { 11: 320, 12: 192, 13: 160, 21: 160, 22: 96, 23: 96 },
182
+ 0x0B: { 11: 352, 12: 224, 13: 192, 21: 176, 22: 112, 23: 112 },
183
+ 0x0C: { 11: 384, 12: 256, 13: 224, 21: 192, 22: 128, 23: 128 },
184
+ 0x0D: { 11: 416, 12: 320, 13: 256, 21: 224, 22: 144, 23: 144 },
185
+ 0x0E: { 11: 448, 12: 384, 13: 320, 21: 256, 22: 160, 23: 160 }
186
+ };
187
+ MpegFrameHeader.sampling_rate_freq_index = {
188
+ 1: { 0x00: 44100, 0x01: 48000, 0x02: 32000 },
189
+ 2: { 0x00: 22050, 0x01: 24000, 0x02: 16000 },
190
+ 2.5: { 0x00: 11025, 0x01: 12000, 0x02: 8000 }
191
+ };
192
+ MpegFrameHeader.samplesInFrameTable = [
193
+ /* Layer I II III */
194
+ [0, 384, 1152, 1152],
195
+ [0, 384, 1152, 576] // MPEG-2(.5
196
+ ];
197
+ /**
198
+ * MPEG Audio Layer I/II/III
199
+ */
200
+ const FrameHeader = {
201
+ len: 4,
202
+ get: (buf, off) => {
203
+ return new MpegFrameHeader(buf, off);
204
+ }
205
+ };
206
+ function getVbrCodecProfile(vbrScale) {
207
+ return 'V' + Math.floor((100 - vbrScale) / 10);
208
+ }
209
+ class MpegParser extends AbstractID3Parser_1.AbstractID3Parser {
210
+ constructor() {
211
+ super(...arguments);
212
+ this.frameCount = 0;
213
+ this.syncFrameCount = -1;
214
+ this.countSkipFrameData = 0;
215
+ this.totalDataLength = 0;
216
+ this.bitrates = [];
217
+ this.calculateEofDuration = false;
218
+ this.buf_frame_header = Buffer.alloc(4);
219
+ this.syncPeek = {
220
+ buf: Buffer.alloc(maxPeekLen),
221
+ len: 0
222
+ };
223
+ }
224
+ /**
225
+ * Called after ID3 headers have been parsed
226
+ */
227
+ async postId3v2Parse() {
228
+ this.metadata.setFormat('lossless', false);
229
+ try {
230
+ let quit = false;
231
+ while (!quit) {
232
+ await this.sync();
233
+ quit = await this.parseCommonMpegHeader();
234
+ }
235
+ }
236
+ catch (err) {
237
+ if (err instanceof core_1.EndOfStreamError) {
238
+ debug(`End-of-stream`);
239
+ if (this.calculateEofDuration) {
240
+ const numberOfSamples = this.frameCount * this.samplesPerFrame;
241
+ this.metadata.setFormat('numberOfSamples', numberOfSamples);
242
+ const duration = numberOfSamples / this.metadata.format.sampleRate;
243
+ debug(`Calculate duration at EOF: ${duration} sec.`, duration);
244
+ this.metadata.setFormat('duration', duration);
245
+ }
246
+ }
247
+ else {
248
+ throw err;
249
+ }
250
+ }
251
+ }
252
+ /**
253
+ * Called after file has been fully parsed, this allows, if present, to exclude the ID3v1.1 header length
254
+ */
255
+ finalize() {
256
+ const format = this.metadata.format;
257
+ const hasID3v1 = this.metadata.native.hasOwnProperty('ID3v1');
258
+ if (format.duration && this.tokenizer.fileInfo.size) {
259
+ const mpegSize = this.tokenizer.fileInfo.size - this.mpegOffset - (hasID3v1 ? 128 : 0);
260
+ if (format.codecProfile && format.codecProfile[0] === 'V') {
261
+ this.metadata.setFormat('bitrate', mpegSize * 8 / format.duration);
262
+ }
263
+ }
264
+ else if (this.tokenizer.fileInfo.size && format.codecProfile === 'CBR') {
265
+ const mpegSize = this.tokenizer.fileInfo.size - this.mpegOffset - (hasID3v1 ? 128 : 0);
266
+ const numberOfSamples = Math.round(mpegSize / this.frame_size) * this.samplesPerFrame;
267
+ this.metadata.setFormat('numberOfSamples', numberOfSamples);
268
+ const duration = numberOfSamples / format.sampleRate;
269
+ debug("Calculate CBR duration based on file size: %s", duration);
270
+ this.metadata.setFormat('duration', duration);
271
+ }
272
+ }
273
+ async sync() {
274
+ let gotFirstSync = false;
275
+ while (true) {
276
+ let bo = 0;
277
+ this.syncPeek.len = await this.tokenizer.peekBuffer(this.syncPeek.buf, { length: maxPeekLen, mayBeLess: true });
278
+ if (this.syncPeek.len <= 163) {
279
+ throw new core_1.EndOfStreamError();
280
+ }
281
+ while (true) {
282
+ if (gotFirstSync && (this.syncPeek.buf[bo] & 0xE0) === 0xE0) {
283
+ this.buf_frame_header[0] = MpegFrameHeader.SyncByte1;
284
+ this.buf_frame_header[1] = this.syncPeek.buf[bo];
285
+ await this.tokenizer.ignore(bo);
286
+ debug(`Sync at offset=${this.tokenizer.position - 1}, frameCount=${this.frameCount}`);
287
+ if (this.syncFrameCount === this.frameCount) {
288
+ debug(`Re-synced MPEG stream, frameCount=${this.frameCount}`);
289
+ this.frameCount = 0;
290
+ this.frame_size = 0;
291
+ }
292
+ this.syncFrameCount = this.frameCount;
293
+ return; // sync
294
+ }
295
+ else {
296
+ gotFirstSync = false;
297
+ bo = this.syncPeek.buf.indexOf(MpegFrameHeader.SyncByte1, bo);
298
+ if (bo === -1) {
299
+ if (this.syncPeek.len < this.syncPeek.buf.length) {
300
+ throw new core_1.EndOfStreamError();
301
+ }
302
+ await this.tokenizer.ignore(this.syncPeek.len);
303
+ break; // continue with next buffer
304
+ }
305
+ else {
306
+ ++bo;
307
+ gotFirstSync = true;
308
+ }
309
+ }
310
+ }
311
+ }
312
+ }
313
+ /**
314
+ * Combined ADTS & MPEG (MP2 & MP3) header handling
315
+ * @return {Promise<boolean>} true if parser should quit
316
+ */
317
+ async parseCommonMpegHeader() {
318
+ if (this.frameCount === 0) {
319
+ this.mpegOffset = this.tokenizer.position - 1;
320
+ }
321
+ await this.tokenizer.peekBuffer(this.buf_frame_header, { offset: 1, length: 3 });
322
+ let header;
323
+ try {
324
+ header = FrameHeader.get(this.buf_frame_header, 0);
325
+ }
326
+ catch (err) {
327
+ await this.tokenizer.ignore(1);
328
+ this.metadata.addWarning('Parse error: ' + err.message);
329
+ return false; // sync
330
+ }
331
+ await this.tokenizer.ignore(3);
332
+ this.metadata.setFormat('container', header.container);
333
+ this.metadata.setFormat('codec', header.codec);
334
+ this.metadata.setFormat('lossless', false);
335
+ this.metadata.setFormat('sampleRate', header.samplingRate);
336
+ this.frameCount++;
337
+ return header.version >= 2 && header.layer === 0 ? this.parseAdts(header) : this.parseAudioFrameHeader(header);
338
+ }
339
+ /**
340
+ * @return {Promise<boolean>} true if parser should quit
341
+ */
342
+ async parseAudioFrameHeader(header) {
343
+ this.metadata.setFormat('numberOfChannels', header.channelMode === 'mono' ? 1 : 2);
344
+ this.metadata.setFormat('bitrate', header.bitrate);
345
+ if (this.frameCount < 20 * 10000) {
346
+ debug('offset=%s MP%s bitrate=%s sample-rate=%s', this.tokenizer.position - 4, header.layer, header.bitrate, header.samplingRate);
347
+ }
348
+ const slot_size = header.calcSlotSize();
349
+ if (slot_size === null) {
350
+ throw new Error('invalid slot_size');
351
+ }
352
+ const samples_per_frame = header.calcSamplesPerFrame();
353
+ debug(`samples_per_frame=${samples_per_frame}`);
354
+ const bps = samples_per_frame / 8.0;
355
+ const fsize = (bps * header.bitrate / header.samplingRate) +
356
+ ((header.padding) ? slot_size : 0);
357
+ this.frame_size = Math.floor(fsize);
358
+ this.audioFrameHeader = header;
359
+ this.bitrates.push(header.bitrate);
360
+ // xtra header only exists in first frame
361
+ if (this.frameCount === 1) {
362
+ this.offset = FrameHeader.len;
363
+ await this.skipSideInformation();
364
+ return false;
365
+ }
366
+ if (this.frameCount === 3) {
367
+ // the stream is CBR if the first 3 frame bitrates are the same
368
+ if (this.areAllSame(this.bitrates)) {
369
+ // Actual calculation will be done in finalize
370
+ this.samplesPerFrame = samples_per_frame;
371
+ this.metadata.setFormat('codecProfile', 'CBR');
372
+ if (this.tokenizer.fileInfo.size)
373
+ return true; // Will calculate duration based on the file size
374
+ }
375
+ else if (this.metadata.format.duration) {
376
+ return true; // We already got the duration, stop processing MPEG stream any further
377
+ }
378
+ if (!this.options.duration) {
379
+ return true; // Enforce duration not enabled, stop processing entire stream
380
+ }
381
+ }
382
+ // once we know the file is VBR attach listener to end of
383
+ // stream so we can do the duration calculation when we
384
+ // have counted all the frames
385
+ if (this.options.duration && this.frameCount === 4) {
386
+ this.samplesPerFrame = samples_per_frame;
387
+ this.calculateEofDuration = true;
388
+ }
389
+ this.offset = 4;
390
+ if (header.isProtectedByCRC) {
391
+ await this.parseCrc();
392
+ return false;
393
+ }
394
+ else {
395
+ await this.skipSideInformation();
396
+ return false;
397
+ }
398
+ }
399
+ async parseAdts(header) {
400
+ const buf = Buffer.alloc(3);
401
+ await this.tokenizer.readBuffer(buf);
402
+ header.frameLength += common.getBitAllignedNumber(buf, 0, 0, 11);
403
+ this.totalDataLength += header.frameLength;
404
+ this.samplesPerFrame = 1024;
405
+ const framesPerSec = header.samplingRate / this.samplesPerFrame;
406
+ const bytesPerFrame = this.frameCount === 0 ? 0 : this.totalDataLength / this.frameCount;
407
+ const bitrate = 8 * bytesPerFrame * framesPerSec + 0.5;
408
+ this.metadata.setFormat('bitrate', bitrate);
409
+ debug(`frame-count=${this.frameCount}, size=${header.frameLength} bytes, bit-rate=${bitrate}`);
410
+ await this.tokenizer.ignore(header.frameLength > 7 ? header.frameLength - 7 : 1);
411
+ // Consume remaining header and frame data
412
+ if (this.frameCount === 3) {
413
+ this.metadata.setFormat('codecProfile', header.codecProfile);
414
+ if (header.mp4ChannelConfig) {
415
+ this.metadata.setFormat('numberOfChannels', header.mp4ChannelConfig.length);
416
+ }
417
+ if (this.options.duration) {
418
+ this.calculateEofDuration = true;
419
+ }
420
+ else {
421
+ return true; // Stop parsing after the third frame
422
+ }
423
+ }
424
+ return false;
425
+ }
426
+ async parseCrc() {
427
+ this.crc = await this.tokenizer.readNumber(Token.INT16_BE);
428
+ this.offset += 2;
429
+ return this.skipSideInformation();
430
+ }
431
+ async skipSideInformation() {
432
+ const sideinfo_length = this.audioFrameHeader.calculateSideInfoLength();
433
+ // side information
434
+ await this.tokenizer.readToken(new Token.Uint8ArrayType(sideinfo_length));
435
+ this.offset += sideinfo_length;
436
+ await this.readXtraInfoHeader();
437
+ return;
438
+ }
439
+ async readXtraInfoHeader() {
440
+ const headerTag = await this.tokenizer.readToken(XingTag_1.InfoTagHeaderTag);
441
+ this.offset += XingTag_1.InfoTagHeaderTag.len; // 12
442
+ switch (headerTag) {
443
+ case 'Info':
444
+ this.metadata.setFormat('codecProfile', 'CBR');
445
+ return this.readXingInfoHeader();
446
+ case 'Xing':
447
+ const infoTag = await this.readXingInfoHeader();
448
+ const codecProfile = getVbrCodecProfile(infoTag.vbrScale);
449
+ this.metadata.setFormat('codecProfile', codecProfile);
450
+ return null;
451
+ case 'Xtra':
452
+ // ToDo: ???
453
+ break;
454
+ case 'LAME':
455
+ const version = await this.tokenizer.readToken(XingTag_1.LameEncoderVersion);
456
+ if (this.frame_size >= this.offset + XingTag_1.LameEncoderVersion.len) {
457
+ this.offset += XingTag_1.LameEncoderVersion.len;
458
+ this.metadata.setFormat('tool', 'LAME ' + version);
459
+ await this.skipFrameData(this.frame_size - this.offset);
460
+ return null;
461
+ }
462
+ else {
463
+ this.metadata.addWarning('Corrupt LAME header');
464
+ break;
465
+ }
466
+ // ToDo: ???
467
+ }
468
+ // ToDo: promise duration???
469
+ const frameDataLeft = this.frame_size - this.offset;
470
+ if (frameDataLeft < 0) {
471
+ this.metadata.addWarning('Frame ' + this.frameCount + 'corrupt: negative frameDataLeft');
472
+ }
473
+ else {
474
+ await this.skipFrameData(frameDataLeft);
475
+ }
476
+ return null;
477
+ }
478
+ /**
479
+ * Ref: http://gabriel.mp3-tech.org/mp3infotag.html
480
+ * @returns {Promise<string>}
481
+ */
482
+ async readXingInfoHeader() {
483
+ const offset = this.tokenizer.position;
484
+ const infoTag = await (0, XingTag_1.readXingHeader)(this.tokenizer);
485
+ this.offset += this.tokenizer.position - offset;
486
+ if (infoTag.lame) {
487
+ this.metadata.setFormat('tool', 'LAME ' + common.stripNulls(infoTag.lame.version));
488
+ if (infoTag.lame.extended) {
489
+ // this.metadata.setFormat('trackGain', infoTag.lame.extended.track_gain);
490
+ this.metadata.setFormat('trackPeakLevel', infoTag.lame.extended.track_peak);
491
+ if (infoTag.lame.extended.track_gain) {
492
+ this.metadata.setFormat('trackGain', infoTag.lame.extended.track_gain.adjustment);
493
+ }
494
+ if (infoTag.lame.extended.album_gain) {
495
+ this.metadata.setFormat('albumGain', infoTag.lame.extended.album_gain.adjustment);
496
+ }
497
+ this.metadata.setFormat('duration', infoTag.lame.extended.music_length / 1000);
498
+ }
499
+ }
500
+ if (infoTag.streamSize) {
501
+ const duration = this.audioFrameHeader.calcDuration(infoTag.numFrames);
502
+ this.metadata.setFormat('duration', duration);
503
+ debug('Get duration from Xing header: %s', this.metadata.format.duration);
504
+ return infoTag;
505
+ }
506
+ // frames field is not present
507
+ const frameDataLeft = this.frame_size - this.offset;
508
+ await this.skipFrameData(frameDataLeft);
509
+ return infoTag;
510
+ }
511
+ async skipFrameData(frameDataLeft) {
512
+ if (frameDataLeft < 0)
513
+ throw new Error('frame-data-left cannot be negative');
514
+ await this.tokenizer.ignore(frameDataLeft);
515
+ this.countSkipFrameData += frameDataLeft;
516
+ }
517
+ areAllSame(array) {
518
+ const first = array[0];
519
+ return array.every(element => {
520
+ return element === first;
521
+ });
522
+ }
523
+ }
524
+ exports.MpegParser = MpegParser;