music-metadata 10.0.0 → 10.1.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 +4 -3
- package/lib/ParserFactory.d.ts +20 -28
- package/lib/ParserFactory.js +204 -207
- package/lib/aiff/AiffParser.js +20 -18
- package/lib/aiff/AiffToken.d.ts +14 -2
- package/lib/aiff/AiffToken.js +12 -0
- package/lib/apev2/APEv2Parser.d.ts +4 -4
- package/lib/apev2/APEv2Parser.js +20 -12
- package/lib/apev2/APEv2Token.d.ts +2 -4
- package/lib/apev2/APEv2Token.js +1 -4
- package/lib/asf/AsfObject.d.ts +7 -16
- package/lib/asf/AsfObject.js +8 -34
- package/lib/asf/AsfParser.js +17 -10
- package/lib/asf/AsfTagMapper.d.ts +1 -1
- package/lib/asf/AsfTagMapper.js +3 -2
- package/lib/asf/AsfUtil.d.ts +3 -11
- package/lib/asf/AsfUtil.js +29 -30
- package/lib/asf/GUID.js +6 -9
- package/lib/common/BasicParser.d.ts +4 -4
- package/lib/common/BasicParser.js +6 -0
- package/lib/common/CaseInsensitiveTagMap.d.ts +1 -1
- package/lib/common/CombinedTagMapper.d.ts +5 -5
- package/lib/common/CombinedTagMapper.js +1 -1
- package/lib/common/FourCC.d.ts +1 -1
- package/lib/common/GenericTagMapper.d.ts +8 -8
- package/lib/common/GenericTagMapper.js +4 -4
- package/lib/common/GenericTagTypes.d.ts +5 -4
- package/lib/common/GenericTagTypes.js +2 -2
- package/lib/common/MetadataCollector.d.ts +9 -9
- package/lib/common/MetadataCollector.js +24 -17
- package/lib/common/RandomFileReader.d.ts +1 -1
- package/lib/common/RandomFileReader.js +1 -1
- package/lib/common/RandomUint8ArrayReader.d.ts +1 -1
- package/lib/common/Util.d.ts +2 -6
- package/lib/common/Util.js +9 -11
- package/lib/core.d.ts +7 -9
- package/lib/core.js +10 -7
- package/lib/dsdiff/DsdiffParser.js +26 -14
- package/lib/dsdiff/DsdiffToken.d.ts +2 -2
- package/lib/dsdiff/DsdiffToken.js +1 -0
- package/lib/dsf/DsfChunk.js +1 -0
- package/lib/dsf/DsfParser.js +4 -2
- package/lib/flac/FlacParser.d.ts +3 -3
- package/lib/flac/FlacParser.js +9 -12
- package/lib/id3v1/ID3v1Parser.d.ts +1 -1
- package/lib/id3v1/ID3v1Parser.js +7 -4
- package/lib/id3v2/AbstractID3Parser.d.ts +1 -1
- package/lib/id3v2/AbstractID3Parser.js +2 -1
- package/lib/id3v2/FrameParser.d.ts +28 -3
- package/lib/id3v2/FrameParser.js +50 -28
- package/lib/id3v2/ID3v22TagMapper.d.ts +1 -1
- package/lib/id3v2/ID3v24TagMapper.d.ts +2 -1
- package/lib/id3v2/ID3v24TagMapper.js +23 -16
- package/lib/id3v2/ID3v2Parser.d.ts +2 -2
- package/lib/id3v2/ID3v2Parser.js +19 -8
- package/lib/iff/index.js +1 -0
- package/lib/index.d.ts +5 -6
- package/lib/index.js +7 -8
- package/lib/lyrics3/Lyrics3.d.ts +1 -1
- package/lib/lyrics3/Lyrics3.js +1 -1
- package/lib/matroska/MatroskaDtd.d.ts +1 -1
- package/lib/matroska/MatroskaDtd.js +139 -138
- package/lib/matroska/MatroskaParser.d.ts +4 -4
- package/lib/matroska/MatroskaParser.js +12 -12
- package/lib/matroska/types.d.ts +6 -4
- package/lib/mp4/Atom.d.ts +3 -3
- package/lib/mp4/Atom.js +4 -7
- package/lib/mp4/AtomToken.js +2 -1
- package/lib/mp4/MP4Parser.js +29 -20
- package/lib/mp4/MP4TagMapper.d.ts +2 -2
- package/lib/mp4/MP4TagMapper.js +1 -1
- package/lib/mpeg/ExtendedLameHeader.d.ts +4 -4
- package/lib/mpeg/ExtendedLameHeader.js +2 -1
- package/lib/mpeg/MpegParser.d.ts +1 -1
- package/lib/mpeg/MpegParser.js +145 -96
- package/lib/mpeg/ReplayGainDataFormat.d.ts +1 -1
- package/lib/mpeg/ReplayGainDataFormat.js +1 -0
- package/lib/mpeg/XingTag.d.ts +4 -4
- package/lib/mpeg/XingTag.js +5 -4
- package/lib/musepack/index.js +1 -0
- package/lib/musepack/sv7/BitReader.js +14 -17
- package/lib/musepack/sv7/MpcSv7Parser.js +6 -1
- package/lib/musepack/sv7/StreamVersion7.js +1 -0
- package/lib/musepack/sv8/MpcSv8Parser.js +6 -2
- package/lib/musepack/sv8/StreamVersion8.d.ts +1 -1
- package/lib/musepack/sv8/StreamVersion8.js +1 -0
- package/lib/ogg/Ogg.d.ts +1 -1
- package/lib/ogg/Ogg.js +1 -0
- package/lib/ogg/OggParser.d.ts +3 -3
- package/lib/ogg/OggParser.js +25 -16
- package/lib/ogg/opus/Opus.d.ts +1 -1
- package/lib/ogg/opus/Opus.js +1 -0
- package/lib/ogg/opus/OpusParser.d.ts +4 -4
- package/lib/ogg/opus/OpusParser.js +2 -0
- package/lib/ogg/speex/Speex.js +1 -0
- package/lib/ogg/speex/SpeexParser.d.ts +4 -4
- package/lib/ogg/speex/SpeexParser.js +1 -0
- package/lib/ogg/theora/Theora.js +1 -0
- package/lib/ogg/theora/TheoraParser.d.ts +4 -4
- package/lib/ogg/theora/TheoraParser.js +1 -0
- package/lib/ogg/vorbis/Vorbis.d.ts +3 -3
- package/lib/ogg/vorbis/Vorbis.js +22 -11
- package/lib/ogg/vorbis/VorbisDecoder.d.ts +1 -1
- package/lib/ogg/vorbis/VorbisDecoder.js +1 -0
- package/lib/ogg/vorbis/VorbisParser.d.ts +4 -4
- package/lib/ogg/vorbis/VorbisParser.js +3 -2
- package/lib/ogg/vorbis/VorbisTagMapper.d.ts +2 -2
- package/lib/ogg/vorbis/VorbisTagMapper.js +2 -2
- package/lib/riff/RiffChunk.d.ts +3 -3
- package/lib/riff/RiffChunk.js +1 -0
- package/lib/riff/RiffInfoTagMap.d.ts +1 -1
- package/lib/type.d.ts +35 -25
- package/lib/wav/BwfChunk.js +1 -0
- package/lib/wav/WaveChunk.d.ts +1 -1
- package/lib/wav/WaveChunk.js +1 -0
- package/lib/wav/WaveParser.js +27 -17
- package/lib/wavpack/WavPackParser.d.ts +1 -1
- package/lib/wavpack/WavPackParser.js +22 -13
- package/lib/wavpack/WavPackToken.d.ts +13 -17
- package/lib/wavpack/WavPackToken.js +22 -23
- package/package.json +15 -28
|
@@ -24,7 +24,7 @@ export class DsdiffParser extends BasicParser {
|
|
|
24
24
|
this.metadata.setFormat('lossless', true);
|
|
25
25
|
return this.readFmt8Chunks(header.chunkSize - BigInt(FourCcToken.len));
|
|
26
26
|
default:
|
|
27
|
-
throw Error(`Unsupported DSDIFF type: ${type}`);
|
|
27
|
+
throw new Error(`Unsupported DSDIFF type: ${type}`);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
async readFmt8Chunks(remainingSize) {
|
|
@@ -40,28 +40,35 @@ export class DsdiffParser extends BasicParser {
|
|
|
40
40
|
debug(`Reading data of chunk[ID=${header.chunkID}, size=${header.chunkSize}]`);
|
|
41
41
|
const p0 = this.tokenizer.position;
|
|
42
42
|
switch (header.chunkID.trim()) {
|
|
43
|
-
case 'FVER': // 3.1 FORMAT VERSION CHUNK
|
|
43
|
+
case 'FVER': { // 3.1 FORMAT VERSION CHUNK
|
|
44
44
|
const version = await this.tokenizer.readToken(Token.UINT32_LE);
|
|
45
45
|
debug(`DSDIFF version=${version}`);
|
|
46
46
|
break;
|
|
47
|
-
|
|
47
|
+
}
|
|
48
|
+
case 'PROP': { // 3.2 PROPERTY CHUNK
|
|
48
49
|
const propType = await this.tokenizer.readToken(FourCcToken);
|
|
49
50
|
if (propType !== 'SND ')
|
|
50
51
|
throw new Error('Unexpected PROP-chunk ID');
|
|
51
52
|
await this.handleSoundPropertyChunks(header.chunkSize - BigInt(FourCcToken.len));
|
|
52
53
|
break;
|
|
53
|
-
|
|
54
|
+
}
|
|
55
|
+
case 'ID3': { // Unofficial ID3 tag support
|
|
54
56
|
const id3_data = await this.tokenizer.readToken(new Token.Uint8ArrayType(Number(header.chunkSize)));
|
|
55
57
|
const rst = strtok3.fromBuffer(id3_data);
|
|
56
58
|
await new ID3v2Parser().parse(this.metadata, rst, this.options);
|
|
57
59
|
break;
|
|
60
|
+
}
|
|
61
|
+
case 'DSD':
|
|
62
|
+
if (this.metadata.format.numberOfChannels) {
|
|
63
|
+
this.metadata.setFormat('numberOfSamples', Number(header.chunkSize * BigInt(8) / BigInt(this.metadata.format.numberOfChannels)));
|
|
64
|
+
}
|
|
65
|
+
if (this.metadata.format.numberOfSamples && this.metadata.format.sampleRate) {
|
|
66
|
+
this.metadata.setFormat('duration', this.metadata.format.numberOfSamples / this.metadata.format.sampleRate);
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
58
69
|
default:
|
|
59
70
|
debug(`Ignore chunk[ID=${header.chunkID}, size=${header.chunkSize}]`);
|
|
60
71
|
break;
|
|
61
|
-
case 'DSD':
|
|
62
|
-
this.metadata.setFormat('numberOfSamples', Number(header.chunkSize * BigInt(8) / BigInt(this.metadata.format.numberOfChannels)));
|
|
63
|
-
this.metadata.setFormat('duration', this.metadata.format.numberOfSamples / this.metadata.format.sampleRate);
|
|
64
|
-
break;
|
|
65
72
|
}
|
|
66
73
|
const remaining = header.chunkSize - BigInt(this.tokenizer.position - p0);
|
|
67
74
|
if (remaining > 0) {
|
|
@@ -76,16 +83,18 @@ export class DsdiffParser extends BasicParser {
|
|
|
76
83
|
debug(`Sound-property-chunk[ID=${sndPropHeader.chunkID}, size=${sndPropHeader.chunkSize}]`);
|
|
77
84
|
const p0 = this.tokenizer.position;
|
|
78
85
|
switch (sndPropHeader.chunkID.trim()) {
|
|
79
|
-
case 'FS': // 3.2.1 Sample Rate Chunk
|
|
86
|
+
case 'FS': { // 3.2.1 Sample Rate Chunk
|
|
80
87
|
const sampleRate = await this.tokenizer.readToken(Token.UINT32_BE);
|
|
81
88
|
this.metadata.setFormat('sampleRate', sampleRate);
|
|
82
89
|
break;
|
|
83
|
-
|
|
90
|
+
}
|
|
91
|
+
case 'CHNL': { // 3.2.2 Channels Chunk
|
|
84
92
|
const numChannels = await this.tokenizer.readToken(Token.UINT16_BE);
|
|
85
93
|
this.metadata.setFormat('numberOfChannels', numChannels);
|
|
86
94
|
await this.handleChannelChunks(sndPropHeader.chunkSize - BigInt(Token.UINT16_BE.len));
|
|
87
95
|
break;
|
|
88
|
-
|
|
96
|
+
}
|
|
97
|
+
case 'CMPR': { // 3.2.3 Compression Type Chunk
|
|
89
98
|
const compressionIdCode = (await this.tokenizer.readToken(FourCcToken)).trim();
|
|
90
99
|
const count = await this.tokenizer.readToken(Token.UINT8);
|
|
91
100
|
const compressionName = await this.tokenizer.readToken(new Token.StringType(count, 'ascii'));
|
|
@@ -95,18 +104,20 @@ export class DsdiffParser extends BasicParser {
|
|
|
95
104
|
}
|
|
96
105
|
this.metadata.setFormat('codec', `${compressionIdCode} (${compressionName})`);
|
|
97
106
|
break;
|
|
98
|
-
|
|
107
|
+
}
|
|
108
|
+
case 'ABSS': { // 3.2.4 Absolute Start Time Chunk
|
|
99
109
|
const hours = await this.tokenizer.readToken(Token.UINT16_BE);
|
|
100
110
|
const minutes = await this.tokenizer.readToken(Token.UINT8);
|
|
101
111
|
const seconds = await this.tokenizer.readToken(Token.UINT8);
|
|
102
112
|
const samples = await this.tokenizer.readToken(Token.UINT32_BE);
|
|
103
113
|
debug(`ABSS ${hours}:${minutes}:${seconds}.${samples}`);
|
|
104
114
|
break;
|
|
105
|
-
|
|
115
|
+
}
|
|
116
|
+
case 'LSCO': { // 3.2.5 Loudspeaker Configuration Chunk
|
|
106
117
|
const lsConfig = await this.tokenizer.readToken(Token.UINT16_BE);
|
|
107
118
|
debug(`LSCO lsConfig=${lsConfig}`);
|
|
108
119
|
break;
|
|
109
|
-
|
|
120
|
+
}
|
|
110
121
|
default:
|
|
111
122
|
debug(`Unknown sound-property-chunk[ID=${sndPropHeader.chunkID}, size=${sndPropHeader.chunkSize}]`);
|
|
112
123
|
await this.tokenizer.ignore(Number(sndPropHeader.chunkSize));
|
|
@@ -137,3 +148,4 @@ export class DsdiffParser extends BasicParser {
|
|
|
137
148
|
return channels;
|
|
138
149
|
}
|
|
139
150
|
}
|
|
151
|
+
//# sourceMappingURL=DsdiffParser.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IGetToken } from 'strtok3';
|
|
2
|
-
import { IChunkHeader64 } from '../iff/index.js';
|
|
3
|
-
export { IChunkHeader64 } from '../iff/index.js';
|
|
2
|
+
import type { IChunkHeader64 } from '../iff/index.js';
|
|
3
|
+
export { type IChunkHeader64 } from '../iff/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* DSDIFF chunk header
|
|
6
6
|
* The data-size encoding is deviating from EA-IFF 85
|
package/lib/dsf/DsfChunk.js
CHANGED
package/lib/dsf/DsfParser.js
CHANGED
|
@@ -17,7 +17,7 @@ export class DsfParser extends AbstractID3Parser {
|
|
|
17
17
|
this.metadata.setFormat('lossless', true);
|
|
18
18
|
const dsdChunk = await this.tokenizer.readToken(DsdChunk);
|
|
19
19
|
if (dsdChunk.metadataPointer === BigInt(0)) {
|
|
20
|
-
debug(
|
|
20
|
+
debug("No ID3v2 tag present");
|
|
21
21
|
}
|
|
22
22
|
else {
|
|
23
23
|
debug(`expect ID3v2 at offset=${dsdChunk.metadataPointer}`);
|
|
@@ -32,7 +32,7 @@ export class DsfParser extends AbstractID3Parser {
|
|
|
32
32
|
const chunkHeader = await this.tokenizer.readToken(ChunkHeader);
|
|
33
33
|
debug(`Parsing chunk name=${chunkHeader.id} size=${chunkHeader.size}`);
|
|
34
34
|
switch (chunkHeader.id) {
|
|
35
|
-
case 'fmt ':
|
|
35
|
+
case 'fmt ': {
|
|
36
36
|
const formatChunk = await this.tokenizer.readToken(FormatChunk);
|
|
37
37
|
this.metadata.setFormat('numberOfChannels', formatChunk.channelNum);
|
|
38
38
|
this.metadata.setFormat('sampleRate', formatChunk.samplingFrequency);
|
|
@@ -42,6 +42,7 @@ export class DsfParser extends AbstractID3Parser {
|
|
|
42
42
|
const bitrate = formatChunk.bitsPerSample * formatChunk.samplingFrequency * formatChunk.channelNum;
|
|
43
43
|
this.metadata.setFormat('bitrate', bitrate);
|
|
44
44
|
return; // We got what we want, stop further processing of chunks
|
|
45
|
+
}
|
|
45
46
|
default:
|
|
46
47
|
this.tokenizer.ignore(Number(chunkHeader.size) - ChunkHeader.len);
|
|
47
48
|
break;
|
|
@@ -50,3 +51,4 @@ export class DsfParser extends AbstractID3Parser {
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
}
|
|
54
|
+
//# sourceMappingURL=DsfParser.js.map
|
package/lib/flac/FlacParser.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ITokenizer } from 'strtok3';
|
|
2
2
|
import { AbstractID3Parser } from '../id3v2/AbstractID3Parser.js';
|
|
3
|
-
import { INativeMetadataCollector } from '../common/MetadataCollector.js';
|
|
4
|
-
import { IOptions } from '../type.js';
|
|
5
|
-
import { ITokenParser } from '../ParserFactory.js';
|
|
3
|
+
import type { INativeMetadataCollector } from '../common/MetadataCollector.js';
|
|
4
|
+
import type { IOptions } from '../type.js';
|
|
5
|
+
import type { ITokenParser } from '../ParserFactory.js';
|
|
6
6
|
export declare class FlacParser extends AbstractID3Parser {
|
|
7
7
|
private vorbisParser;
|
|
8
8
|
private padding;
|
package/lib/flac/FlacParser.js
CHANGED
|
@@ -45,7 +45,7 @@ export class FlacParser extends AbstractID3Parser {
|
|
|
45
45
|
let blockHeader;
|
|
46
46
|
do {
|
|
47
47
|
// Read block header
|
|
48
|
-
blockHeader = await this.tokenizer.readToken(
|
|
48
|
+
blockHeader = await this.tokenizer.readToken(BlockHeader);
|
|
49
49
|
// Parse block data
|
|
50
50
|
await this.parseDataBlock(blockHeader);
|
|
51
51
|
} while (!blockHeader.lastBlock);
|
|
@@ -74,7 +74,7 @@ export class FlacParser extends AbstractID3Parser {
|
|
|
74
74
|
await this.parsePicture(blockHeader.length);
|
|
75
75
|
return;
|
|
76
76
|
default:
|
|
77
|
-
this.metadata.addWarning(
|
|
77
|
+
this.metadata.addWarning(`Unknown block type: ${blockHeader.type}`);
|
|
78
78
|
}
|
|
79
79
|
// Ignore data block
|
|
80
80
|
return this.tokenizer.ignore(blockHeader.length).then();
|
|
@@ -83,9 +83,9 @@ export class FlacParser extends AbstractID3Parser {
|
|
|
83
83
|
* Parse STREAMINFO
|
|
84
84
|
*/
|
|
85
85
|
async parseBlockStreamInfo(dataLen) {
|
|
86
|
-
if (dataLen !==
|
|
86
|
+
if (dataLen !== BlockStreamInfo.len)
|
|
87
87
|
throw new Error('Unexpected block-stream-info length');
|
|
88
|
-
const streamInfo = await this.tokenizer.readToken(
|
|
88
|
+
const streamInfo = await this.tokenizer.readToken(BlockStreamInfo);
|
|
89
89
|
this.metadata.setFormat('container', 'FLAC');
|
|
90
90
|
this.metadata.setFormat('codec', 'FLAC');
|
|
91
91
|
this.metadata.setFormat('lossless', true);
|
|
@@ -115,15 +115,11 @@ export class FlacParser extends AbstractID3Parser {
|
|
|
115
115
|
if (this.options.skipCovers) {
|
|
116
116
|
return this.tokenizer.ignore(dataLen);
|
|
117
117
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.vorbisParser.addTag('METADATA_BLOCK_PICTURE', picture);
|
|
121
|
-
}
|
|
118
|
+
const picture = await this.tokenizer.readToken(new VorbisPictureToken(dataLen));
|
|
119
|
+
this.vorbisParser.addTag('METADATA_BLOCK_PICTURE', picture);
|
|
122
120
|
}
|
|
123
121
|
}
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
Metadata.BlockHeader = {
|
|
122
|
+
const BlockHeader = {
|
|
127
123
|
len: 4,
|
|
128
124
|
get: (buf, off) => {
|
|
129
125
|
return {
|
|
@@ -137,7 +133,7 @@ Metadata.BlockHeader = {
|
|
|
137
133
|
* METADATA_BLOCK_DATA
|
|
138
134
|
* Ref: https://xiph.org/flac/format.html#metadata_block_streaminfo
|
|
139
135
|
*/
|
|
140
|
-
|
|
136
|
+
const BlockStreamInfo = {
|
|
141
137
|
len: 34,
|
|
142
138
|
get: (buf, off) => {
|
|
143
139
|
return {
|
|
@@ -171,3 +167,4 @@ Metadata.BlockStreamInfo = {
|
|
|
171
167
|
};
|
|
172
168
|
}
|
|
173
169
|
};
|
|
170
|
+
//# sourceMappingURL=FlacParser.js.map
|
package/lib/id3v1/ID3v1Parser.js
CHANGED
|
@@ -66,12 +66,13 @@ const Iid3v1Token = {
|
|
|
66
66
|
} : null;
|
|
67
67
|
}
|
|
68
68
|
};
|
|
69
|
-
class Id3v1StringType
|
|
69
|
+
class Id3v1StringType {
|
|
70
70
|
constructor(len) {
|
|
71
|
-
|
|
71
|
+
this.len = len;
|
|
72
|
+
this.stringType = new StringType(len, 'latin1');
|
|
72
73
|
}
|
|
73
74
|
get(buf, off) {
|
|
74
|
-
let value =
|
|
75
|
+
let value = this.stringType.get(buf, off);
|
|
75
76
|
value = util.trimRightNull(value);
|
|
76
77
|
value = value.trim();
|
|
77
78
|
return value.length > 0 ? value : undefined;
|
|
@@ -103,7 +104,8 @@ export class ID3v1Parser extends BasicParser {
|
|
|
103
104
|
const header = await this.tokenizer.readToken(Iid3v1Token, offset);
|
|
104
105
|
if (header) {
|
|
105
106
|
debug('ID3v1 header found at: pos=%s', this.tokenizer.fileInfo.size - Iid3v1Token.len);
|
|
106
|
-
|
|
107
|
+
const props = ['title', 'artist', 'album', 'comment', 'track', 'year'];
|
|
108
|
+
for (const id of props) {
|
|
107
109
|
if (header[id] && header[id] !== '')
|
|
108
110
|
await this.addTag(id, header[id]);
|
|
109
111
|
}
|
|
@@ -127,3 +129,4 @@ export async function hasID3v1Header(reader) {
|
|
|
127
129
|
}
|
|
128
130
|
return false;
|
|
129
131
|
}
|
|
132
|
+
//# sourceMappingURL=ID3v1Parser.js.map
|
|
@@ -22,7 +22,7 @@ export class AbstractID3Parser extends BasicParser {
|
|
|
22
22
|
}
|
|
23
23
|
catch (err) {
|
|
24
24
|
if (err instanceof EndOfStreamError) {
|
|
25
|
-
debug(
|
|
25
|
+
debug("End-of-stream");
|
|
26
26
|
}
|
|
27
27
|
else {
|
|
28
28
|
throw err;
|
|
@@ -54,3 +54,4 @@ export class AbstractID3Parser extends BasicParser {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
//# sourceMappingURL=AbstractID3Parser.js.map
|
|
@@ -1,5 +1,29 @@
|
|
|
1
|
-
import { ID3v2MajorVersion, ITextEncoding } from './ID3v2Token.js';
|
|
2
|
-
import { IWarningCollector } from '../common/MetadataCollector.js';
|
|
1
|
+
import { type ID3v2MajorVersion, type ITextEncoding } from './ID3v2Token.js';
|
|
2
|
+
import type { IWarningCollector } from '../common/MetadataCollector.js';
|
|
3
|
+
interface ICustomTag {
|
|
4
|
+
owner_identifier: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ICustomDataTag extends ICustomTag {
|
|
7
|
+
data: Uint8Array;
|
|
8
|
+
}
|
|
9
|
+
export interface IIdentifierTag extends ICustomTag {
|
|
10
|
+
identifier: Uint8Array;
|
|
11
|
+
}
|
|
12
|
+
export interface ITextTag {
|
|
13
|
+
description: string;
|
|
14
|
+
text: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface IPopularimeter {
|
|
17
|
+
email: string;
|
|
18
|
+
rating: number;
|
|
19
|
+
counter: number;
|
|
20
|
+
}
|
|
21
|
+
export interface IGeneralEncapsulatedObject {
|
|
22
|
+
type: string;
|
|
23
|
+
filename: string;
|
|
24
|
+
description: string;
|
|
25
|
+
data: Uint8Array;
|
|
26
|
+
}
|
|
3
27
|
export declare function parseGenre(origVal: string): string[];
|
|
4
28
|
export declare class FrameParser {
|
|
5
29
|
private major;
|
|
@@ -10,7 +34,7 @@ export declare class FrameParser {
|
|
|
10
34
|
* @param warningCollector - Used to collect decode issue
|
|
11
35
|
*/
|
|
12
36
|
constructor(major: ID3v2MajorVersion, warningCollector: IWarningCollector);
|
|
13
|
-
readData(uint8Array: Uint8Array, type: string, includeCovers: boolean):
|
|
37
|
+
readData(uint8Array: Uint8Array, type: string, includeCovers: boolean): unknown;
|
|
14
38
|
protected static readNullTerminatedString(uint8Array: Uint8Array, encoding: ITextEncoding): {
|
|
15
39
|
text: string;
|
|
16
40
|
len: number;
|
|
@@ -33,3 +57,4 @@ export declare class FrameParser {
|
|
|
33
57
|
private static readIdentifierAndData;
|
|
34
58
|
private static getNullTerminatorLength;
|
|
35
59
|
}
|
|
60
|
+
export {};
|
package/lib/id3v2/FrameParser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import initDebug from 'debug';
|
|
2
2
|
import * as Token from 'token-types';
|
|
3
3
|
import * as util from '../common/Util.js';
|
|
4
|
-
import { AttachedPictureType,
|
|
4
|
+
import { AttachedPictureType, SyncTextHeader, TextEncodingToken, TextHeader } from './ID3v2Token.js';
|
|
5
5
|
import { Genres } from '../id3v1/ID3v1Parser.js';
|
|
6
6
|
const debug = initDebug('music-metadata:id3v2:frame-parser');
|
|
7
7
|
const defaultEnc = 'latin1'; // latin1 == iso-8859-1;
|
|
@@ -39,9 +39,11 @@ export function parseGenre(origVal) {
|
|
|
39
39
|
}
|
|
40
40
|
if (word) {
|
|
41
41
|
if (genres.length === 0 && word.match(/^\d*$/)) {
|
|
42
|
-
word =
|
|
42
|
+
word = parseGenreCode(word);
|
|
43
|
+
}
|
|
44
|
+
if (word) {
|
|
45
|
+
genres.push(word);
|
|
43
46
|
}
|
|
44
|
-
genres.push(word);
|
|
45
47
|
}
|
|
46
48
|
return genres;
|
|
47
49
|
}
|
|
@@ -51,7 +53,7 @@ function parseGenreCode(code) {
|
|
|
51
53
|
if (code === 'CR')
|
|
52
54
|
return 'Cover';
|
|
53
55
|
if (code.match(/^\d*$/)) {
|
|
54
|
-
return Genres[code];
|
|
56
|
+
return Genres[Number.parseInt(code)];
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
export class FrameParser {
|
|
@@ -83,20 +85,23 @@ export class FrameParser {
|
|
|
83
85
|
case 'MVIN':
|
|
84
86
|
case 'MVNM':
|
|
85
87
|
case 'PCS':
|
|
86
|
-
case 'PCST':
|
|
88
|
+
case 'PCST': {
|
|
87
89
|
let text;
|
|
88
90
|
try {
|
|
89
91
|
text = util.decodeString(uint8Array.slice(1), encoding).replace(/\x00+$/, '');
|
|
90
92
|
}
|
|
91
93
|
catch (error) {
|
|
92
|
-
|
|
94
|
+
if (error instanceof Error) {
|
|
95
|
+
this.warningCollector.addWarning(`id3v2.${this.major} type=${type} header has invalid string value: ${error.message}`);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
93
99
|
}
|
|
94
100
|
switch (type) {
|
|
95
101
|
case 'TMCL': // Musician credits list
|
|
96
102
|
case 'TIPL': // Involved people list
|
|
97
103
|
case 'IPLS': // Involved people list
|
|
98
|
-
output = this.splitValue(type, text);
|
|
99
|
-
output = FrameParser.functionList(output);
|
|
104
|
+
output = FrameParser.functionList(this.splitValue(type, text));
|
|
100
105
|
break;
|
|
101
106
|
case 'TRK':
|
|
102
107
|
case 'TRCK':
|
|
@@ -126,13 +131,16 @@ export class FrameParser {
|
|
|
126
131
|
output = this.major >= 4 ? this.splitValue(type, text) : [text];
|
|
127
132
|
}
|
|
128
133
|
break;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
}
|
|
135
|
+
case 'TXXX': {
|
|
136
|
+
const idAndData = FrameParser.readIdentifierAndData(uint8Array, offset + 1, length, encoding);
|
|
137
|
+
const textTag = {
|
|
138
|
+
description: idAndData.id,
|
|
139
|
+
text: this.splitValue(type, util.decodeString(idAndData.data, encoding).replace(/\x00+$/, ''))
|
|
134
140
|
};
|
|
141
|
+
output = textTag;
|
|
135
142
|
break;
|
|
143
|
+
}
|
|
136
144
|
case 'PIC':
|
|
137
145
|
case 'APIC':
|
|
138
146
|
if (includeCovers) {
|
|
@@ -150,7 +158,7 @@ export class FrameParser {
|
|
|
150
158
|
offset = fzero + 1;
|
|
151
159
|
break;
|
|
152
160
|
default:
|
|
153
|
-
throw new Error(
|
|
161
|
+
throw new Error(`Warning: unexpected major versionIndex: ${this.major}`);
|
|
154
162
|
}
|
|
155
163
|
pic.format = FrameParser.fixPictureMimeType(pic.format);
|
|
156
164
|
pic.type = AttachedPictureType[uint8Array[offset]];
|
|
@@ -166,7 +174,7 @@ export class FrameParser {
|
|
|
166
174
|
case 'PCNT':
|
|
167
175
|
output = Token.UINT32_BE.get(uint8Array, 0);
|
|
168
176
|
break;
|
|
169
|
-
case 'SYLT':
|
|
177
|
+
case 'SYLT': {
|
|
170
178
|
const syltHeader = SyncTextHeader.get(uint8Array, 0);
|
|
171
179
|
offset += SyncTextHeader.len;
|
|
172
180
|
const result = {
|
|
@@ -195,10 +203,11 @@ export class FrameParser {
|
|
|
195
203
|
}
|
|
196
204
|
output = result;
|
|
197
205
|
break;
|
|
206
|
+
}
|
|
198
207
|
case 'ULT':
|
|
199
208
|
case 'USLT':
|
|
200
209
|
case 'COM':
|
|
201
|
-
case 'COMM':
|
|
210
|
+
case 'COMM': {
|
|
202
211
|
const textHeader = TextHeader.get(uint8Array, offset);
|
|
203
212
|
offset += TextHeader.len;
|
|
204
213
|
const descriptorStr = FrameParser.readNullTerminatedString(uint8Array.subarray(offset), textHeader.encoding);
|
|
@@ -211,15 +220,18 @@ export class FrameParser {
|
|
|
211
220
|
};
|
|
212
221
|
output = comment;
|
|
213
222
|
break;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
}
|
|
224
|
+
case 'UFID': {
|
|
225
|
+
const ufid = FrameParser.readIdentifierAndData(uint8Array, offset, length, defaultEnc);
|
|
226
|
+
output = { owner_identifier: ufid.id, identifier: ufid.data };
|
|
217
227
|
break;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
228
|
+
}
|
|
229
|
+
case 'PRIV': { // private frame
|
|
230
|
+
const priv = FrameParser.readIdentifierAndData(uint8Array, offset, length, defaultEnc);
|
|
231
|
+
output = { owner_identifier: priv.id, data: priv.data };
|
|
221
232
|
break;
|
|
222
|
-
|
|
233
|
+
}
|
|
234
|
+
case 'POPM': { // Popularimeter
|
|
223
235
|
fzero = util.findZero(uint8Array, offset, length, defaultEnc);
|
|
224
236
|
const email = util.decodeString(uint8Array.slice(offset, fzero), defaultEnc);
|
|
225
237
|
offset = fzero + 1;
|
|
@@ -230,6 +242,7 @@ export class FrameParser {
|
|
|
230
242
|
counter: dataLen >= 5 ? Token.UINT32_BE.get(uint8Array, offset + 1) : undefined
|
|
231
243
|
};
|
|
232
244
|
break;
|
|
245
|
+
}
|
|
233
246
|
case 'GEOB': { // General encapsulated object
|
|
234
247
|
fzero = util.findZero(uint8Array, offset + 1, length, encoding);
|
|
235
248
|
const mimeType = util.decodeString(uint8Array.slice(offset + 1, fzero), defaultEnc);
|
|
@@ -239,12 +252,13 @@ export class FrameParser {
|
|
|
239
252
|
offset = fzero + 1;
|
|
240
253
|
fzero = util.findZero(uint8Array, offset, length - offset, encoding);
|
|
241
254
|
const description = util.decodeString(uint8Array.slice(offset, fzero), defaultEnc);
|
|
242
|
-
|
|
255
|
+
const geob = {
|
|
243
256
|
type: mimeType,
|
|
244
257
|
filename,
|
|
245
258
|
description,
|
|
246
259
|
data: uint8Array.slice(offset + 1, length)
|
|
247
260
|
};
|
|
261
|
+
output = geob;
|
|
248
262
|
break;
|
|
249
263
|
}
|
|
250
264
|
// W-Frames:
|
|
@@ -257,6 +271,7 @@ export class FrameParser {
|
|
|
257
271
|
case 'WPAY':
|
|
258
272
|
case 'WPUB':
|
|
259
273
|
// Decode URL
|
|
274
|
+
fzero = util.findZero(uint8Array, offset + 1, length, encoding);
|
|
260
275
|
output = util.decodeString(uint8Array.slice(offset, fzero), defaultEnc);
|
|
261
276
|
break;
|
|
262
277
|
case 'WXXX': {
|
|
@@ -277,15 +292,21 @@ export class FrameParser {
|
|
|
277
292
|
break;
|
|
278
293
|
}
|
|
279
294
|
default:
|
|
280
|
-
debug(
|
|
295
|
+
debug(`Warning: unsupported id3v2-tag-type: ${type}`);
|
|
281
296
|
break;
|
|
282
297
|
}
|
|
283
298
|
return output;
|
|
284
299
|
}
|
|
285
300
|
static readNullTerminatedString(uint8Array, encoding) {
|
|
286
301
|
let offset = encoding.bom ? 2 : 0;
|
|
287
|
-
const
|
|
288
|
-
|
|
302
|
+
const zeroIndex = util.findZero(uint8Array, offset, uint8Array.length, encoding.encoding);
|
|
303
|
+
const txt = uint8Array.slice(offset, zeroIndex);
|
|
304
|
+
if (encoding.encoding === 'utf-16le') {
|
|
305
|
+
offset = zeroIndex + 2;
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
offset = zeroIndex + 1;
|
|
309
|
+
}
|
|
289
310
|
return {
|
|
290
311
|
text: util.decodeString(txt, encoding.encoding),
|
|
291
312
|
len: offset
|
|
@@ -309,7 +330,7 @@ export class FrameParser {
|
|
|
309
330
|
const res = {};
|
|
310
331
|
for (let i = 0; i + 1 < entries.length; i += 2) {
|
|
311
332
|
const names = entries[i + 1].split(',');
|
|
312
|
-
res[entries[i]] = res
|
|
333
|
+
res[entries[i]] = res[entries[i]] ? res[entries[i]].concat(names) : names;
|
|
313
334
|
}
|
|
314
335
|
return res;
|
|
315
336
|
}
|
|
@@ -349,3 +370,4 @@ export class FrameParser {
|
|
|
349
370
|
return enc === 'utf-16le' ? 2 : 1;
|
|
350
371
|
}
|
|
351
372
|
}
|
|
373
|
+
//# sourceMappingURL=FrameParser.js.map
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { CaseInsensitiveTagMap } from '../common/CaseInsensitiveTagMap.js';
|
|
2
2
|
import type { INativeMetadataCollector } from '../common/MetadataCollector.js';
|
|
3
3
|
import type { IRating, ITag } from '../type.js';
|
|
4
|
+
import type { IPopularimeter } from './FrameParser.js';
|
|
4
5
|
export declare class ID3v24TagMapper extends CaseInsensitiveTagMap {
|
|
5
|
-
static toRating(popm:
|
|
6
|
+
static toRating(popm: IPopularimeter): IRating;
|
|
6
7
|
constructor();
|
|
7
8
|
/**
|
|
8
9
|
* Handle post mapping exceptions / correction
|
|
@@ -156,25 +156,32 @@ export class ID3v24TagMapper extends CaseInsensitiveTagMap {
|
|
|
156
156
|
*/
|
|
157
157
|
postMap(tag, warnings) {
|
|
158
158
|
switch (tag.id) {
|
|
159
|
-
case 'UFID':
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
case 'UFID':
|
|
160
|
+
{
|
|
161
|
+
// decode MusicBrainz Recording Id
|
|
162
|
+
const idTag = tag.value;
|
|
163
|
+
if (idTag.owner_identifier === 'http://musicbrainz.org') {
|
|
164
|
+
tag.id += `:${idTag.owner_identifier}`;
|
|
165
|
+
tag.value = decodeString(idTag.identifier, 'latin1'); // latin1 == iso-8859-1
|
|
166
|
+
}
|
|
163
167
|
}
|
|
164
168
|
break;
|
|
165
169
|
case 'PRIV':
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
170
|
+
{
|
|
171
|
+
const customTag = tag.value;
|
|
172
|
+
switch (customTag.owner_identifier) {
|
|
173
|
+
// decode Windows Media Player
|
|
174
|
+
case 'AverageLevel':
|
|
175
|
+
case 'PeakValue':
|
|
176
|
+
tag.id += `:${customTag.owner_identifier}`;
|
|
177
|
+
tag.value = customTag.data.length === 4 ? UINT32_LE.get(customTag.data, 0) : null;
|
|
178
|
+
if (tag.value === null) {
|
|
179
|
+
warnings.addWarning('Failed to parse PRIV:PeakValue');
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
default:
|
|
183
|
+
warnings.addWarning(`Unknown PRIV owner-identifier: ${customTag.data}`);
|
|
184
|
+
}
|
|
178
185
|
}
|
|
179
186
|
break;
|
|
180
187
|
case 'POPM':
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ITokenizer } from 'strtok3';
|
|
2
|
-
import { IOptions } from '../type.js';
|
|
3
|
-
import { INativeMetadataCollector } from '../common/MetadataCollector.js';
|
|
2
|
+
import type { IOptions } from '../type.js';
|
|
3
|
+
import type { INativeMetadataCollector } from '../common/MetadataCollector.js';
|
|
4
4
|
export declare class ID3v2Parser {
|
|
5
5
|
static removeUnsyncBytes(buffer: Uint8Array): Uint8Array;
|
|
6
6
|
private static getFrameHeaderLength;
|