music-metadata 11.15.0 → 11.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -32
- package/lib/ParserFactory.d.ts +1 -1
- package/lib/ParserFactory.js +17 -13
- package/lib/aiff/AiffParser.js +19 -12
- package/lib/aiff/AiffToken.d.ts +1 -1
- package/lib/aiff/AiffToken.js +2 -1
- package/lib/apev2/APEv2Parser.d.ts +4 -3
- package/lib/apev2/APEv2Parser.js +59 -16
- package/lib/apev2/APEv2TagMapper.js +1 -0
- package/lib/apev2/APEv2Token.d.ts +1 -1
- package/lib/apev2/APEv2Token.js +1 -1
- package/lib/asf/AsfGuid.js +56 -50
- package/lib/asf/AsfObject.d.ts +1 -1
- package/lib/asf/AsfObject.js +3 -2
- package/lib/asf/AsfParser.js +15 -7
- package/lib/asf/AsfTagMapper.d.ts +3 -2
- package/lib/asf/AsfTagMapper.js +21 -4
- package/lib/common/CaseInsensitiveTagMap.d.ts +1 -1
- package/lib/common/CombinedTagMapper.d.ts +2 -2
- package/lib/common/CombinedTagMapper.js +6 -6
- package/lib/common/FourCC.js +3 -2
- package/lib/common/GenericTagMapper.d.ts +1 -1
- package/lib/common/GenericTagTypes.js +1 -1
- package/lib/common/MetadataCollector.d.ts +3 -1
- package/lib/common/MetadataCollector.js +28 -9
- package/lib/common/Util.js +25 -17
- package/lib/core.d.ts +4 -4
- package/lib/core.js +18 -11
- package/lib/dsdiff/DsdiffParser.js +29 -16
- package/lib/dsf/DsfChunk.d.ts +1 -1
- package/lib/dsf/DsfParser.js +3 -5
- package/lib/ebml/EbmlIterator.d.ts +1 -1
- package/lib/ebml/EbmlIterator.js +35 -6
- package/lib/ebml/types.d.ts +1 -1
- package/lib/ebml/types.js +1 -1
- package/lib/flac/FlacParser.d.ts +1 -1
- package/lib/flac/FlacParser.js +6 -5
- package/lib/flac/FlacToken.d.ts +1 -1
- package/lib/flac/FlacToken.js +1 -1
- package/lib/id3v1/ID3v1Parser.d.ts +1 -1
- package/lib/id3v1/ID3v1Parser.js +212 -42
- package/lib/id3v2/AbstractID3Parser.js +5 -5
- package/lib/id3v2/FrameHeader.d.ts +1 -1
- package/lib/id3v2/FrameHeader.js +8 -5
- package/lib/id3v2/FrameParser.d.ts +2 -1
- package/lib/id3v2/FrameParser.js +53 -22
- package/lib/id3v2/ID3v22TagMapper.d.ts +1 -1
- package/lib/id3v2/ID3v22TagMapper.js +1 -1
- package/lib/id3v2/ID3v24TagMapper.js +3 -3
- package/lib/id3v2/ID3v2ChapterToken.js +2 -2
- package/lib/id3v2/ID3v2Parser.d.ts +1 -1
- package/lib/id3v2/ID3v2Parser.js +32 -16
- package/lib/id3v2/ID3v2Token.d.ts +2 -2
- package/lib/id3v2/ID3v2Token.js +2 -3
- package/lib/index.js +6 -3
- package/lib/lrc/LyricsParser.js +3 -5
- package/lib/matroska/MatroskaDtd.js +7 -3
- package/lib/matroska/MatroskaParser.js +22 -11
- package/lib/matroska/types.d.ts +2 -2
- package/lib/mp4/Atom.d.ts +4 -3
- package/lib/mp4/Atom.js +44 -11
- package/lib/mp4/AtomToken.d.ts +1 -1
- package/lib/mp4/AtomToken.js +30 -8
- package/lib/mp4/MP4Parser.d.ts +2 -0
- package/lib/mp4/MP4Parser.js +79 -54
- package/lib/mp4/MP4TagMapper.d.ts +2 -2
- package/lib/mpeg/MpegLoader.js +1 -1
- package/lib/mpeg/MpegParser.js +41 -19
- package/lib/mpeg/ReplayGainDataFormat.d.ts +2 -2
- package/lib/mpeg/ReplayGainDataFormat.js +1 -1
- package/lib/mpeg/XingTag.d.ts +1 -1
- package/lib/musepack/MusepackParser.js +2 -2
- package/lib/musepack/sv7/BitReader.js +1 -1
- package/lib/musepack/sv7/MpcSv7Parser.js +4 -3
- package/lib/musepack/sv7/StreamVersion7.js +3 -3
- package/lib/musepack/sv8/MpcSv8Parser.js +7 -5
- package/lib/musepack/sv8/StreamVersion8.js +2 -2
- package/lib/ogg/OggParser.js +16 -12
- package/lib/ogg/OggToken.js +1 -1
- package/lib/ogg/flac/FlacStream.d.ts +2 -2
- package/lib/ogg/flac/FlacStream.js +2 -2
- package/lib/ogg/opus/OpusStream.d.ts +2 -2
- package/lib/ogg/opus/OpusStream.js +8 -4
- package/lib/ogg/speex/SpeexStream.d.ts +2 -2
- package/lib/ogg/speex/SpeexStream.js +2 -1
- package/lib/ogg/theora/TheoraStream.d.ts +2 -2
- package/lib/ogg/vorbis/Vorbis.d.ts +1 -1
- package/lib/ogg/vorbis/VorbisDecoder.js +1 -1
- package/lib/ogg/vorbis/VorbisStream.d.ts +3 -3
- package/lib/ogg/vorbis/VorbisStream.js +14 -9
- package/lib/ogg/vorbis/VorbisTagMapper.js +4 -0
- package/lib/riff/RiffChunk.d.ts +3 -1
- package/lib/riff/RiffChunk.js +3 -2
- package/lib/riff/RiffInfoTagMap.d.ts +1 -1
- package/lib/type.d.ts +3 -3
- package/lib/type.js +1 -1
- package/lib/wav/WaveChunk.d.ts +1 -1
- package/lib/wav/WaveChunk.js +3 -2
- package/lib/wav/WaveParser.d.ts +6 -2
- package/lib/wav/WaveParser.js +75 -22
- package/lib/wavpack/WavPackParser.js +22 -13
- package/lib/wavpack/WavPackToken.js +3 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ Following tag header formats are supported:
|
|
|
84
84
|
Following lyric formats are supported:
|
|
85
85
|
- [LRC](https://en.wikipedia.org/wiki/LRC_(file_format))
|
|
86
86
|
- Synchronized lyrics (SYLT)
|
|
87
|
-
- Unsynchronized lyrics (
|
|
87
|
+
- Unsynchronized lyrics (USLT)
|
|
88
88
|
|
|
89
89
|
Support for [MusicBrainz](https://musicbrainz.org/) tags as written by [Picard](https://picard.musicbrainz.org/).
|
|
90
90
|
[ReplayGain](https://wiki.hydrogenaud.io/index.php?title=ReplayGain) tags are supported.
|
|
@@ -123,7 +123,7 @@ yarn add music-metadata
|
|
|
123
123
|
|
|
124
124
|
**Node.js specific** functions to read an audio file or stream:
|
|
125
125
|
1. **File Parsing**: Parse audio files directly from the filesystem using the [parseFile function](#parsefile-function)
|
|
126
|
-
1. **Stream Parsing**: Parse audio metadata from a Node.js [Readable stream](https://nodejs.org/api/stream.html#class-streamreadable) using the [parseStream function](#
|
|
126
|
+
1. **Stream Parsing**: Parse audio metadata from a Node.js [Readable stream](https://nodejs.org/api/stream.html#class-streamreadable) using the [parseStream function](#parsestream-function).
|
|
127
127
|
|
|
128
128
|
**Cross-platform** functions available to read an audio file or stream:
|
|
129
129
|
|
|
@@ -136,8 +136,8 @@ There are multiple ways to parse (read) audio tracks:
|
|
|
136
136
|
> [!NOTE]
|
|
137
137
|
> Direct file access in Node.js is generally faster because it can 'jump' to various parts of the file without reading intermediate data.
|
|
138
138
|
|
|
139
|
-
### Node.js specific
|
|
140
|
-
These functions
|
|
139
|
+
### Node.js specific functions
|
|
140
|
+
These functions use Node.js-specific APIs,
|
|
141
141
|
making them incompatible with browser-based JavaScript engines.
|
|
142
142
|
|
|
143
143
|
#### `parseFile` function
|
|
@@ -269,7 +269,7 @@ import { createReadStream } from 'fs';
|
|
|
269
269
|
|
|
270
270
|
### Cross-platform functions
|
|
271
271
|
These functions are designed to be cross-platform,
|
|
272
|
-
meaning
|
|
272
|
+
meaning they can be used in both Node.js and web browsers.
|
|
273
273
|
|
|
274
274
|
#### `parseWebStream` function
|
|
275
275
|
|
|
@@ -295,14 +295,14 @@ parseWebStream(webStream: ReadableStream<Uint8Array>, fileInfo?: IFileInfo | str
|
|
|
295
295
|
An object containing file-related information or a string representing the MIME-type of the audio stream.
|
|
296
296
|
The fileInfo parameter can help the parser to correctly identify the audio format and may include:
|
|
297
297
|
|
|
298
|
-
|
|
298
|
+
- `mimeType`: A string representing the [MIME-type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) (e.g., `audio/mpeg`).
|
|
299
299
|
|
|
300
|
-
|
|
301
|
-
|
|
300
|
+
If provided, it is assumed the streamed file content is to be the MIME-type.
|
|
301
|
+
If not provided, the parser will attempt to determine the format based on the content of the stream.
|
|
302
302
|
|
|
303
|
-
|
|
303
|
+
- `size`: The total size of the audio stream in bytes (useful for streams with a known length).
|
|
304
304
|
|
|
305
|
-
|
|
305
|
+
- `path`: A string representing the file path or filename, which can also assist in determining the format.
|
|
306
306
|
|
|
307
307
|
- `options`: `IOptions` (optional)
|
|
308
308
|
|
|
@@ -351,19 +351,19 @@ The `response.body` provides a [`ReadableStream`](https://developer.mozilla.org/
|
|
|
351
351
|
#### `parseBlob` function
|
|
352
352
|
|
|
353
353
|
Parses metadata from an audio file represented as a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob).
|
|
354
|
-
This function
|
|
354
|
+
This function reads slices of the Blob and supports both browser File objects and Node.js Blob objects.
|
|
355
355
|
|
|
356
356
|
##### Syntax
|
|
357
357
|
```ts
|
|
358
|
-
parseBlob(blob: Blob, options?: IOptions
|
|
358
|
+
parseBlob(blob: Blob, options?: IOptions): Promise<IAudioMetadata>
|
|
359
359
|
```
|
|
360
360
|
|
|
361
361
|
##### Parameters
|
|
362
362
|
|
|
363
363
|
- `blob`: [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
|
|
364
364
|
|
|
365
|
-
|
|
366
|
-
|
|
365
|
+
The Blob object containing the audio data to be parsed.
|
|
366
|
+
This can be a File selected by the user or a Blob containing audio data. The Blob size and MIME type are passed to the parser.
|
|
367
367
|
|
|
368
368
|
- `options`: [IOptions](#ioptions-interface) (optional)
|
|
369
369
|
|
|
@@ -400,7 +400,7 @@ This function is particularly useful when you already have audio data in memory.
|
|
|
400
400
|
|
|
401
401
|
##### Syntax
|
|
402
402
|
```ts
|
|
403
|
-
parseBuffer(
|
|
403
|
+
parseBuffer(uint8Array: Uint8Array, fileInfo?: IFileInfo | string, options?: IOptions): Promise<IAudioMetadata>
|
|
404
404
|
```
|
|
405
405
|
|
|
406
406
|
##### Parameters
|
|
@@ -518,12 +518,13 @@ readMetadata();
|
|
|
518
518
|
|
|
519
519
|
### Handling Parse Errors
|
|
520
520
|
|
|
521
|
-
`music-metadata`
|
|
522
|
-
|
|
521
|
+
`music-metadata` exports custom error classes that extend JavaScript `Error`.
|
|
522
|
+
Parsing can also reject with other errors, such as filesystem, stream, or tokenizer errors.
|
|
523
|
+
Handle caught values as `unknown` in TypeScript and narrow them before accessing error properties.
|
|
523
524
|
|
|
524
525
|
#### Union of Parse Errors
|
|
525
526
|
|
|
526
|
-
|
|
527
|
+
The `UnionOfParseErrors` type groups the following library error classes; it does not describe every possible rejection:
|
|
527
528
|
```ts
|
|
528
529
|
export type UnionOfParseErrors =
|
|
529
530
|
| CouldNotDetermineFileTypeError
|
|
@@ -545,10 +546,10 @@ export type UnionOfParseErrors =
|
|
|
545
546
|
|
|
546
547
|
#### `orderTags` function
|
|
547
548
|
|
|
548
|
-
|
|
549
|
+
Converts an array of native tags to a dictionary keyed by tag identifier. Each value is an array of tag values.
|
|
549
550
|
|
|
550
551
|
```ts
|
|
551
|
-
orderTags(nativeTags: ITag[]):
|
|
552
|
+
orderTags(nativeTags: ITag[]): INativeTagDict
|
|
552
553
|
```
|
|
553
554
|
|
|
554
555
|
```js
|
|
@@ -571,7 +572,7 @@ import { inspect } from 'util';
|
|
|
571
572
|
Can be used to convert the normalized rating value to the 0..5 stars, where 0 an undefined rating, 1 the star the lowest rating and 5 the highest rating.
|
|
572
573
|
|
|
573
574
|
```ts
|
|
574
|
-
ratingToStars(rating: number): number
|
|
575
|
+
ratingToStars(rating: number | undefined): number
|
|
575
576
|
```
|
|
576
577
|
#### `selectCover` function
|
|
577
578
|
|
|
@@ -613,7 +614,7 @@ Returns a list of supported MIME-types. This may include some MIME-types which a
|
|
|
613
614
|
- Metadata not listed in the SeekHead may be skipped.
|
|
614
615
|
- If the SeekHead is missing, this option has no effect.
|
|
615
616
|
|
|
616
|
-
- `observer`: `(update:
|
|
617
|
+
- `observer`: `(update: IMetadataEvent) => void`:
|
|
617
618
|
|
|
618
619
|
Callback function triggered when common tags or format properties are updated during parsing.
|
|
619
620
|
Allows real-time monitoring of metadata as it becomes available.
|
|
@@ -628,7 +629,7 @@ Returns a list of supported MIME-types. This may include some MIME-types which a
|
|
|
628
629
|
This is particularly beneficial for streaming input, as it avoids the need to read the entire stream.
|
|
629
630
|
|
|
630
631
|
> [!NOTE]
|
|
631
|
-
> -
|
|
632
|
+
> - `format.duration` may be available without enabling `duration`. Set `duration: true` to allow additional scanning when needed; duration can still be unavailable.
|
|
632
633
|
> - Using `mkvUseIndex` can improve performance in Matroska files, but be aware of potential side effects, such as missing metadata due to skipped elements.
|
|
633
634
|
|
|
634
635
|
|
|
@@ -637,8 +638,9 @@ Returns a list of supported MIME-types. This may include some MIME-types which a
|
|
|
637
638
|
If the returned promise resolves, the metadata (TypeScript `IAudioMetadata` interface) contains:
|
|
638
639
|
- [`metadata.format`](#metadataformat) Audio format information
|
|
639
640
|
- [`metadata.common`](#metadatacommon) Is a generic (abstract) way of reading metadata information.
|
|
640
|
-
- [`metadata.trackInfo`](#
|
|
641
|
-
- `metadata.native`
|
|
641
|
+
- [`metadata.format.trackInfo`](#metadataformattrackinfo) Describes individual audio and video tracks when available.
|
|
642
|
+
- `metadata.native` Maps each tag format to an array of native (original) tags found in the parsed audio file.
|
|
643
|
+
- `metadata.quality.warnings` Contains non-fatal parsing warnings, each with a `message` string.
|
|
642
644
|
|
|
643
645
|
#### `metadata.format`
|
|
644
646
|
|
|
@@ -660,15 +662,16 @@ Audio format information. Defined in the TypeScript `IFormat` interface:
|
|
|
660
662
|
- `format.trackGain?: number` Track gain in dB
|
|
661
663
|
- `format.albumGain?: number` Album gain in dB
|
|
662
664
|
|
|
663
|
-
#### `metadata.trackInfo`
|
|
665
|
+
#### `metadata.format.trackInfo`
|
|
664
666
|
|
|
665
|
-
|
|
667
|
+
Containers such as Matroska and MPEG-4 can contain multiple audio and video tracks.
|
|
668
|
+
The experimental `metadata.format.trackInfo` property describes these individual tracks.
|
|
666
669
|
|
|
667
|
-
`metadata.trackInfo` is
|
|
670
|
+
`metadata.format.trackInfo` is an array of [trackInfo](#trackinfo) objects, empty when no track information is available.
|
|
668
671
|
|
|
669
672
|
##### trackInfo
|
|
670
673
|
|
|
671
|
-
|
|
674
|
+
Individual track information. Defined in the TypeScript `ITrackInfo` interface:
|
|
672
675
|
- `trackInfo.type?: TrackType` Track type
|
|
673
676
|
- `trackInfo.codecName?: string` Codec name
|
|
674
677
|
- `trackInfo.codecSettings?: string` Codec settings
|
|
@@ -685,7 +688,7 @@ Audio format information. Defined in the TypeScript `IFormat` interface:
|
|
|
685
688
|
- `audioTrack.samplingFrequency?: number`
|
|
686
689
|
- `audioTrack.outputSamplingFrequency?: number`
|
|
687
690
|
- `audioTrack.channels?: number`
|
|
688
|
-
- `audioTrack.channelPositions?:
|
|
691
|
+
- `audioTrack.channelPositions?: Uint8Array`
|
|
689
692
|
- `audioTrack.bitDepth?: number`
|
|
690
693
|
|
|
691
694
|
##### `trackInfo.videoTrack`
|
|
@@ -698,7 +701,7 @@ Audio format information. Defined in the TypeScript `IFormat` interface:
|
|
|
698
701
|
- `videoTrack.displayHeight?: number`
|
|
699
702
|
- `videoTrack.displayUnit?: number`
|
|
700
703
|
- `videoTrack.aspectRatioType?: number`
|
|
701
|
-
- `videoTrack.colourSpace?:
|
|
704
|
+
- `videoTrack.colourSpace?: Uint8Array`
|
|
702
705
|
- `videoTrack.gammaValue?: number`
|
|
703
706
|
|
|
704
707
|
#### `metadata.common`
|
|
@@ -737,7 +740,7 @@ export interface IPicture {
|
|
|
737
740
|
/**
|
|
738
741
|
* Image data
|
|
739
742
|
*/
|
|
740
|
-
data:
|
|
743
|
+
data: Uint8Array;
|
|
741
744
|
/**
|
|
742
745
|
* Optional description
|
|
743
746
|
*/
|
package/lib/ParserFactory.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type MediaType } from 'media-typer';
|
|
2
|
+
import type { ITokenizer } from 'strtok3';
|
|
2
3
|
import { type INativeMetadataCollector } from './common/MetadataCollector.js';
|
|
3
4
|
import { type IAudioMetadata, type IOptions, type ParserType } from './type.js';
|
|
4
|
-
import type { ITokenizer } from 'strtok3';
|
|
5
5
|
export interface IParserLoader {
|
|
6
6
|
/**
|
|
7
7
|
* Returns a list of supported file extensions
|
package/lib/ParserFactory.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { fileTypeFromBuffer } from 'file-type';
|
|
2
1
|
import ContentType from 'content-type';
|
|
3
|
-
import { parse as mimeTypeParse } from 'media-typer';
|
|
4
2
|
import initDebug from 'debug';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { CouldNotDetermineFileTypeError, UnsupportedFileTypeError } from './ParseError.js';
|
|
3
|
+
import { fileTypeFromBuffer } from 'file-type';
|
|
4
|
+
import { parse as mimeTypeParse } from 'media-typer';
|
|
5
|
+
import { aiffParserLoader } from './aiff/AiffLoader.js';
|
|
9
6
|
import { apeParserLoader } from './apev2/Apev2Loader.js';
|
|
10
7
|
import { asfParserLoader } from './asf/AsfLoader.js';
|
|
8
|
+
import { MetadataCollector } from './common/MetadataCollector.js';
|
|
9
|
+
import { scanAppendingHeaders } from './core.js';
|
|
11
10
|
import { dsdiffParserLoader } from './dsdiff/DsdiffLoader.js';
|
|
12
|
-
import { aiffParserLoader } from './aiff/AiffLoader.js';
|
|
13
11
|
import { dsfParserLoader } from './dsf/DsfLoader.js';
|
|
14
12
|
import { flacParserLoader } from './flac/FlacLoader.js';
|
|
15
13
|
import { matroskaParserLoader } from './matroska/MatroskaLoader.js';
|
|
16
14
|
import { mp4ParserLoader } from './mp4/Mp4Loader.js';
|
|
15
|
+
import { mpegParserLoader } from './mpeg/MpegLoader.js';
|
|
17
16
|
import { musepackParserLoader } from './musepack/MusepackLoader.js';
|
|
18
17
|
import { oggParserLoader } from './ogg/OggLoader.js';
|
|
19
|
-
import {
|
|
18
|
+
import { CouldNotDetermineFileTypeError, UnsupportedFileTypeError } from './ParseError.js';
|
|
19
|
+
import { TrackType } from './type.js';
|
|
20
20
|
import { riffParserLoader } from './wav/WaveLoader.js';
|
|
21
|
-
import {
|
|
21
|
+
import { wavpackParserLoader } from './wavpack/WavPackLoader.js';
|
|
22
22
|
const debug = initDebug('music-metadata:parser:factory');
|
|
23
23
|
export function parseHttpContentType(contentType) {
|
|
24
24
|
const type = ContentType.parse(contentType);
|
|
@@ -47,7 +47,9 @@ export class ParserFactory {
|
|
|
47
47
|
musepackParserLoader,
|
|
48
48
|
dsfParserLoader,
|
|
49
49
|
dsdiffParserLoader
|
|
50
|
-
].forEach(parser => {
|
|
50
|
+
].forEach(parser => {
|
|
51
|
+
this.registerParser(parser);
|
|
52
|
+
});
|
|
51
53
|
}
|
|
52
54
|
registerParser(parser) {
|
|
53
55
|
this.parsers.push(parser);
|
|
@@ -73,7 +75,7 @@ export class ParserFactory {
|
|
|
73
75
|
debug('Guess parser on content...');
|
|
74
76
|
await tokenizer.peekBuffer(buf, { mayBeLess: true });
|
|
75
77
|
const guessedType = await fileTypeFromBuffer(buf, { mpegOffsetTolerance: 10 });
|
|
76
|
-
if (!guessedType
|
|
78
|
+
if (!guessedType?.mime) {
|
|
77
79
|
throw new CouldNotDetermineFileTypeError('Failed to determine audio format');
|
|
78
80
|
}
|
|
79
81
|
debug(`Guessed file type is mime=${guessedType.mime}, extension=${guessedType.ext}`);
|
|
@@ -105,15 +107,17 @@ export class ParserFactory {
|
|
|
105
107
|
* @return Parser submodule name
|
|
106
108
|
*/
|
|
107
109
|
findLoaderForExtension(filePath) {
|
|
108
|
-
if (!filePath)
|
|
110
|
+
if (!filePath) {
|
|
109
111
|
return;
|
|
112
|
+
}
|
|
110
113
|
const extension = getExtension(filePath).toLocaleLowerCase() || filePath;
|
|
111
114
|
return this.parsers.find(parser => parser.extensions.indexOf(extension) !== -1);
|
|
112
115
|
}
|
|
113
116
|
findLoaderForContentType(httpContentType) {
|
|
114
117
|
let mime;
|
|
115
|
-
if (!httpContentType)
|
|
118
|
+
if (!httpContentType) {
|
|
116
119
|
return;
|
|
120
|
+
}
|
|
117
121
|
try {
|
|
118
122
|
mime = parseHttpContentType(httpContentType);
|
|
119
123
|
}
|
package/lib/aiff/AiffParser.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import * as Token from 'token-types';
|
|
2
1
|
import initDebug from 'debug';
|
|
3
2
|
import * as strtok3 from 'strtok3';
|
|
4
|
-
import
|
|
5
|
-
import { FourCcToken } from '../common/FourCC.js';
|
|
3
|
+
import * as Token from 'token-types';
|
|
6
4
|
import { BasicParser } from '../common/BasicParser.js';
|
|
5
|
+
import { FourCcToken } from '../common/FourCC.js';
|
|
6
|
+
import { ID3v2Parser } from '../id3v2/ID3v2Parser.js';
|
|
7
|
+
import * as iff from '../iff/index.js';
|
|
7
8
|
import * as AiffToken from './AiffToken.js';
|
|
8
9
|
import { AiffContentError, compressionTypes } from './AiffToken.js';
|
|
9
|
-
import * as iff from '../iff/index.js';
|
|
10
10
|
const debug = initDebug('music-metadata:parser:aiff');
|
|
11
11
|
/**
|
|
12
12
|
* AIFF - Audio Interchange File Format
|
|
@@ -22,8 +22,9 @@ export class AIFFParser extends BasicParser {
|
|
|
22
22
|
}
|
|
23
23
|
async parse() {
|
|
24
24
|
const header = await this.tokenizer.readToken(iff.Header);
|
|
25
|
-
if (header.chunkID !== 'FORM')
|
|
26
|
-
throw new AiffContentError(
|
|
25
|
+
if (header.chunkID !== 'FORM') {
|
|
26
|
+
throw new AiffContentError("Invalid Chunk-ID, expected 'FORM'"); // Not AIFF format
|
|
27
|
+
}
|
|
27
28
|
const type = await this.tokenizer.readToken(FourCcToken);
|
|
28
29
|
switch (type) {
|
|
29
30
|
case 'AIFF':
|
|
@@ -40,7 +41,8 @@ export class AIFFParser extends BasicParser {
|
|
|
40
41
|
this.metadata.setFormat('lossless', !this.isCompressed);
|
|
41
42
|
this.metadata.setAudioOnly();
|
|
42
43
|
try {
|
|
43
|
-
while (!this.tokenizer.fileInfo.size ||
|
|
44
|
+
while (!this.tokenizer.fileInfo.size ||
|
|
45
|
+
this.tokenizer.fileInfo.size - this.tokenizer.position >= iff.Header.len) {
|
|
44
46
|
debug(`Reading AIFF chunk at offset=${this.tokenizer.position}`);
|
|
45
47
|
const chunkHeader = await this.tokenizer.readToken(iff.Header);
|
|
46
48
|
const nextChunk = 2 * Math.round(chunkHeader.chunkSize / 2);
|
|
@@ -50,7 +52,7 @@ export class AIFFParser extends BasicParser {
|
|
|
50
52
|
}
|
|
51
53
|
catch (err) {
|
|
52
54
|
if (err instanceof strtok3.EndOfStreamError) {
|
|
53
|
-
debug(
|
|
55
|
+
debug('End-of-stream');
|
|
54
56
|
}
|
|
55
57
|
else {
|
|
56
58
|
throw err;
|
|
@@ -59,7 +61,8 @@ export class AIFFParser extends BasicParser {
|
|
|
59
61
|
}
|
|
60
62
|
async readData(header) {
|
|
61
63
|
switch (header.chunkID) {
|
|
62
|
-
case 'COMM': {
|
|
64
|
+
case 'COMM': {
|
|
65
|
+
// The Common Chunk
|
|
63
66
|
if (this.isCompressed === null) {
|
|
64
67
|
throw new AiffContentError('Failed to parse AIFF.COMM chunk when compression type is unknown');
|
|
65
68
|
}
|
|
@@ -74,7 +77,8 @@ export class AIFFParser extends BasicParser {
|
|
|
74
77
|
}
|
|
75
78
|
return header.chunkSize;
|
|
76
79
|
}
|
|
77
|
-
case 'ID3 ': {
|
|
80
|
+
case 'ID3 ': {
|
|
81
|
+
// ID3-meta-data
|
|
78
82
|
const id3_data = await this.tokenizer.readToken(new Token.Uint8ArrayType(header.chunkSize));
|
|
79
83
|
const rst = strtok3.fromBuffer(id3_data);
|
|
80
84
|
await new ID3v2Parser().parse(this.metadata, rst, this.options);
|
|
@@ -82,7 +86,7 @@ export class AIFFParser extends BasicParser {
|
|
|
82
86
|
}
|
|
83
87
|
case 'SSND': // Sound Data Chunk
|
|
84
88
|
if (this.metadata.format.duration) {
|
|
85
|
-
this.metadata.setFormat('bitrate', 8 * header.chunkSize / this.metadata.format.duration);
|
|
89
|
+
this.metadata.setFormat('bitrate', (8 * header.chunkSize) / this.metadata.format.duration);
|
|
86
90
|
}
|
|
87
91
|
return 0;
|
|
88
92
|
case 'NAME': // Sample name chunk
|
|
@@ -97,7 +101,10 @@ export class AIFFParser extends BasicParser {
|
|
|
97
101
|
}
|
|
98
102
|
async readTextChunk(header) {
|
|
99
103
|
const value = await this.tokenizer.readToken(new Token.StringType(header.chunkSize, 'ascii'));
|
|
100
|
-
const values = value
|
|
104
|
+
const values = value
|
|
105
|
+
.split('\0')
|
|
106
|
+
.map(v => v.trim())
|
|
107
|
+
.filter(v => v?.length);
|
|
101
108
|
await Promise.all(values.map(v => this.metadata.addTag('AIFF', header.chunkID, v)));
|
|
102
109
|
return header.chunkSize;
|
|
103
110
|
}
|
package/lib/aiff/AiffToken.d.ts
CHANGED
package/lib/aiff/AiffToken.js
CHANGED
|
@@ -18,8 +18,9 @@ export class Common {
|
|
|
18
18
|
constructor(header, isAifc) {
|
|
19
19
|
this.isAifc = isAifc;
|
|
20
20
|
const minimumChunkSize = isAifc ? 22 : 18;
|
|
21
|
-
if (header.chunkSize < minimumChunkSize)
|
|
21
|
+
if (header.chunkSize < minimumChunkSize) {
|
|
22
22
|
throw new AiffContentError(`COMMON CHUNK size should always be at least ${minimumChunkSize}`);
|
|
23
|
+
}
|
|
23
24
|
this.len = header.chunkSize;
|
|
24
25
|
}
|
|
25
26
|
get(buf, off) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import type { IRandomAccessTokenizer } from 'strtok3';
|
|
1
2
|
import * as strtok3 from 'strtok3';
|
|
2
|
-
import type { IOptions, IApeHeader } from '../type.js';
|
|
3
|
-
import type { INativeMetadataCollector } from '../common/MetadataCollector.js';
|
|
4
3
|
import { BasicParser } from '../common/BasicParser.js';
|
|
4
|
+
import type { INativeMetadataCollector } from '../common/MetadataCollector.js';
|
|
5
|
+
import type { IApeHeader, IOptions } from '../type.js';
|
|
5
6
|
import { type IFooter, type IHeader } from './APEv2Token.js';
|
|
6
|
-
import type { IRandomAccessTokenizer } from 'strtok3';
|
|
7
7
|
declare const ApeContentError_base: {
|
|
8
8
|
new (message: string): {
|
|
9
9
|
readonly fileType: string;
|
|
@@ -40,6 +40,7 @@ export declare class APEv2Parser extends BasicParser {
|
|
|
40
40
|
tryParseApeHeader(): Promise<void>;
|
|
41
41
|
parse(): Promise<void>;
|
|
42
42
|
parseTags(footer: IFooter): Promise<void>;
|
|
43
|
+
private readTagValue;
|
|
43
44
|
private parseDescriptorExpansion;
|
|
44
45
|
private parseHeader;
|
|
45
46
|
}
|
package/lib/apev2/APEv2Parser.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { textDecode } from '@borewit/text-codec';
|
|
1
2
|
import initDebug from 'debug';
|
|
2
3
|
import * as strtok3 from 'strtok3';
|
|
3
4
|
import { StringType } from 'token-types';
|
|
4
|
-
import * as util from '../common/Util.js';
|
|
5
5
|
import { BasicParser } from '../common/BasicParser.js';
|
|
6
|
-
import
|
|
6
|
+
import * as util from '../common/Util.js';
|
|
7
7
|
import { makeUnexpectedFileContentError } from '../ParseError.js';
|
|
8
|
-
import {
|
|
8
|
+
import { DataType, DescriptorParser, Header, TagFooter, TagItemHeader } from './APEv2Token.js';
|
|
9
9
|
const debug = initDebug('music-metadata:parser:APEv2');
|
|
10
10
|
const tagFormat = 'APEv2';
|
|
11
11
|
const preamble = 'APETAGEX';
|
|
@@ -61,8 +61,9 @@ export class APEv2Parser extends BasicParser {
|
|
|
61
61
|
}
|
|
62
62
|
static parseTagFooter(metadata, buffer, options) {
|
|
63
63
|
const footer = TagFooter.get(buffer, buffer.length - TagFooter.len);
|
|
64
|
-
if (footer.ID !== preamble)
|
|
64
|
+
if (footer.ID !== preamble) {
|
|
65
65
|
throw new ApeContentError('Unexpected APEv2 Footer ID preamble value');
|
|
66
|
+
}
|
|
66
67
|
strtok3.fromBuffer(buffer);
|
|
67
68
|
const apeParser = new APEv2Parser(metadata, strtok3.fromBuffer(buffer), options);
|
|
68
69
|
return apeParser.parseTags(footer);
|
|
@@ -72,7 +73,7 @@ export class APEv2Parser extends BasicParser {
|
|
|
72
73
|
*/
|
|
73
74
|
async tryParseApeHeader() {
|
|
74
75
|
if (this.tokenizer.fileInfo.size && this.tokenizer.fileInfo.size - this.tokenizer.position < TagFooter.len) {
|
|
75
|
-
debug(
|
|
76
|
+
debug('No APEv2 header found, end-of-file reached');
|
|
76
77
|
return;
|
|
77
78
|
}
|
|
78
79
|
const footer = await this.tokenizer.peekToken(TagFooter);
|
|
@@ -91,8 +92,9 @@ export class APEv2Parser extends BasicParser {
|
|
|
91
92
|
}
|
|
92
93
|
async parse() {
|
|
93
94
|
const descriptor = await this.tokenizer.readToken(DescriptorParser);
|
|
94
|
-
if (descriptor.ID !== 'MAC ')
|
|
95
|
+
if (descriptor.ID !== 'MAC ') {
|
|
95
96
|
throw new ApeContentError('Unexpected descriptor ID');
|
|
97
|
+
}
|
|
96
98
|
this.ape.descriptor = descriptor;
|
|
97
99
|
const lenExp = descriptor.descriptorBytes - DescriptorParser.len;
|
|
98
100
|
const header = await (lenExp > 0 ? this.parseDescriptorExpansion(lenExp) : this.parseHeader());
|
|
@@ -111,15 +113,31 @@ export class APEv2Parser extends BasicParser {
|
|
|
111
113
|
}
|
|
112
114
|
// Only APEv2 tag has tag item headers
|
|
113
115
|
const tagItemHeader = await this.tokenizer.readToken(TagItemHeader);
|
|
114
|
-
bytesRemaining -= TagItemHeader.len
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
bytesRemaining -= TagItemHeader.len;
|
|
117
|
+
// Reserve the value bytes before looking for the key terminator.
|
|
118
|
+
if (tagItemHeader.size >= bytesRemaining) {
|
|
119
|
+
throw new ApeContentError(`Invalid tag item size: ${tagItemHeader.size}`);
|
|
120
|
+
}
|
|
121
|
+
const keyBytes = await this.tokenizer.peekBuffer(keyBuffer, {
|
|
122
|
+
length: Math.min(keyBuffer.length, bytesRemaining - tagItemHeader.size),
|
|
123
|
+
mayBeLess: true
|
|
124
|
+
});
|
|
125
|
+
let zero = keyBuffer.subarray(0, keyBytes).indexOf(0);
|
|
126
|
+
if (zero === -1) {
|
|
127
|
+
throw new ApeContentError('Unterminated tag item key');
|
|
128
|
+
}
|
|
117
129
|
const key = await this.tokenizer.readToken(new StringType(zero, 'ascii'));
|
|
118
130
|
await this.tokenizer.ignore(1);
|
|
119
131
|
bytesRemaining -= key.length + 1;
|
|
132
|
+
if (this.tokenizer.fileInfo.size !== undefined &&
|
|
133
|
+
tagItemHeader.size > this.tokenizer.fileInfo.size - this.tokenizer.position) {
|
|
134
|
+
throw new ApeContentError(`Invalid tag item size: ${tagItemHeader.size}`);
|
|
135
|
+
}
|
|
136
|
+
bytesRemaining -= tagItemHeader.size;
|
|
120
137
|
switch (tagItemHeader.flags.dataType) {
|
|
121
|
-
case DataType.text_utf8: {
|
|
122
|
-
|
|
138
|
+
case DataType.text_utf8: {
|
|
139
|
+
// utf-8 text-string
|
|
140
|
+
const value = textDecode(await this.readTagValue(tagItemHeader.size), 'utf-8');
|
|
123
141
|
const values = value.split(/\x00/g);
|
|
124
142
|
await Promise.all(values.map(val => this.metadata.addTag(tagFormat, key, val)));
|
|
125
143
|
break;
|
|
@@ -129,8 +147,7 @@ export class APEv2Parser extends BasicParser {
|
|
|
129
147
|
await this.tokenizer.ignore(tagItemHeader.size);
|
|
130
148
|
}
|
|
131
149
|
else {
|
|
132
|
-
const picData =
|
|
133
|
-
await this.tokenizer.readBuffer(picData);
|
|
150
|
+
const picData = await this.readTagValue(tagItemHeader.size);
|
|
134
151
|
zero = util.findZero(picData);
|
|
135
152
|
const description = textDecode(picData.subarray(0, zero), 'utf-8');
|
|
136
153
|
const data = picData.subarray(zero + 1);
|
|
@@ -152,6 +169,30 @@ export class APEv2Parser extends BasicParser {
|
|
|
152
169
|
}
|
|
153
170
|
}
|
|
154
171
|
}
|
|
172
|
+
async readTagValue(size) {
|
|
173
|
+
const chunkSize = 64 * 1024;
|
|
174
|
+
if ((!this.tokenizer.supportsRandomAccess() || this.tokenizer.fileInfo.size === undefined) && size > chunkSize) {
|
|
175
|
+
// Stream sizes may be unknown or untrustworthy. Only allocate the full
|
|
176
|
+
// value after all of its bytes have actually been read.
|
|
177
|
+
const chunks = [];
|
|
178
|
+
for (let remaining = size; remaining > 0;) {
|
|
179
|
+
const chunk = new Uint8Array(Math.min(remaining, chunkSize));
|
|
180
|
+
await this.tokenizer.readBuffer(chunk);
|
|
181
|
+
chunks.push(chunk);
|
|
182
|
+
remaining -= chunk.length;
|
|
183
|
+
}
|
|
184
|
+
const value = new Uint8Array(size);
|
|
185
|
+
let offset = 0;
|
|
186
|
+
for (const chunk of chunks) {
|
|
187
|
+
value.set(chunk, offset);
|
|
188
|
+
offset += chunk.length;
|
|
189
|
+
}
|
|
190
|
+
return value;
|
|
191
|
+
}
|
|
192
|
+
const value = new Uint8Array(size);
|
|
193
|
+
await this.tokenizer.readBuffer(value);
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
155
196
|
async parseDescriptorExpansion(lenExp) {
|
|
156
197
|
await this.tokenizer.ignore(lenExp);
|
|
157
198
|
return this.parseHeader();
|
|
@@ -160,7 +201,7 @@ export class APEv2Parser extends BasicParser {
|
|
|
160
201
|
const header = await this.tokenizer.readToken(Header);
|
|
161
202
|
// ToDo before
|
|
162
203
|
this.metadata.setFormat('lossless', true);
|
|
163
|
-
this.metadata.setFormat('container',
|
|
204
|
+
this.metadata.setFormat('container', "Monkey's Audio");
|
|
164
205
|
this.metadata.setFormat('bitsPerSample', header.bitsPerSample);
|
|
165
206
|
this.metadata.setFormat('sampleRate', header.sampleRate);
|
|
166
207
|
this.metadata.setFormat('numberOfChannels', header.channel);
|
|
@@ -169,8 +210,10 @@ export class APEv2Parser extends BasicParser {
|
|
|
169
210
|
throw new ApeContentError('Missing APE descriptor');
|
|
170
211
|
}
|
|
171
212
|
return {
|
|
172
|
-
forwardBytes: this.ape.descriptor.seekTableBytes +
|
|
173
|
-
this.ape.descriptor.
|
|
213
|
+
forwardBytes: this.ape.descriptor.seekTableBytes +
|
|
214
|
+
this.ape.descriptor.headerDataBytes +
|
|
215
|
+
this.ape.descriptor.apeFrameDataBytes +
|
|
216
|
+
this.ape.descriptor.terminatingDataBytes
|
|
174
217
|
};
|
|
175
218
|
}
|
|
176
219
|
}
|
|
@@ -58,7 +58,7 @@ export declare const DataType: {
|
|
|
58
58
|
external_info: number;
|
|
59
59
|
reserved: number;
|
|
60
60
|
};
|
|
61
|
-
export type DataType = typeof DataType[keyof typeof DataType];
|
|
61
|
+
export type DataType = (typeof DataType)[keyof typeof DataType];
|
|
62
62
|
/**
|
|
63
63
|
* APE_DESCRIPTOR: defines the sizes (and offsets) of all the pieces, as well as the MD5 checksum
|
|
64
64
|
*/
|
package/lib/apev2/APEv2Token.js
CHANGED