music-metadata 7.11.6 → 7.11.7

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 (152) hide show
  1. package/README.md +432 -432
  2. package/lib/ParserFactory.d.ts +49 -49
  3. package/lib/ParserFactory.js +251 -251
  4. package/lib/aiff/AiffParser.d.ts +15 -15
  5. package/lib/aiff/AiffParser.js +85 -85
  6. package/lib/aiff/AiffToken.d.ts +22 -22
  7. package/lib/aiff/AiffToken.js +43 -43
  8. package/lib/apev2/APEv2Parser.d.ts +30 -30
  9. package/lib/apev2/APEv2Parser.js +162 -162
  10. package/lib/apev2/APEv2TagMapper.d.ts +4 -4
  11. package/lib/apev2/APEv2TagMapper.js +86 -86
  12. package/lib/apev2/APEv2Token.d.ts +100 -100
  13. package/lib/apev2/APEv2Token.js +126 -126
  14. package/lib/asf/AsfObject.d.ts +319 -319
  15. package/lib/asf/AsfObject.js +384 -384
  16. package/lib/asf/AsfParser.d.ts +17 -17
  17. package/lib/asf/AsfParser.js +135 -135
  18. package/lib/asf/AsfTagMapper.d.ts +7 -7
  19. package/lib/asf/AsfTagMapper.js +95 -95
  20. package/lib/asf/AsfUtil.d.ts +13 -13
  21. package/lib/asf/AsfUtil.js +40 -40
  22. package/lib/asf/GUID.d.ts +86 -86
  23. package/lib/asf/GUID.js +123 -123
  24. package/lib/common/BasicParser.d.ts +17 -17
  25. package/lib/common/BasicParser.js +18 -18
  26. package/lib/common/CaseInsensitiveTagMap.d.ts +10 -10
  27. package/lib/common/CaseInsensitiveTagMap.js +21 -21
  28. package/lib/common/CombinedTagMapper.d.ts +19 -19
  29. package/lib/common/CombinedTagMapper.js +51 -51
  30. package/lib/common/FourCC.d.ts +6 -6
  31. package/lib/common/FourCC.js +28 -28
  32. package/lib/common/GenericTagMapper.d.ts +51 -51
  33. package/lib/common/GenericTagMapper.js +55 -55
  34. package/lib/common/GenericTagTypes.d.ts +33 -33
  35. package/lib/common/GenericTagTypes.js +131 -131
  36. package/lib/common/MetadataCollector.d.ts +76 -76
  37. package/lib/common/MetadataCollector.js +275 -275
  38. package/lib/common/RandomFileReader.d.ts +20 -20
  39. package/lib/common/RandomFileReader.js +37 -37
  40. package/lib/common/RandomUint8ArrayReader.d.ts +18 -18
  41. package/lib/common/RandomUint8ArrayReader.js +25 -25
  42. package/lib/common/Util.d.ts +58 -57
  43. package/lib/common/Util.js +162 -169
  44. package/lib/core.d.ts +48 -48
  45. package/lib/core.js +90 -90
  46. package/lib/dsdiff/DsdiffParser.d.ts +14 -14
  47. package/lib/dsdiff/DsdiffParser.js +143 -143
  48. package/lib/dsdiff/DsdiffToken.d.ts +9 -9
  49. package/lib/dsdiff/DsdiffToken.js +21 -21
  50. package/lib/dsf/DsfChunk.d.ts +86 -86
  51. package/lib/dsf/DsfChunk.js +54 -54
  52. package/lib/dsf/DsfParser.d.ts +9 -9
  53. package/lib/dsf/DsfParser.js +56 -56
  54. package/lib/flac/FlacParser.d.ts +28 -28
  55. package/lib/flac/FlacParser.js +175 -175
  56. package/lib/id3v1/ID3v1Parser.d.ts +13 -13
  57. package/lib/id3v1/ID3v1Parser.js +134 -134
  58. package/lib/id3v1/ID3v1TagMap.d.ts +4 -4
  59. package/lib/id3v1/ID3v1TagMap.js +22 -22
  60. package/lib/id3v2/AbstractID3Parser.d.ts +17 -17
  61. package/lib/id3v2/AbstractID3Parser.js +60 -60
  62. package/lib/id3v2/FrameParser.d.ts +32 -32
  63. package/lib/id3v2/FrameParser.js +329 -323
  64. package/lib/id3v2/ID3v22TagMapper.d.ts +9 -9
  65. package/lib/id3v2/ID3v22TagMapper.js +55 -55
  66. package/lib/id3v2/ID3v24TagMapper.d.ts +14 -14
  67. package/lib/id3v2/ID3v24TagMapper.js +193 -193
  68. package/lib/id3v2/ID3v2Parser.d.ts +29 -29
  69. package/lib/id3v2/ID3v2Parser.js +194 -194
  70. package/lib/id3v2/ID3v2Token.d.ts +73 -73
  71. package/lib/id3v2/ID3v2Token.js +106 -106
  72. package/lib/iff/index.d.ts +33 -33
  73. package/lib/iff/index.js +19 -19
  74. package/lib/index.d.ts +45 -45
  75. package/lib/index.js +74 -74
  76. package/lib/lyrics3/Lyrics3.d.ts +3 -3
  77. package/lib/lyrics3/Lyrics3.js +17 -17
  78. package/lib/matroska/MatroskaDtd.d.ts +8 -8
  79. package/lib/matroska/MatroskaDtd.js +279 -279
  80. package/lib/matroska/MatroskaParser.d.ts +37 -37
  81. package/lib/matroska/MatroskaParser.js +235 -235
  82. package/lib/matroska/MatroskaTagMapper.d.ts +4 -4
  83. package/lib/matroska/MatroskaTagMapper.js +35 -35
  84. package/lib/matroska/types.d.ts +175 -175
  85. package/lib/matroska/types.js +32 -32
  86. package/lib/mp4/Atom.d.ts +16 -16
  87. package/lib/mp4/Atom.js +70 -70
  88. package/lib/mp4/AtomToken.d.ts +395 -395
  89. package/lib/mp4/AtomToken.js +406 -406
  90. package/lib/mp4/MP4Parser.d.ts +30 -30
  91. package/lib/mp4/MP4Parser.js +511 -511
  92. package/lib/mp4/MP4TagMapper.d.ts +5 -5
  93. package/lib/mp4/MP4TagMapper.js +115 -115
  94. package/lib/mpeg/ExtendedLameHeader.d.ts +27 -27
  95. package/lib/mpeg/ExtendedLameHeader.js +31 -31
  96. package/lib/mpeg/MpegParser.d.ts +49 -49
  97. package/lib/mpeg/MpegParser.js +529 -529
  98. package/lib/mpeg/ReplayGainDataFormat.d.ts +55 -55
  99. package/lib/mpeg/ReplayGainDataFormat.js +69 -69
  100. package/lib/mpeg/XingTag.d.ts +45 -45
  101. package/lib/mpeg/XingTag.js +69 -69
  102. package/lib/musepack/index.d.ts +5 -5
  103. package/lib/musepack/index.js +32 -32
  104. package/lib/musepack/sv7/BitReader.d.ts +13 -13
  105. package/lib/musepack/sv7/BitReader.js +54 -54
  106. package/lib/musepack/sv7/MpcSv7Parser.d.ts +8 -8
  107. package/lib/musepack/sv7/MpcSv7Parser.js +46 -46
  108. package/lib/musepack/sv7/StreamVersion7.d.ts +28 -28
  109. package/lib/musepack/sv7/StreamVersion7.js +41 -41
  110. package/lib/musepack/sv8/MpcSv8Parser.d.ts +6 -6
  111. package/lib/musepack/sv8/MpcSv8Parser.js +55 -55
  112. package/lib/musepack/sv8/StreamVersion8.d.ts +40 -40
  113. package/lib/musepack/sv8/StreamVersion8.js +80 -80
  114. package/lib/ogg/Ogg.d.ts +72 -72
  115. package/lib/ogg/Ogg.js +2 -2
  116. package/lib/ogg/OggParser.d.ts +23 -23
  117. package/lib/ogg/OggParser.js +126 -126
  118. package/lib/ogg/opus/Opus.d.ts +48 -48
  119. package/lib/ogg/opus/Opus.js +28 -28
  120. package/lib/ogg/opus/OpusParser.d.ts +25 -25
  121. package/lib/ogg/opus/OpusParser.js +56 -56
  122. package/lib/ogg/speex/Speex.d.ts +36 -36
  123. package/lib/ogg/speex/Speex.js +31 -31
  124. package/lib/ogg/speex/SpeexParser.d.ts +22 -22
  125. package/lib/ogg/speex/SpeexParser.js +35 -35
  126. package/lib/ogg/theora/Theora.d.ts +20 -20
  127. package/lib/ogg/theora/Theora.js +23 -23
  128. package/lib/ogg/theora/TheoraParser.d.ts +28 -28
  129. package/lib/ogg/theora/TheoraParser.js +44 -44
  130. package/lib/ogg/vorbis/Vorbis.d.ts +79 -79
  131. package/lib/ogg/vorbis/Vorbis.js +78 -78
  132. package/lib/ogg/vorbis/VorbisDecoder.d.ts +12 -12
  133. package/lib/ogg/vorbis/VorbisDecoder.js +32 -32
  134. package/lib/ogg/vorbis/VorbisParser.d.ts +36 -36
  135. package/lib/ogg/vorbis/VorbisParser.js +128 -128
  136. package/lib/ogg/vorbis/VorbisTagMapper.d.ts +7 -7
  137. package/lib/ogg/vorbis/VorbisTagMapper.js +132 -132
  138. package/lib/riff/RiffChunk.d.ts +16 -16
  139. package/lib/riff/RiffChunk.js +32 -32
  140. package/lib/riff/RiffInfoTagMap.d.ts +10 -10
  141. package/lib/riff/RiffInfoTagMap.js +37 -37
  142. package/lib/type.d.ts +599 -599
  143. package/lib/type.js +13 -13
  144. package/lib/wav/WaveChunk.d.ts +64 -64
  145. package/lib/wav/WaveChunk.js +65 -65
  146. package/lib/wav/WaveParser.d.ts +24 -24
  147. package/lib/wav/WaveParser.js +144 -144
  148. package/lib/wavpack/WavPackParser.d.ts +14 -14
  149. package/lib/wavpack/WavPackParser.js +105 -105
  150. package/lib/wavpack/WavPackToken.d.ts +64 -64
  151. package/lib/wavpack/WavPackToken.js +76 -76
  152. package/package.json +142 -142
@@ -1,529 +1,529 @@
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 initDebug = require("debug");
7
- const common = require("../common/Util");
8
- const AbstractID3Parser_1 = require("../id3v2/AbstractID3Parser");
9
- const XingTag_1 = require("./XingTag");
10
- const debug = initDebug('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 _parse() {
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
- if (header.version >= 2 && header.layer === 0) {
338
- return this.parseAdts(header); // ADTS, usually AAC
339
- }
340
- else {
341
- return this.parseAudioFrameHeader(header); // MP3
342
- }
343
- }
344
- /**
345
- * @return {Promise<boolean>} true if parser should quit
346
- */
347
- async parseAudioFrameHeader(header) {
348
- this.metadata.setFormat('numberOfChannels', header.channelMode === 'mono' ? 1 : 2);
349
- this.metadata.setFormat('bitrate', header.bitrate);
350
- if (this.frameCount < 20 * 10000) {
351
- debug('offset=%s MP%s bitrate=%s sample-rate=%s', this.tokenizer.position - 4, header.layer, header.bitrate, header.samplingRate);
352
- }
353
- const slot_size = header.calcSlotSize();
354
- if (slot_size === null) {
355
- throw new Error('invalid slot_size');
356
- }
357
- const samples_per_frame = header.calcSamplesPerFrame();
358
- debug(`samples_per_frame=${samples_per_frame}`);
359
- const bps = samples_per_frame / 8.0;
360
- const fsize = (bps * header.bitrate / header.samplingRate) +
361
- ((header.padding) ? slot_size : 0);
362
- this.frame_size = Math.floor(fsize);
363
- this.audioFrameHeader = header;
364
- this.bitrates.push(header.bitrate);
365
- // xtra header only exists in first frame
366
- if (this.frameCount === 1) {
367
- this.offset = FrameHeader.len;
368
- await this.skipSideInformation();
369
- return false;
370
- }
371
- if (this.frameCount === 3) {
372
- // the stream is CBR if the first 3 frame bitrates are the same
373
- if (this.areAllSame(this.bitrates)) {
374
- // Actual calculation will be done in finalize
375
- this.samplesPerFrame = samples_per_frame;
376
- this.metadata.setFormat('codecProfile', 'CBR');
377
- if (this.tokenizer.fileInfo.size)
378
- return true; // Will calculate duration based on the file size
379
- }
380
- else if (this.metadata.format.duration) {
381
- return true; // We already got the duration, stop processing MPEG stream any further
382
- }
383
- if (!this.options.duration) {
384
- return true; // Enforce duration not enabled, stop processing entire stream
385
- }
386
- }
387
- // once we know the file is VBR attach listener to end of
388
- // stream so we can do the duration calculation when we
389
- // have counted all the frames
390
- if (this.options.duration && this.frameCount === 4) {
391
- this.samplesPerFrame = samples_per_frame;
392
- this.calculateEofDuration = true;
393
- }
394
- this.offset = 4;
395
- if (header.isProtectedByCRC) {
396
- await this.parseCrc();
397
- return false;
398
- }
399
- else {
400
- await this.skipSideInformation();
401
- return false;
402
- }
403
- }
404
- async parseAdts(header) {
405
- const buf = Buffer.alloc(3);
406
- await this.tokenizer.readBuffer(buf);
407
- header.frameLength += common.getBitAllignedNumber(buf, 0, 0, 11);
408
- this.totalDataLength += header.frameLength;
409
- this.samplesPerFrame = 1024;
410
- const framesPerSec = header.samplingRate / this.samplesPerFrame;
411
- const bytesPerFrame = this.frameCount === 0 ? 0 : this.totalDataLength / this.frameCount;
412
- const bitrate = 8 * bytesPerFrame * framesPerSec + 0.5;
413
- this.metadata.setFormat('bitrate', bitrate);
414
- debug(`frame-count=${this.frameCount}, size=${header.frameLength} bytes, bit-rate=${bitrate}`);
415
- await this.tokenizer.ignore(header.frameLength > 7 ? header.frameLength - 7 : 1);
416
- // Consume remaining header and frame data
417
- if (this.frameCount === 3) {
418
- this.metadata.setFormat('codecProfile', header.codecProfile);
419
- if (header.mp4ChannelConfig) {
420
- this.metadata.setFormat('numberOfChannels', header.mp4ChannelConfig.length);
421
- }
422
- if (this.options.duration) {
423
- this.calculateEofDuration = true;
424
- }
425
- else {
426
- return true; // Stop parsing after the third frame
427
- }
428
- }
429
- return false;
430
- }
431
- async parseCrc() {
432
- this.crc = await this.tokenizer.readNumber(Token.INT16_BE);
433
- this.offset += 2;
434
- return this.skipSideInformation();
435
- }
436
- async skipSideInformation() {
437
- const sideinfo_length = this.audioFrameHeader.calculateSideInfoLength();
438
- // side information
439
- await this.tokenizer.readToken(new Token.Uint8ArrayType(sideinfo_length));
440
- this.offset += sideinfo_length;
441
- await this.readXtraInfoHeader();
442
- return;
443
- }
444
- async readXtraInfoHeader() {
445
- const headerTag = await this.tokenizer.readToken(XingTag_1.InfoTagHeaderTag);
446
- this.offset += XingTag_1.InfoTagHeaderTag.len; // 12
447
- switch (headerTag) {
448
- case 'Info':
449
- this.metadata.setFormat('codecProfile', 'CBR');
450
- return this.readXingInfoHeader();
451
- case 'Xing':
452
- const infoTag = await this.readXingInfoHeader();
453
- const codecProfile = getVbrCodecProfile(infoTag.vbrScale);
454
- this.metadata.setFormat('codecProfile', codecProfile);
455
- return null;
456
- case 'Xtra':
457
- // ToDo: ???
458
- break;
459
- case 'LAME':
460
- const version = await this.tokenizer.readToken(XingTag_1.LameEncoderVersion);
461
- if (this.frame_size >= this.offset + XingTag_1.LameEncoderVersion.len) {
462
- this.offset += XingTag_1.LameEncoderVersion.len;
463
- this.metadata.setFormat('tool', 'LAME ' + version);
464
- await this.skipFrameData(this.frame_size - this.offset);
465
- return null;
466
- }
467
- else {
468
- this.metadata.addWarning('Corrupt LAME header');
469
- break;
470
- }
471
- // ToDo: ???
472
- }
473
- // ToDo: promise duration???
474
- const frameDataLeft = this.frame_size - this.offset;
475
- if (frameDataLeft < 0) {
476
- this.metadata.addWarning('Frame ' + this.frameCount + 'corrupt: negative frameDataLeft');
477
- }
478
- else {
479
- await this.skipFrameData(frameDataLeft);
480
- }
481
- return null;
482
- }
483
- /**
484
- * Ref: http://gabriel.mp3-tech.org/mp3infotag.html
485
- * @returns {Promise<string>}
486
- */
487
- async readXingInfoHeader() {
488
- const _offset = this.tokenizer.position;
489
- const infoTag = await (0, XingTag_1.readXingHeader)(this.tokenizer);
490
- this.offset += this.tokenizer.position - _offset;
491
- if (infoTag.lame) {
492
- this.metadata.setFormat('tool', 'LAME ' + common.stripNulls(infoTag.lame.version));
493
- if (infoTag.lame.extended) {
494
- // this.metadata.setFormat('trackGain', infoTag.lame.extended.track_gain);
495
- this.metadata.setFormat('trackPeakLevel', infoTag.lame.extended.track_peak);
496
- if (infoTag.lame.extended.track_gain) {
497
- this.metadata.setFormat('trackGain', infoTag.lame.extended.track_gain.adjustment);
498
- }
499
- if (infoTag.lame.extended.album_gain) {
500
- this.metadata.setFormat('albumGain', infoTag.lame.extended.album_gain.adjustment);
501
- }
502
- this.metadata.setFormat('duration', infoTag.lame.extended.music_length / 1000);
503
- }
504
- }
505
- if (infoTag.streamSize) {
506
- const duration = this.audioFrameHeader.calcDuration(infoTag.numFrames);
507
- this.metadata.setFormat('duration', duration);
508
- debug('Get duration from Xing header: %s', this.metadata.format.duration);
509
- return infoTag;
510
- }
511
- // frames field is not present
512
- const frameDataLeft = this.frame_size - this.offset;
513
- await this.skipFrameData(frameDataLeft);
514
- return infoTag;
515
- }
516
- async skipFrameData(frameDataLeft) {
517
- if (frameDataLeft < 0)
518
- throw new Error('frame-data-left cannot be negative');
519
- await this.tokenizer.ignore(frameDataLeft);
520
- this.countSkipFrameData += frameDataLeft;
521
- }
522
- areAllSame(array) {
523
- const first = array[0];
524
- return array.every(element => {
525
- return element === first;
526
- });
527
- }
528
- }
529
- 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 initDebug = require("debug");
7
+ const common = require("../common/Util");
8
+ const AbstractID3Parser_1 = require("../id3v2/AbstractID3Parser");
9
+ const XingTag_1 = require("./XingTag");
10
+ const debug = initDebug('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 _parse() {
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
+ if (header.version >= 2 && header.layer === 0) {
338
+ return this.parseAdts(header); // ADTS, usually AAC
339
+ }
340
+ else {
341
+ return this.parseAudioFrameHeader(header); // MP3
342
+ }
343
+ }
344
+ /**
345
+ * @return {Promise<boolean>} true if parser should quit
346
+ */
347
+ async parseAudioFrameHeader(header) {
348
+ this.metadata.setFormat('numberOfChannels', header.channelMode === 'mono' ? 1 : 2);
349
+ this.metadata.setFormat('bitrate', header.bitrate);
350
+ if (this.frameCount < 20 * 10000) {
351
+ debug('offset=%s MP%s bitrate=%s sample-rate=%s', this.tokenizer.position - 4, header.layer, header.bitrate, header.samplingRate);
352
+ }
353
+ const slot_size = header.calcSlotSize();
354
+ if (slot_size === null) {
355
+ throw new Error('invalid slot_size');
356
+ }
357
+ const samples_per_frame = header.calcSamplesPerFrame();
358
+ debug(`samples_per_frame=${samples_per_frame}`);
359
+ const bps = samples_per_frame / 8.0;
360
+ const fsize = (bps * header.bitrate / header.samplingRate) +
361
+ ((header.padding) ? slot_size : 0);
362
+ this.frame_size = Math.floor(fsize);
363
+ this.audioFrameHeader = header;
364
+ this.bitrates.push(header.bitrate);
365
+ // xtra header only exists in first frame
366
+ if (this.frameCount === 1) {
367
+ this.offset = FrameHeader.len;
368
+ await this.skipSideInformation();
369
+ return false;
370
+ }
371
+ if (this.frameCount === 3) {
372
+ // the stream is CBR if the first 3 frame bitrates are the same
373
+ if (this.areAllSame(this.bitrates)) {
374
+ // Actual calculation will be done in finalize
375
+ this.samplesPerFrame = samples_per_frame;
376
+ this.metadata.setFormat('codecProfile', 'CBR');
377
+ if (this.tokenizer.fileInfo.size)
378
+ return true; // Will calculate duration based on the file size
379
+ }
380
+ else if (this.metadata.format.duration) {
381
+ return true; // We already got the duration, stop processing MPEG stream any further
382
+ }
383
+ if (!this.options.duration) {
384
+ return true; // Enforce duration not enabled, stop processing entire stream
385
+ }
386
+ }
387
+ // once we know the file is VBR attach listener to end of
388
+ // stream so we can do the duration calculation when we
389
+ // have counted all the frames
390
+ if (this.options.duration && this.frameCount === 4) {
391
+ this.samplesPerFrame = samples_per_frame;
392
+ this.calculateEofDuration = true;
393
+ }
394
+ this.offset = 4;
395
+ if (header.isProtectedByCRC) {
396
+ await this.parseCrc();
397
+ return false;
398
+ }
399
+ else {
400
+ await this.skipSideInformation();
401
+ return false;
402
+ }
403
+ }
404
+ async parseAdts(header) {
405
+ const buf = Buffer.alloc(3);
406
+ await this.tokenizer.readBuffer(buf);
407
+ header.frameLength += common.getBitAllignedNumber(buf, 0, 0, 11);
408
+ this.totalDataLength += header.frameLength;
409
+ this.samplesPerFrame = 1024;
410
+ const framesPerSec = header.samplingRate / this.samplesPerFrame;
411
+ const bytesPerFrame = this.frameCount === 0 ? 0 : this.totalDataLength / this.frameCount;
412
+ const bitrate = 8 * bytesPerFrame * framesPerSec + 0.5;
413
+ this.metadata.setFormat('bitrate', bitrate);
414
+ debug(`frame-count=${this.frameCount}, size=${header.frameLength} bytes, bit-rate=${bitrate}`);
415
+ await this.tokenizer.ignore(header.frameLength > 7 ? header.frameLength - 7 : 1);
416
+ // Consume remaining header and frame data
417
+ if (this.frameCount === 3) {
418
+ this.metadata.setFormat('codecProfile', header.codecProfile);
419
+ if (header.mp4ChannelConfig) {
420
+ this.metadata.setFormat('numberOfChannels', header.mp4ChannelConfig.length);
421
+ }
422
+ if (this.options.duration) {
423
+ this.calculateEofDuration = true;
424
+ }
425
+ else {
426
+ return true; // Stop parsing after the third frame
427
+ }
428
+ }
429
+ return false;
430
+ }
431
+ async parseCrc() {
432
+ this.crc = await this.tokenizer.readNumber(Token.INT16_BE);
433
+ this.offset += 2;
434
+ return this.skipSideInformation();
435
+ }
436
+ async skipSideInformation() {
437
+ const sideinfo_length = this.audioFrameHeader.calculateSideInfoLength();
438
+ // side information
439
+ await this.tokenizer.readToken(new Token.Uint8ArrayType(sideinfo_length));
440
+ this.offset += sideinfo_length;
441
+ await this.readXtraInfoHeader();
442
+ return;
443
+ }
444
+ async readXtraInfoHeader() {
445
+ const headerTag = await this.tokenizer.readToken(XingTag_1.InfoTagHeaderTag);
446
+ this.offset += XingTag_1.InfoTagHeaderTag.len; // 12
447
+ switch (headerTag) {
448
+ case 'Info':
449
+ this.metadata.setFormat('codecProfile', 'CBR');
450
+ return this.readXingInfoHeader();
451
+ case 'Xing':
452
+ const infoTag = await this.readXingInfoHeader();
453
+ const codecProfile = getVbrCodecProfile(infoTag.vbrScale);
454
+ this.metadata.setFormat('codecProfile', codecProfile);
455
+ return null;
456
+ case 'Xtra':
457
+ // ToDo: ???
458
+ break;
459
+ case 'LAME':
460
+ const version = await this.tokenizer.readToken(XingTag_1.LameEncoderVersion);
461
+ if (this.frame_size >= this.offset + XingTag_1.LameEncoderVersion.len) {
462
+ this.offset += XingTag_1.LameEncoderVersion.len;
463
+ this.metadata.setFormat('tool', 'LAME ' + version);
464
+ await this.skipFrameData(this.frame_size - this.offset);
465
+ return null;
466
+ }
467
+ else {
468
+ this.metadata.addWarning('Corrupt LAME header');
469
+ break;
470
+ }
471
+ // ToDo: ???
472
+ }
473
+ // ToDo: promise duration???
474
+ const frameDataLeft = this.frame_size - this.offset;
475
+ if (frameDataLeft < 0) {
476
+ this.metadata.addWarning('Frame ' + this.frameCount + 'corrupt: negative frameDataLeft');
477
+ }
478
+ else {
479
+ await this.skipFrameData(frameDataLeft);
480
+ }
481
+ return null;
482
+ }
483
+ /**
484
+ * Ref: http://gabriel.mp3-tech.org/mp3infotag.html
485
+ * @returns {Promise<string>}
486
+ */
487
+ async readXingInfoHeader() {
488
+ const _offset = this.tokenizer.position;
489
+ const infoTag = await (0, XingTag_1.readXingHeader)(this.tokenizer);
490
+ this.offset += this.tokenizer.position - _offset;
491
+ if (infoTag.lame) {
492
+ this.metadata.setFormat('tool', 'LAME ' + common.stripNulls(infoTag.lame.version));
493
+ if (infoTag.lame.extended) {
494
+ // this.metadata.setFormat('trackGain', infoTag.lame.extended.track_gain);
495
+ this.metadata.setFormat('trackPeakLevel', infoTag.lame.extended.track_peak);
496
+ if (infoTag.lame.extended.track_gain) {
497
+ this.metadata.setFormat('trackGain', infoTag.lame.extended.track_gain.adjustment);
498
+ }
499
+ if (infoTag.lame.extended.album_gain) {
500
+ this.metadata.setFormat('albumGain', infoTag.lame.extended.album_gain.adjustment);
501
+ }
502
+ this.metadata.setFormat('duration', infoTag.lame.extended.music_length / 1000);
503
+ }
504
+ }
505
+ if (infoTag.streamSize) {
506
+ const duration = this.audioFrameHeader.calcDuration(infoTag.numFrames);
507
+ this.metadata.setFormat('duration', duration);
508
+ debug('Get duration from Xing header: %s', this.metadata.format.duration);
509
+ return infoTag;
510
+ }
511
+ // frames field is not present
512
+ const frameDataLeft = this.frame_size - this.offset;
513
+ await this.skipFrameData(frameDataLeft);
514
+ return infoTag;
515
+ }
516
+ async skipFrameData(frameDataLeft) {
517
+ if (frameDataLeft < 0)
518
+ throw new Error('frame-data-left cannot be negative');
519
+ await this.tokenizer.ignore(frameDataLeft);
520
+ this.countSkipFrameData += frameDataLeft;
521
+ }
522
+ areAllSame(array) {
523
+ const first = array[0];
524
+ return array.every(element => {
525
+ return element === first;
526
+ });
527
+ }
528
+ }
529
+ exports.MpegParser = MpegParser;