• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Borewit / music-metadata / 15210028329

23 May 2025 12:14PM UTC coverage: 97.12%. Remained the same
15210028329

Pull #2422

github

web-flow
Bump file-type from 20.5.0 to 21.0.0

Bumps [file-type](https://github.com/sindresorhus/file-type) from 20.5.0 to 21.0.0.
- [Release notes](https://github.com/sindresorhus/file-type/releases)
- [Commits](https://github.com/sindresorhus/file-type/compare/v20.5.0...v21.0.0)

---
updated-dependencies:
- dependency-name: file-type
  dependency-version: 21.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #2422: Bump file-type from 20.5.0 to 21.0.0

1348 of 1523 branches covered (88.51%)

Branch coverage included in aggregate %.

11837 of 12053 relevant lines covered (98.21%)

30030.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.02
/lib/wav/WaveParser.ts
1
import * as strtok3 from 'strtok3';
6✔
2
import * as Token from 'token-types';
6✔
3
import initDebug from 'debug';
6✔
4

6✔
5
import * as riff from '../riff/RiffChunk.js';
6✔
6
import * as WaveChunk from './WaveChunk.js';
6✔
7
import { ID3v2Parser } from '../id3v2/ID3v2Parser.js';
6✔
8
import * as util from '../common/Util.js';
6✔
9
import { FourCcToken } from '../common/FourCC.js';
6✔
10
import { BasicParser } from '../common/BasicParser.js';
6✔
11
import { BroadcastAudioExtensionChunk, type IBroadcastAudioExtensionChunk } from './BwfChunk.js';
6✔
12
import type { AnyTagValue } from '../type.js';
6✔
13
import { WaveContentError } from './WaveChunk.js';
6✔
14

6✔
15
const debug = initDebug('music-metadata:parser:RIFF');
6✔
16

6✔
17
/**
6✔
18
 * Resource Interchange File Format (RIFF) Parser
6✔
19
 *
6✔
20
 * WAVE PCM soundfile format
6✔
21
 *
6✔
22
 * Ref:
6✔
23
 * - http://www.johnloomis.org/cpe102/asgn/asgn1/riff.html
6✔
24
 * - http://soundfile.sapp.org/doc/WaveFormat
6✔
25
 *
6✔
26
 * ToDo: Split WAVE part from RIFF parser
6✔
27
 */
6✔
28
export class WaveParser extends BasicParser {
102✔
29

102✔
30
  private fact: WaveChunk.IFactChunk | undefined;
102✔
31
  private blockAlign = 0;
102✔
32
  private header: riff.IChunkHeader | undefined;
102✔
33

102✔
34
  public async parse(): Promise<void> {
102✔
35

102✔
36
    const riffHeader = await this.tokenizer.readToken<riff.IChunkHeader>(riff.Header);
102✔
37
    debug(`pos=${this.tokenizer.position}, parse: chunkID=${riffHeader.chunkID}`);
102✔
38
    if (riffHeader.chunkID !== 'RIFF')
102✔
39
      return; // Not RIFF format
102✔
40
    return this.parseRiffChunk(riffHeader.chunkSize).catch(err => {
90✔
41
      if (!(err instanceof strtok3.EndOfStreamError)) {
12!
42
        throw err;
×
43
      }
×
44
    });
90✔
45
  }
90✔
46

102✔
47
  public async parseRiffChunk(chunkSize: number): Promise<void> {
102✔
48
    const type = await this.tokenizer.readToken<string>(FourCcToken);
90✔
49
    this.metadata.setFormat('container', type);
90✔
50
    switch (type) {
90✔
51
      case 'WAVE':
90✔
52
        return this.readWaveChunk(chunkSize - FourCcToken.len);
90✔
53
      default:
90!
54
        throw new WaveContentError(`Unsupported RIFF format: RIFF/${type}`);
×
55
    }
90✔
56
  }
90✔
57

102✔
58
  public async readWaveChunk(remaining: number): Promise<void> {
102✔
59

90✔
60
    while (remaining >= riff.Header.len) {
90✔
61
      const header = await this.tokenizer.readToken<riff.IChunkHeader>(riff.Header);
432✔
62
      remaining -= riff.Header.len + header.chunkSize;
420✔
63
      if (header.chunkSize > remaining) {
432✔
64
        this.metadata.addWarning('Data chunk size exceeds file size');
138✔
65
      }
138✔
66

420✔
67
      this.header = header;
420✔
68
      debug(`pos=${this.tokenizer.position}, readChunk: chunkID=RIFF/WAVE/${header.chunkID}`);
420✔
69
      switch (header.chunkID) {
420✔
70

420✔
71
        case 'LIST':
432✔
72
          await this.parseListTag(header);
78✔
73
          break;
78✔
74

432✔
75
        case 'fact': // extended Format chunk,
432✔
76
          this.metadata.setFormat('lossless', false);
6✔
77
          this.fact = await this.tokenizer.readToken(new WaveChunk.FactChunk(header));
6✔
78
          break;
6✔
79

432✔
80
        case 'fmt ': { // The Util Chunk, non-PCM Formats
432✔
81
          const fmt = await this.tokenizer.readToken<WaveChunk.IWaveFormat>(new WaveChunk.Format(header));
90✔
82

90✔
83
          let subFormat = WaveChunk.WaveFormatNameMap[fmt.wFormatTag];
90✔
84
          if (!subFormat) {
90!
85
            debug(`WAVE/non-PCM format=${fmt.wFormatTag}`);
×
86
            subFormat = `non-PCM (${fmt.wFormatTag})`;
×
87
          }
×
88
          this.metadata.setFormat('codec', subFormat);
90✔
89
          this.metadata.setFormat('bitsPerSample', fmt.wBitsPerSample);
90✔
90
          this.metadata.setFormat('sampleRate', fmt.nSamplesPerSec);
90✔
91
          this.metadata.setFormat('numberOfChannels', fmt.nChannels);
90✔
92
          this.metadata.setFormat('bitrate', fmt.nBlockAlign * fmt.nSamplesPerSec * 8);
90✔
93
          this.blockAlign = fmt.nBlockAlign;
90✔
94
          break;
90✔
95
        }
90✔
96

432✔
97
        case 'id3 ': // The way Picard, FooBar currently stores, ID3 meta-data
432!
98
        case 'ID3 ': { // The way Mp3Tags stores ID3 meta-data
432✔
99
          const id3_data = await this.tokenizer.readToken<Uint8Array>(new Token.Uint8ArrayType(header.chunkSize));
42✔
100
          const rst = strtok3.fromBuffer(id3_data);
42✔
101
          await new ID3v2Parser().parse(this.metadata, rst, this.options);
42✔
102
          break;
42✔
103
        }
42✔
104

432✔
105
        case 'data': { // PCM-data
432✔
106
          if (this.metadata.format.lossless !== false) {
90✔
107
            this.metadata.setFormat('lossless', true);
84✔
108
          }
84✔
109

90✔
110
          let chunkSize = header.chunkSize;
90✔
111
          if (this.tokenizer.fileInfo.size) {
90✔
112
            const calcRemaining = this.tokenizer.fileInfo.size - this.tokenizer.position;
84✔
113
            if (calcRemaining < chunkSize) {
84✔
114
              this.metadata.addWarning('data chunk length exceeding file length');
18✔
115
              chunkSize = calcRemaining;
18✔
116
            }
18✔
117
          }
84✔
118

90✔
119
          const numberOfSamples = this.fact ? this.fact.dwSampleLength : (chunkSize === 0xffffffff ? undefined : chunkSize / this.blockAlign);
90!
120
          if (numberOfSamples) {
90✔
121
            this.metadata.setFormat('numberOfSamples', numberOfSamples);
84✔
122
            if (this.metadata.format.sampleRate) {
84✔
123
              this.metadata.setFormat('duration', numberOfSamples / this.metadata.format.sampleRate);
84✔
124
            }
84✔
125
          }
84✔
126

90✔
127
          if (this.metadata.format.codec === 'ADPCM') { // ADPCM is 4 bits lossy encoding resulting in 352kbps
90✔
128
            this.metadata.setFormat('bitrate', 352000);
6✔
129
          } else if (this.metadata.format.sampleRate) {
90✔
130
            this.metadata.setFormat('bitrate', this.blockAlign * this.metadata.format.sampleRate * 8);
84✔
131
          }
84✔
132
          await this.tokenizer.ignore(header.chunkSize);
90✔
133
          break;
90✔
134
        }
90✔
135

432✔
136
        case 'bext': { // Broadcast Audio Extension chunk        https://tech.ebu.ch/docs/tech/tech3285.pdf
432✔
137
          const bext = await this.tokenizer.readToken(BroadcastAudioExtensionChunk);
12✔
138
          Object.keys(bext).forEach(key => {
12✔
139
            this.metadata.addTag('exif', `bext.${key}`, bext[key as keyof IBroadcastAudioExtensionChunk]);
156✔
140
          });
12✔
141
          const bextRemaining = header.chunkSize - BroadcastAudioExtensionChunk.len;
12✔
142
          await this.tokenizer.ignore(bextRemaining);
12✔
143
          break;
12✔
144
        }
12✔
145

432✔
146
        case '\x00\x00\x00\x00': // padding ??
432!
147
          debug(`Ignore padding chunk: RIFF/${header.chunkID} of ${header.chunkSize} bytes`);
×
148
          this.metadata.addWarning(`Ignore chunk: RIFF/${header.chunkID}`);
×
149
          await this.tokenizer.ignore(header.chunkSize);
×
150
          break;
×
151

432✔
152
        default:
432✔
153
          debug(`Ignore chunk: RIFF/${header.chunkID} of ${header.chunkSize} bytes`);
102✔
154
          this.metadata.addWarning(`Ignore chunk: RIFF/${header.chunkID}`);
102✔
155
          await this.tokenizer.ignore(header.chunkSize);
102✔
156
      }
102✔
157

420✔
158
      if ((this.header as riff.IChunkHeader).chunkSize % 2 === 1) {
432✔
159
        debug('Read odd padding byte'); // https://wiki.multimedia.cx/index.php/RIFF
36✔
160
        await this.tokenizer.ignore(1);
36✔
161
      }
36✔
162
    }
432✔
163
  }
90✔
164

102✔
165
  public async parseListTag(listHeader: riff.IChunkHeader): Promise<void> {
102✔
166
    const listType = await this.tokenizer.readToken(new Token.StringType(4, 'latin1'));
78✔
167
    debug('pos=%s, parseListTag: chunkID=RIFF/WAVE/LIST/%s', this.tokenizer.position, listType);
78✔
168
    switch (listType) {
78✔
169
      case 'INFO':
78✔
170
        return this.parseRiffInfoTags(listHeader.chunkSize - 4);
72✔
171
      default:
78✔
172
        this.metadata.addWarning(`Ignore chunk: RIFF/WAVE/LIST/${listType}`);
6✔
173
        debug(`Ignoring chunkID=RIFF/WAVE/LIST/${listType}`);
6✔
174
        return this.tokenizer.ignore(listHeader.chunkSize - 4).then();
6✔
175
    }
78✔
176
  }
78✔
177

102✔
178
  private async parseRiffInfoTags(chunkSize: number): Promise<void> {
102✔
179
    while (chunkSize >= 8) {
72✔
180
      const header = await this.tokenizer.readToken<riff.IChunkHeader>(riff.Header);
420✔
181
      const valueToken = new riff.ListInfoTagValue(header);
420✔
182
      const value = await this.tokenizer.readToken(valueToken);
420✔
183
      this.addTag(header.chunkID, util.stripNulls(value));
420✔
184
      chunkSize -= (8 + valueToken.len);
420✔
185
    }
420✔
186

72✔
187
    if (chunkSize !== 0) {
72!
188
      throw new WaveContentError(`Illegal remaining size: ${chunkSize}`);
×
189
    }
×
190
  }
72✔
191

102✔
192
  private addTag(id: string, value: AnyTagValue) {
102✔
193
    this.metadata.addTag('exif', id, value);
420✔
194
  }
420✔
195

102✔
196
}
102✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc