music-metadata 11.3.0 → 11.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE.txt +9 -9
  2. package/README.md +859 -859
  3. package/lib/ParserFactory.d.ts +1 -1
  4. package/lib/ParserFactory.js +10 -1
  5. package/lib/aiff/AiffParser.js +1 -0
  6. package/lib/apev2/APEv2Parser.d.ts +1 -1
  7. package/lib/apev2/APEv2Parser.js +5 -4
  8. package/lib/asf/AsfObject.d.ts +1 -1
  9. package/lib/asf/AsfObject.js +1 -1
  10. package/lib/common/GenericTagMapper.d.ts +1 -1
  11. package/lib/common/GenericTagMapper.js +1 -1
  12. package/lib/common/MetadataCollector.d.ts +2 -0
  13. package/lib/common/MetadataCollector.js +5 -1
  14. package/lib/core.d.ts +2 -2
  15. package/lib/core.js +3 -6
  16. package/lib/dsdiff/DsdiffParser.js +1 -0
  17. package/lib/dsf/DsfParser.js +1 -0
  18. package/lib/ebml/EbmlIterator.js +1 -1
  19. package/lib/flac/FlacParser.js +1 -0
  20. package/lib/mp4/Atom.js +1 -1
  21. package/lib/mp4/AtomToken.d.ts +84 -2
  22. package/lib/mp4/AtomToken.js +141 -9
  23. package/lib/mp4/MP4Parser.d.ts +4 -0
  24. package/lib/mp4/MP4Parser.js +156 -57
  25. package/lib/mp4/MP4TagMapper.d.ts +1 -1
  26. package/lib/mp4/MP4TagMapper.js +1 -1
  27. package/lib/mpeg/MpegParser.js +1 -0
  28. package/lib/musepack/MusepackParser.js +1 -0
  29. package/lib/musepack/sv7/MpcSv7Parser.js +2 -2
  30. package/lib/musepack/sv8/MpcSv8Parser.js +2 -2
  31. package/lib/ogg/opus/OpusParser.d.ts +1 -1
  32. package/lib/ogg/opus/OpusParser.js +2 -1
  33. package/lib/ogg/speex/SpeexParser.d.ts +1 -1
  34. package/lib/ogg/speex/SpeexParser.js +2 -1
  35. package/lib/ogg/theora/TheoraParser.d.ts +3 -5
  36. package/lib/ogg/theora/TheoraParser.js +4 -5
  37. package/lib/ogg/vorbis/VorbisParser.d.ts +1 -1
  38. package/lib/ogg/vorbis/VorbisParser.js +2 -1
  39. package/lib/type.d.ts +9 -1
  40. package/lib/wav/WaveParser.js +1 -0
  41. package/lib/wavpack/WavPackParser.js +3 -2
  42. package/package.json +146 -146
@@ -4,7 +4,7 @@ import { BasicParser } from '../common/BasicParser.js';
4
4
  import { Genres } from '../id3v1/ID3v1Parser.js';
5
5
  import { Atom } from './Atom.js';
6
6
  import * as AtomToken from './AtomToken.js';
7
- import { Mp4ContentError } from './AtomToken.js';
7
+ import { ChapterTrackReferenceBox, Mp4ContentError, } from './AtomToken.js';
8
8
  import { TrackType } from '../type.js';
9
9
  import { uint8ArrayToHex, uint8ArrayToString } from 'uint8array-extras';
10
10
  const debug = initDebug('music-metadata:parser:MP4');
@@ -92,7 +92,9 @@ function distinct(value, index, self) {
92
92
  export class MP4Parser extends BasicParser {
93
93
  constructor() {
94
94
  super(...arguments);
95
- this.tracks = [];
95
+ this.tracks = new Map();
96
+ this.hasVideoTrack = false;
97
+ this.hasAudioTrack = true;
96
98
  this.atomParsers = {
97
99
  /**
98
100
  * Parse movie header (mvhd) atom
@@ -103,19 +105,6 @@ export class MP4Parser extends BasicParser {
103
105
  this.metadata.setFormat('creationTime', mvhd.creationTime);
104
106
  this.metadata.setFormat('modificationTime', mvhd.modificationTime);
105
107
  },
106
- /**
107
- * Parse media header (mdhd) atom
108
- * Ref: https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25615
109
- */
110
- mdhd: async (len) => {
111
- const mdhd_data = await this.tokenizer.readToken(new AtomToken.MdhdAtom(len));
112
- // this.parse_mxhd(mdhd_data, this.currentTrack);
113
- const td = this.getTrackDescription();
114
- td.creationTime = mdhd_data.creationTime;
115
- td.modificationTime = mdhd_data.modificationTime;
116
- td.timeScale = mdhd_data.timeScale;
117
- td.duration = mdhd_data.duration;
118
- },
119
108
  chap: async (len) => {
120
109
  const td = this.getTrackDescription();
121
110
  const trackIds = [];
@@ -125,10 +114,6 @@ export class MP4Parser extends BasicParser {
125
114
  }
126
115
  td.chapterList = trackIds;
127
116
  },
128
- tkhd: async (len) => {
129
- const track = (await this.tokenizer.readToken(new AtomToken.TrackHeaderAtom(len)));
130
- this.tracks.push(track);
131
- },
132
117
  /**
133
118
  * Parse mdat atom.
134
119
  * Will scan for chapters
@@ -137,10 +122,10 @@ export class MP4Parser extends BasicParser {
137
122
  this.audioLengthInBytes = len;
138
123
  this.calculateBitRate();
139
124
  if (this.options.includeChapters) {
140
- const trackWithChapters = this.tracks.filter(track => track.chapterList);
125
+ const trackWithChapters = [...this.tracks.values()].filter(track => track.chapterList);
141
126
  if (trackWithChapters.length === 1) {
142
127
  const chapterTrackIds = trackWithChapters[0].chapterList;
143
- const chapterTracks = this.tracks.filter(track => chapterTrackIds.indexOf(track.trackId) !== -1);
128
+ const chapterTracks = [...this.tracks.values()].filter(track => chapterTrackIds.indexOf(track.header.trackId) !== -1);
144
129
  if (chapterTracks.length === 1) {
145
130
  return this.parseChapterTrack(chapterTracks[0], trackWithChapters[0], len);
146
131
  }
@@ -170,20 +155,6 @@ export class MP4Parser extends BasicParser {
170
155
  const trackDescription = this.getTrackDescription();
171
156
  trackDescription.soundSampleDescription = stsd.table.map(dfEntry => this.parseSoundSampleDescription(dfEntry));
172
157
  },
173
- /**
174
- * sample-to-Chunk Atoms
175
- */
176
- stsc: async (len) => {
177
- const stsc = await this.tokenizer.readToken(new AtomToken.StscAtom(len));
178
- this.getTrackDescription().sampleToChunkTable = stsc.entries;
179
- },
180
- /**
181
- * time-to-sample table
182
- */
183
- stts: async (len) => {
184
- const stts = await this.tokenizer.readToken(new AtomToken.SttsAtom(len));
185
- this.getTrackDescription().timeToSampleTable = stts.entries;
186
- },
187
158
  /**
188
159
  * Parse sample-sizes atom ('stsz')
189
160
  */
@@ -193,13 +164,6 @@ export class MP4Parser extends BasicParser {
193
164
  td.sampleSize = stsz.sampleSize;
194
165
  td.sampleSizeTable = stsz.entries;
195
166
  },
196
- /**
197
- * Parse chunk-offset atom ('stco')
198
- */
199
- stco: async (len) => {
200
- const stco = await this.tokenizer.readToken(new AtomToken.StcoAtom(len));
201
- this.getTrackDescription().chunkOffsetTable = stco.entries; // remember chunk offsets
202
- },
203
167
  date: async (len) => {
204
168
  const date = await this.tokenizer.readToken(new Token.StringType(len, 'utf-8'));
205
169
  await this.addTag('date', date);
@@ -215,7 +179,9 @@ export class MP4Parser extends BasicParser {
215
179
  return Number(token.get(array, 0));
216
180
  }
217
181
  async parse() {
218
- this.tracks = [];
182
+ this.hasVideoTrack = false;
183
+ this.hasAudioTrack = true;
184
+ this.tracks.clear();
219
185
  let remainingFileSize = this.tokenizer.fileInfo.size || 0;
220
186
  while (!this.tokenizer.fileInfo.size || remainingFileSize > 0) {
221
187
  try {
@@ -274,21 +240,39 @@ export class MP4Parser extends BasicParser {
274
240
  if (formatList.length > 0) {
275
241
  this.metadata.setFormat('codec', formatList.filter(distinct).join('+'));
276
242
  }
277
- const audioTracks = this.tracks.filter(track => {
243
+ const audioTracks = [...this.tracks.values()].filter(track => {
278
244
  return track.soundSampleDescription.length >= 1 && track.soundSampleDescription[0].description && track.soundSampleDescription[0].description.numAudioChannels > 0;
279
245
  });
280
246
  if (audioTracks.length >= 1) {
281
247
  const audioTrack = audioTracks[0];
282
- if (audioTrack.timeScale > 0) {
283
- const duration = audioTrack.duration / audioTrack.timeScale; // calculate duration in seconds
284
- this.metadata.setFormat('duration', duration);
248
+ if (audioTrack.media.header && audioTrack.media.header.timeScale > 0) {
249
+ if (audioTrack.media.header.duration > 0) {
250
+ debug('Using duration defined on audio track');
251
+ const duration = audioTrack.media.header.duration / audioTrack.media.header.timeScale; // calculate duration in seconds
252
+ this.metadata.setFormat('duration', duration);
253
+ }
254
+ else if (audioTrack.fragments.length > 0) {
255
+ debug('Calculate duration defined in track fragments');
256
+ let totalTimeUnits = 0;
257
+ for (const fragment of audioTrack.fragments) {
258
+ const defaultDuration = fragment.header.defaultSampleDuration;
259
+ for (const sample of fragment.trackRun.samples) {
260
+ const dur = sample.sampleDuration ?? defaultDuration;
261
+ if (dur == null) {
262
+ throw new Error("Missing sampleDuration and no default_sample_duration in tfhd");
263
+ }
264
+ totalTimeUnits += dur;
265
+ }
266
+ }
267
+ this.metadata.setFormat('duration', totalTimeUnits / audioTrack.media.header.timeScale);
268
+ }
285
269
  }
286
270
  const ssd = audioTrack.soundSampleDescription[0];
287
- if (ssd.description) {
271
+ if (ssd.description && audioTrack.media.header) {
288
272
  this.metadata.setFormat('sampleRate', ssd.description.sampleRate);
289
273
  this.metadata.setFormat('bitsPerSample', ssd.description.sampleSize);
290
274
  this.metadata.setFormat('numberOfChannels', ssd.description.numAudioChannels);
291
- if (audioTrack.timeScale === 0 && audioTrack.timeToSampleTable.length > 0) {
275
+ if (audioTrack.media.header.timeScale === 0 && audioTrack.timeToSampleTable.length > 0) {
292
276
  const totalSampleSize = audioTrack.timeToSampleTable
293
277
  .map(ttstEntry => ttstEntry.count * ttstEntry.duration)
294
278
  .reduce((total, sampleSize) => total + sampleSize);
@@ -302,6 +286,8 @@ export class MP4Parser extends BasicParser {
302
286
  }
303
287
  this.calculateBitRate();
304
288
  }
289
+ this.metadata.setFormat('hasAudio', this.hasAudioTrack);
290
+ this.metadata.setFormat('hasVideo', this.hasVideoTrack);
305
291
  }
306
292
  async handleAtom(atom, remaining) {
307
293
  if (atom.parent) {
@@ -309,6 +295,17 @@ export class MP4Parser extends BasicParser {
309
295
  case 'ilst':
310
296
  case '<id>':
311
297
  return this.parseMetadataItemData(atom);
298
+ case 'moov':
299
+ switch (atom.header.name) {
300
+ case 'trak':
301
+ return this.parseTrackBox(atom);
302
+ }
303
+ break;
304
+ case 'moof':
305
+ switch (atom.header.name) {
306
+ case 'traf':
307
+ return this.parseTrackFragmentBox(atom);
308
+ }
312
309
  }
313
310
  }
314
311
  // const payloadLength = atom.getPayloadLength(remaining);
@@ -319,7 +316,9 @@ export class MP4Parser extends BasicParser {
319
316
  await this.tokenizer.ignore(remaining);
320
317
  }
321
318
  getTrackDescription() {
322
- return this.tracks[this.tracks.length - 1];
319
+ // ToDo: pick the right track, not the last track!!!!
320
+ const tracks = [...this.tracks.values()];
321
+ return tracks[tracks.length - 1];
323
322
  }
324
323
  calculateBitRate() {
325
324
  if (this.audioLengthInBytes && this.metadata.format.duration) {
@@ -432,6 +431,111 @@ export class MP4Parser extends BasicParser {
432
431
  this.addWarning(`atom key=${tagKey}, has unknown well-known-type (data-type): ${dataAtom.type.type}`);
433
432
  }
434
433
  }
434
+ async parseTrackBox(trakBox) {
435
+ // @ts-ignore
436
+ const track = {
437
+ media: {},
438
+ fragments: []
439
+ };
440
+ await trakBox.readAtoms(this.tokenizer, async (child, remaining) => {
441
+ const payLoadLength = child.getPayloadLength(remaining);
442
+ switch (child.header.name) {
443
+ case 'chap': {
444
+ const chap = await this.tokenizer.readToken(new ChapterTrackReferenceBox(remaining));
445
+ track.chapterList = chap;
446
+ break;
447
+ }
448
+ case 'tkhd': // TrackHeaderBox
449
+ track.header = await this.tokenizer.readToken(new AtomToken.TrackHeaderAtom(payLoadLength));
450
+ break;
451
+ case 'hdlr': // TrackHeaderBox
452
+ track.handler = await this.tokenizer.readToken(new AtomToken.HandlerBox(payLoadLength));
453
+ switch (track.handler.handlerType) {
454
+ case 'audi':
455
+ debug('Contains audio track');
456
+ this.hasAudioTrack = true;
457
+ break;
458
+ case 'vide':
459
+ debug('Contains video track');
460
+ this.hasVideoTrack = true;
461
+ break;
462
+ }
463
+ break;
464
+ case 'mdhd': { // Parse media header (mdhd) box
465
+ const mdhd_data = await this.tokenizer.readToken(new AtomToken.MdhdAtom(payLoadLength));
466
+ track.media.header = mdhd_data;
467
+ break;
468
+ }
469
+ case 'stco': {
470
+ const stco = await this.tokenizer.readToken(new AtomToken.StcoAtom(payLoadLength));
471
+ track.chunkOffsetTable = stco.entries; // remember chunk offsets
472
+ break;
473
+ }
474
+ case 'stsc': { // sample-to-Chunk box
475
+ const stsc = await this.tokenizer.readToken(new AtomToken.StscAtom(payLoadLength));
476
+ track.sampleToChunkTable = stsc.entries;
477
+ break;
478
+ }
479
+ case 'stsd': { // sample description box
480
+ const stsd = await this.tokenizer.readToken(new AtomToken.StsdAtom(payLoadLength));
481
+ track.soundSampleDescription = stsd.table.map(dfEntry => this.parseSoundSampleDescription(dfEntry));
482
+ break;
483
+ }
484
+ case 'stts': { // time-to-sample table
485
+ const stts = await this.tokenizer.readToken(new AtomToken.SttsAtom(payLoadLength));
486
+ track.timeToSampleTable = stts.entries;
487
+ break;
488
+ }
489
+ case 'stsz': {
490
+ const stsz = await this.tokenizer.readToken(new AtomToken.StszAtom(payLoadLength));
491
+ track.sampleSize = stsz.sampleSize;
492
+ track.sampleSizeTable = stsz.entries;
493
+ break;
494
+ }
495
+ case 'dinf':
496
+ case 'vmhd':
497
+ case 'smhd':
498
+ debug(`Ignoring: ${child.header.name}`);
499
+ await this.tokenizer.ignore(payLoadLength);
500
+ break;
501
+ default: {
502
+ debug(`Unexpected track box: ${child.header.name}`);
503
+ await this.tokenizer.ignore(payLoadLength);
504
+ }
505
+ }
506
+ }, trakBox.getPayloadLength(0));
507
+ // Register track
508
+ this.tracks.set(track.header.trackId, track);
509
+ }
510
+ parseTrackFragmentBox(trafBox) {
511
+ let tfhd;
512
+ return trafBox.readAtoms(this.tokenizer, async (child, remaining) => {
513
+ const payLoadLength = child.getPayloadLength(remaining);
514
+ switch (child.header.name) {
515
+ case 'tfhd': { // TrackFragmentHeaderBox
516
+ const fragmentHeaderBox = new AtomToken.TrackFragmentHeaderBox(child.getPayloadLength(remaining));
517
+ tfhd = await this.tokenizer.readToken(fragmentHeaderBox);
518
+ break;
519
+ }
520
+ case 'tfdt': // TrackFragmentBaseMediaDecodeTimeBo
521
+ await this.tokenizer.ignore(payLoadLength);
522
+ break;
523
+ case 'trun': { // TrackRunBox
524
+ const trackRunBox = new AtomToken.TrackRunBox(payLoadLength);
525
+ const trun = await this.tokenizer.readToken(trackRunBox);
526
+ if (tfhd) {
527
+ const track = this.tracks.get(tfhd.trackId);
528
+ track?.fragments.push({ header: tfhd, trackRun: trun });
529
+ }
530
+ break;
531
+ }
532
+ default: {
533
+ debug(`Unexpected box: ${child.header.name}`);
534
+ await this.tokenizer.ignore(payLoadLength);
535
+ }
536
+ }
537
+ }, trafBox.getPayloadLength(0));
538
+ }
435
539
  /**
436
540
  * @param sampleDescription
437
541
  * Ref: https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html#//apple_ref/doc/uid/TP40000939-CH205-128916
@@ -476,22 +580,17 @@ export class MP4Parser extends BasicParser {
476
580
  debug(`Chapter ${i + 1}: ${title}`);
477
581
  const chapter = {
478
582
  title,
479
- timeScale: chapterTrack.timeScale,
583
+ timeScale: chapterTrack.media.header ? chapterTrack.media.header.timeScale : 0,
480
584
  start,
481
585
  sampleOffset: this.findSampleOffset(track, this.tokenizer.position)
482
586
  };
483
- debug(`Chapter title=${chapter.title}, offset=${chapter.sampleOffset}/${this.tracks[0].duration}`);
587
+ debug(`Chapter title=${chapter.title}, offset=${chapter.sampleOffset}/${track.header.duration}`); // ToDo, use media duration if required!!!
484
588
  chapters.push(chapter);
485
589
  }
486
590
  this.metadata.setFormat('chapters', chapters);
487
591
  await this.tokenizer.ignore(len);
488
592
  }
489
593
  findSampleOffset(track, chapterOffset) {
490
- let totalDuration = 0;
491
- track.timeToSampleTable.forEach(e => {
492
- totalDuration += e.count * e.duration;
493
- });
494
- debug(`Total duration=${totalDuration}`);
495
594
  let chunkIndex = 0;
496
595
  while (chunkIndex < track.chunkOffsetTable.length && track.chunkOffsetTable[chunkIndex] < chapterOffset) {
497
596
  ++chunkIndex;
@@ -4,5 +4,5 @@ import type { INativeMetadataCollector } from "../common/MetadataCollector.js";
4
4
  export declare const tagType = "iTunes";
5
5
  export declare class MP4TagMapper extends CaseInsensitiveTagMap {
6
6
  constructor();
7
- protected postMap(tag: ITag, warnings: INativeMetadataCollector): void;
7
+ protected postMap(tag: ITag, _warnings: INativeMetadataCollector): void;
8
8
  }
@@ -110,7 +110,7 @@ export class MP4TagMapper extends CaseInsensitiveTagMap {
110
110
  constructor() {
111
111
  super([tagType], mp4TagMap);
112
112
  }
113
- postMap(tag, warnings) {
113
+ postMap(tag, _warnings) {
114
114
  switch (tag.id) {
115
115
  case 'rate':
116
116
  tag.value = {
@@ -256,6 +256,7 @@ export class MpegParser extends AbstractID3Parser {
256
256
  */
257
257
  async postId3v2Parse() {
258
258
  this.metadata.setFormat('lossless', false);
259
+ this.metadata.setAudioOnly();
259
260
  try {
260
261
  let quit = false;
261
262
  while (!quit) {
@@ -24,6 +24,7 @@ export class MusepackParser extends AbstractID3Parser {
24
24
  throw new MusepackContentError('Invalid signature prefix');
25
25
  }
26
26
  }
27
+ this.metadata.setAudioOnly();
27
28
  return mpcParser.parse();
28
29
  }
29
30
  }
@@ -1,6 +1,6 @@
1
1
  import initDebug from 'debug';
2
2
  import { BasicParser } from '../../common/BasicParser.js';
3
- import { APEv2Parser } from '../../apev2/APEv2Parser.js';
3
+ import { tryParseApeHeader } from '../../apev2/APEv2Parser.js';
4
4
  import { BitReader } from './BitReader.js';
5
5
  import * as SV7 from './StreamVersion7.js';
6
6
  import { MusepackContentError } from '../MusepackConentError.js';
@@ -29,7 +29,7 @@ export class MpcSv7Parser extends BasicParser {
29
29
  this.metadata.setFormat('codec', (version / 100).toFixed(2));
30
30
  await this.skipAudioData(header.frameCount);
31
31
  debug(`End of audio stream, switching to APEv2, offset=${this.tokenizer.position}`);
32
- return APEv2Parser.tryParseApeHeader(this.metadata, this.tokenizer, this.options);
32
+ return tryParseApeHeader(this.metadata, this.tokenizer, this.options);
33
33
  }
34
34
  async skipAudioData(frameCount) {
35
35
  while (frameCount-- > 0) {
@@ -1,6 +1,6 @@
1
1
  import initDebug from 'debug';
2
2
  import { BasicParser } from '../../common/BasicParser.js';
3
- import { APEv2Parser } from '../../apev2/APEv2Parser.js';
3
+ import { tryParseApeHeader } from '../../apev2/APEv2Parser.js';
4
4
  import { FourCcToken } from '../../common/FourCC.js';
5
5
  import * as SV8 from './StreamVersion8.js';
6
6
  import { MusepackContentError } from '../MusepackConentError.js';
@@ -46,7 +46,7 @@ export class MpcSv8Parser extends BasicParser {
46
46
  if (this.metadata.format.duration) {
47
47
  this.metadata.setFormat('bitrate', this.audioLength * 8 / this.metadata.format.duration);
48
48
  }
49
- return APEv2Parser.tryParseApeHeader(this.metadata, this.tokenizer, this.options);
49
+ return tryParseApeHeader(this.metadata, this.tokenizer, this.options);
50
50
  default:
51
51
  throw new MusepackContentError(`Unexpected header: ${header.key}`);
52
52
  }
@@ -18,7 +18,7 @@ export declare class OpusParser extends VorbisParser {
18
18
  * @param {IPageHeader} header
19
19
  * @param {Uint8Array} pageData
20
20
  */
21
- protected parseFirstPage(header: IPageHeader, pageData: Uint8Array): void;
21
+ protected parseFirstPage(_header: IPageHeader, pageData: Uint8Array): void;
22
22
  protected parseFullPage(pageData: Uint8Array): Promise<void>;
23
23
  calculateDuration(header: IPageHeader): void;
24
24
  }
@@ -19,7 +19,7 @@ export class OpusParser extends VorbisParser {
19
19
  * @param {IPageHeader} header
20
20
  * @param {Uint8Array} pageData
21
21
  */
22
- parseFirstPage(header, pageData) {
22
+ parseFirstPage(_header, pageData) {
23
23
  this.metadata.setFormat('codec', 'Opus');
24
24
  // Parse Opus ID Header
25
25
  this.idHeader = new Opus.IdHeader(pageData.length).get(pageData, 0);
@@ -27,6 +27,7 @@ export class OpusParser extends VorbisParser {
27
27
  throw new OpusContentError("Illegal ogg/Opus magic-signature");
28
28
  this.metadata.setFormat('sampleRate', this.idHeader.inputSampleRate);
29
29
  this.metadata.setFormat('numberOfChannels', this.idHeader.channelCount);
30
+ this.metadata.setAudioOnly();
30
31
  }
31
32
  async parseFullPage(pageData) {
32
33
  const magicSignature = new Token.StringType(8, 'ascii').get(pageData, 0);
@@ -17,5 +17,5 @@ export declare class SpeexParser extends VorbisParser {
17
17
  * @param {IPageHeader} header
18
18
  * @param {Uint8Array} pageData
19
19
  */
20
- protected parseFirstPage(header: IPageHeader, pageData: Uint8Array): void;
20
+ protected parseFirstPage(_header: IPageHeader, pageData: Uint8Array): void;
21
21
  }
@@ -18,7 +18,7 @@ export class SpeexParser extends VorbisParser {
18
18
  * @param {IPageHeader} header
19
19
  * @param {Uint8Array} pageData
20
20
  */
21
- parseFirstPage(header, pageData) {
21
+ parseFirstPage(_header, pageData) {
22
22
  debug('First Ogg/Speex page');
23
23
  const speexHeader = Speex.Header.get(pageData, 0);
24
24
  this.metadata.setFormat('codec', `Speex ${speexHeader.version}`);
@@ -27,6 +27,7 @@ export class SpeexParser extends VorbisParser {
27
27
  if (speexHeader.bitrate !== -1) {
28
28
  this.metadata.setFormat('bitrate', speexHeader.bitrate);
29
29
  }
30
+ this.metadata.setAudioOnly();
30
31
  }
31
32
  }
32
33
  //# sourceMappingURL=SpeexParser.js.map
@@ -9,7 +9,7 @@ import type { INativeMetadataCollector } from '../../common/MetadataCollector.js
9
9
  export declare class TheoraParser implements Ogg.IPageConsumer {
10
10
  private metadata;
11
11
  private tokenizer;
12
- constructor(metadata: INativeMetadataCollector, options: IOptions, tokenizer: ITokenizer);
12
+ constructor(metadata: INativeMetadataCollector, _options: IOptions, tokenizer: ITokenizer);
13
13
  /**
14
14
  * Vorbis 1 parser
15
15
  * @param header Ogg Page Header
@@ -17,11 +17,9 @@ export declare class TheoraParser implements Ogg.IPageConsumer {
17
17
  */
18
18
  parsePage(header: Ogg.IPageHeader, pageData: Uint8Array): Promise<void>;
19
19
  flush(): Promise<void>;
20
- calculateDuration(header: Ogg.IPageHeader): void;
20
+ calculateDuration(_header: Ogg.IPageHeader): void;
21
21
  /**
22
22
  * Parse first Theora Ogg page. the initial identification header packet
23
- * @param {IPageHeader} header
24
- * @param {Buffer} pageData
25
23
  */
26
- protected parseFirstPage(header: Ogg.IPageHeader, pageData: Uint8Array): Promise<void>;
24
+ protected parseFirstPage(_header: Ogg.IPageHeader, pageData: Uint8Array): Promise<void>;
27
25
  }
@@ -6,7 +6,7 @@ const debug = initDebug('music-metadata:parser:ogg:theora');
6
6
  * - https://theora.org/doc/Theora.pdf
7
7
  */
8
8
  export class TheoraParser {
9
- constructor(metadata, options, tokenizer) {
9
+ constructor(metadata, _options, tokenizer) {
10
10
  this.metadata = metadata;
11
11
  this.tokenizer = tokenizer;
12
12
  }
@@ -23,19 +23,18 @@ export class TheoraParser {
23
23
  async flush() {
24
24
  debug('flush');
25
25
  }
26
- calculateDuration(header) {
26
+ calculateDuration(_header) {
27
27
  debug('duration calculation not implemented');
28
28
  }
29
29
  /**
30
30
  * Parse first Theora Ogg page. the initial identification header packet
31
- * @param {IPageHeader} header
32
- * @param {Buffer} pageData
33
31
  */
34
- async parseFirstPage(header, pageData) {
32
+ async parseFirstPage(_header, pageData) {
35
33
  debug('First Ogg/Theora page');
36
34
  this.metadata.setFormat('codec', 'Theora');
37
35
  const idHeader = IdentificationHeader.get(pageData, 0);
38
36
  this.metadata.setFormat('bitrate', idHeader.nombr);
37
+ this.metadata.setAudioOnly();
39
38
  }
40
39
  }
41
40
  //# sourceMappingURL=TheoraParser.js.map
@@ -41,7 +41,7 @@ export declare class VorbisParser implements IPageConsumer {
41
41
  * @param header
42
42
  * @param pageData
43
43
  */
44
- protected parseFirstPage(header: IPageHeader, pageData: Uint8Array): void;
44
+ protected parseFirstPage(_header: IPageHeader, pageData: Uint8Array): void;
45
45
  protected parseFullPage(pageData: Uint8Array): Promise<void>;
46
46
  /**
47
47
  * Ref: https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-840005.2
@@ -90,8 +90,9 @@ export class VorbisParser {
90
90
  * @param header
91
91
  * @param pageData
92
92
  */
93
- parseFirstPage(header, pageData) {
93
+ parseFirstPage(_header, pageData) {
94
94
  this.metadata.setFormat('codec', 'Vorbis I');
95
+ this.metadata.setAudioOnly();
95
96
  debug('Parse first page');
96
97
  // Parse Vorbis common header
97
98
  const commonHeader = CommonHeader.get(pageData, 0);
package/lib/type.d.ts CHANGED
@@ -370,7 +370,7 @@ export interface IRatio {
370
370
  */
371
371
  dB: number;
372
372
  }
373
- export type FormatId = 'container' | 'duration' | 'bitrate' | 'sampleRate' | 'bitsPerSample' | 'codec' | 'tool' | 'codecProfile' | 'lossless' | 'numberOfChannels' | 'numberOfSamples' | 'audioMD5' | 'chapters' | 'modificationTime' | 'creationTime' | 'trackPeakLevel' | 'trackGain' | 'albumGain';
373
+ export type FormatId = 'container' | 'duration' | 'bitrate' | 'sampleRate' | 'bitsPerSample' | 'codec' | 'tool' | 'codecProfile' | 'lossless' | 'numberOfChannels' | 'numberOfSamples' | 'audioMD5' | 'chapters' | 'modificationTime' | 'creationTime' | 'trackPeakLevel' | 'trackGain' | 'albumGain' | 'hasAudio' | 'hasVideo';
374
374
  export interface IAudioTrack {
375
375
  samplingFrequency?: number;
376
376
  outputSamplingFrequency?: number;
@@ -470,6 +470,14 @@ export interface IFormat {
470
470
  readonly trackGain?: number;
471
471
  readonly trackPeakLevel?: number;
472
472
  readonly albumGain?: number;
473
+ /**
474
+ * Indicates if the audio files contains an audio stream
475
+ */
476
+ hasAudio?: boolean;
477
+ /**
478
+ * Indicates if the media files contains a video stream
479
+ */
480
+ hasVideo?: boolean;
473
481
  }
474
482
  export interface ITag {
475
483
  id: string;
@@ -31,6 +31,7 @@ export class WaveParser extends BasicParser {
31
31
  debug(`pos=${this.tokenizer.position}, parse: chunkID=${riffHeader.chunkID}`);
32
32
  if (riffHeader.chunkID !== 'RIFF')
33
33
  return; // Not RIFF format
34
+ this.metadata.setAudioOnly();
34
35
  return this.parseRiffChunk(riffHeader.chunkSize).catch(err => {
35
36
  if (!(err instanceof strtok3.EndOfStreamError)) {
36
37
  throw err;
@@ -1,5 +1,5 @@
1
1
  import * as Token from 'token-types';
2
- import { APEv2Parser } from '../apev2/APEv2Parser.js';
2
+ import { tryParseApeHeader } from '../apev2/APEv2Parser.js';
3
3
  import { FourCcToken } from '../common/FourCC.js';
4
4
  import { BasicParser } from '../common/BasicParser.js';
5
5
  import { BlockHeaderToken, MetadataIdToken } from './WavPackToken.js';
@@ -18,11 +18,12 @@ export class WavPackParser extends BasicParser {
18
18
  this.audioDataSize = 0;
19
19
  }
20
20
  async parse() {
21
+ this.metadata.setAudioOnly();
21
22
  this.audioDataSize = 0;
22
23
  // First parse all WavPack blocks
23
24
  await this.parseWavPackBlocks();
24
25
  // try to parse APEv2 header
25
- return APEv2Parser.tryParseApeHeader(this.metadata, this.tokenizer, this.options);
26
+ return tryParseApeHeader(this.metadata, this.tokenizer, this.options);
26
27
  }
27
28
  async parseWavPackBlocks() {
28
29
  do {