music-metadata 7.12.3 → 7.12.4
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/LICENSE.txt +9 -9
- package/README.md +434 -434
- package/lib/ParserFactory.d.ts +48 -48
- package/lib/ParserFactory.js +252 -252
- package/lib/aiff/AiffParser.d.ts +14 -14
- package/lib/aiff/AiffParser.js +84 -84
- package/lib/aiff/AiffToken.d.ts +22 -22
- package/lib/aiff/AiffToken.js +43 -43
- package/lib/apev2/APEv2Parser.d.ts +30 -30
- package/lib/apev2/APEv2Parser.js +164 -164
- package/lib/apev2/APEv2TagMapper.d.ts +4 -4
- package/lib/apev2/APEv2TagMapper.js +86 -86
- package/lib/apev2/APEv2Token.d.ts +100 -100
- package/lib/apev2/APEv2Token.js +126 -126
- package/lib/asf/AsfObject.d.ts +319 -319
- package/lib/asf/AsfObject.js +381 -381
- package/lib/asf/AsfParser.d.ts +17 -17
- package/lib/asf/AsfParser.js +135 -135
- package/lib/asf/AsfTagMapper.d.ts +7 -7
- package/lib/asf/AsfTagMapper.js +95 -95
- package/lib/asf/AsfUtil.d.ts +13 -13
- package/lib/asf/AsfUtil.js +38 -38
- package/lib/asf/GUID.d.ts +84 -84
- package/lib/asf/GUID.js +121 -121
- package/lib/common/BasicParser.d.ts +17 -17
- package/lib/common/BasicParser.js +18 -18
- package/lib/common/CaseInsensitiveTagMap.d.ts +10 -10
- package/lib/common/CaseInsensitiveTagMap.js +21 -21
- package/lib/common/CombinedTagMapper.d.ts +19 -19
- package/lib/common/CombinedTagMapper.js +51 -51
- package/lib/common/FourCC.d.ts +6 -6
- package/lib/common/FourCC.js +28 -28
- package/lib/common/GenericTagMapper.d.ts +51 -51
- package/lib/common/GenericTagMapper.js +55 -55
- package/lib/common/GenericTagTypes.d.ts +33 -33
- package/lib/common/GenericTagTypes.js +131 -131
- package/lib/common/MetadataCollector.d.ts +76 -76
- package/lib/common/MetadataCollector.js +275 -275
- package/lib/common/RandomFileReader.d.ts +22 -22
- package/lib/common/RandomFileReader.js +34 -34
- package/lib/common/RandomUint8ArrayReader.d.ts +18 -18
- package/lib/common/RandomUint8ArrayReader.js +25 -25
- package/lib/common/Util.d.ts +57 -57
- package/lib/common/Util.js +157 -157
- package/lib/core.d.ts +48 -48
- package/lib/core.js +90 -90
- package/lib/dsdiff/DsdiffParser.d.ts +14 -14
- package/lib/dsdiff/DsdiffParser.js +143 -143
- package/lib/dsdiff/DsdiffToken.d.ts +9 -9
- package/lib/dsdiff/DsdiffToken.js +21 -21
- package/lib/dsf/DsfChunk.d.ts +86 -86
- package/lib/dsf/DsfChunk.js +54 -54
- package/lib/dsf/DsfParser.d.ts +9 -9
- package/lib/dsf/DsfParser.js +56 -56
- package/lib/flac/FlacParser.d.ts +28 -28
- package/lib/flac/FlacParser.js +175 -175
- package/lib/id3v1/ID3v1Parser.d.ts +13 -13
- package/lib/id3v1/ID3v1Parser.js +134 -134
- package/lib/id3v1/ID3v1TagMap.d.ts +4 -4
- package/lib/id3v1/ID3v1TagMap.js +22 -22
- package/lib/id3v2/AbstractID3Parser.d.ts +17 -17
- package/lib/id3v2/AbstractID3Parser.js +60 -60
- package/lib/id3v2/FrameParser.d.ts +31 -31
- package/lib/id3v2/FrameParser.js +329 -329
- package/lib/id3v2/ID3v22TagMapper.d.ts +9 -9
- package/lib/id3v2/ID3v22TagMapper.js +55 -55
- package/lib/id3v2/ID3v24TagMapper.d.ts +14 -14
- package/lib/id3v2/ID3v24TagMapper.js +193 -193
- package/lib/id3v2/ID3v2Parser.d.ts +28 -28
- package/lib/id3v2/ID3v2Parser.js +182 -182
- package/lib/id3v2/ID3v2Token.d.ts +73 -73
- package/lib/id3v2/ID3v2Token.js +106 -106
- package/lib/iff/index.d.ts +33 -33
- package/lib/iff/index.js +19 -19
- package/lib/index.d.ts +45 -45
- package/lib/index.js +74 -74
- package/lib/lyrics3/Lyrics3.d.ts +3 -3
- package/lib/lyrics3/Lyrics3.js +17 -17
- package/lib/matroska/MatroskaDtd.d.ts +8 -8
- package/lib/matroska/MatroskaDtd.js +279 -279
- package/lib/matroska/MatroskaParser.d.ts +37 -37
- package/lib/matroska/MatroskaParser.js +235 -235
- package/lib/matroska/MatroskaTagMapper.d.ts +4 -4
- package/lib/matroska/MatroskaTagMapper.js +35 -35
- package/lib/matroska/types.d.ts +175 -175
- package/lib/matroska/types.js +32 -32
- package/lib/mp4/Atom.d.ts +16 -16
- package/lib/mp4/Atom.js +70 -70
- package/lib/mp4/AtomToken.d.ts +395 -395
- package/lib/mp4/AtomToken.js +406 -406
- package/lib/mp4/MP4Parser.d.ts +30 -30
- package/lib/mp4/MP4Parser.js +511 -511
- package/lib/mp4/MP4TagMapper.d.ts +5 -5
- package/lib/mp4/MP4TagMapper.js +115 -115
- package/lib/mpeg/ExtendedLameHeader.d.ts +27 -27
- package/lib/mpeg/ExtendedLameHeader.js +31 -31
- package/lib/mpeg/MpegParser.d.ts +49 -49
- package/lib/mpeg/MpegParser.js +524 -524
- package/lib/mpeg/ReplayGainDataFormat.d.ts +55 -55
- package/lib/mpeg/ReplayGainDataFormat.js +69 -69
- package/lib/mpeg/XingTag.d.ts +45 -45
- package/lib/mpeg/XingTag.js +69 -69
- package/lib/musepack/index.d.ts +5 -5
- package/lib/musepack/index.js +32 -32
- package/lib/musepack/sv7/BitReader.d.ts +13 -13
- package/lib/musepack/sv7/BitReader.js +54 -54
- package/lib/musepack/sv7/MpcSv7Parser.d.ts +8 -8
- package/lib/musepack/sv7/MpcSv7Parser.js +46 -46
- package/lib/musepack/sv7/StreamVersion7.d.ts +28 -28
- package/lib/musepack/sv7/StreamVersion7.js +41 -41
- package/lib/musepack/sv8/MpcSv8Parser.d.ts +6 -6
- package/lib/musepack/sv8/MpcSv8Parser.js +55 -55
- package/lib/musepack/sv8/StreamVersion8.d.ts +40 -40
- package/lib/musepack/sv8/StreamVersion8.js +80 -80
- package/lib/ogg/Ogg.d.ts +72 -72
- package/lib/ogg/Ogg.js +2 -2
- package/lib/ogg/OggParser.d.ts +23 -23
- package/lib/ogg/OggParser.js +126 -126
- package/lib/ogg/opus/Opus.d.ts +48 -48
- package/lib/ogg/opus/Opus.js +28 -28
- package/lib/ogg/opus/OpusParser.d.ts +25 -25
- package/lib/ogg/opus/OpusParser.js +56 -56
- package/lib/ogg/speex/Speex.d.ts +36 -36
- package/lib/ogg/speex/Speex.js +31 -31
- package/lib/ogg/speex/SpeexParser.d.ts +22 -22
- package/lib/ogg/speex/SpeexParser.js +35 -35
- package/lib/ogg/theora/Theora.d.ts +20 -20
- package/lib/ogg/theora/Theora.js +23 -23
- package/lib/ogg/theora/TheoraParser.d.ts +28 -28
- package/lib/ogg/theora/TheoraParser.js +44 -44
- package/lib/ogg/vorbis/Vorbis.d.ts +69 -69
- package/lib/ogg/vorbis/Vorbis.js +78 -78
- package/lib/ogg/vorbis/VorbisDecoder.d.ts +12 -12
- package/lib/ogg/vorbis/VorbisDecoder.js +32 -32
- package/lib/ogg/vorbis/VorbisParser.d.ts +36 -36
- package/lib/ogg/vorbis/VorbisParser.js +128 -128
- package/lib/ogg/vorbis/VorbisTagMapper.d.ts +7 -7
- package/lib/ogg/vorbis/VorbisTagMapper.js +132 -132
- package/lib/riff/RiffChunk.d.ts +16 -16
- package/lib/riff/RiffChunk.js +32 -32
- package/lib/riff/RiffInfoTagMap.d.ts +10 -10
- package/lib/riff/RiffInfoTagMap.js +37 -37
- package/lib/type.d.ts +592 -592
- package/lib/type.js +5 -5
- package/lib/wav/BwfChunk.d.ts +17 -17
- package/lib/wav/BwfChunk.js +29 -28
- package/lib/wav/WaveChunk.d.ts +64 -64
- package/lib/wav/WaveChunk.js +65 -65
- package/lib/wav/WaveParser.d.ts +24 -24
- package/lib/wav/WaveParser.js +158 -156
- package/lib/wavpack/WavPackParser.d.ts +14 -14
- package/lib/wavpack/WavPackParser.js +99 -99
- package/lib/wavpack/WavPackToken.d.ts +64 -64
- package/lib/wavpack/WavPackToken.js +76 -76
- package/package.json +150 -150
package/lib/apev2/APEv2Parser.js
CHANGED
|
@@ -1,164 +1,164 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.APEv2Parser = void 0;
|
|
4
|
-
const debug_1 = require("debug");
|
|
5
|
-
const strtok3 = require("strtok3/lib/core");
|
|
6
|
-
const token_types_1 = require("token-types");
|
|
7
|
-
const util = require("../common/Util");
|
|
8
|
-
const BasicParser_1 = require("../common/BasicParser");
|
|
9
|
-
const APEv2Token_1 = require("./APEv2Token");
|
|
10
|
-
const debug = (0, debug_1.default)('music-metadata:parser:APEv2');
|
|
11
|
-
const tagFormat = 'APEv2';
|
|
12
|
-
const preamble = 'APETAGEX';
|
|
13
|
-
class APEv2Parser extends BasicParser_1.BasicParser {
|
|
14
|
-
constructor() {
|
|
15
|
-
super(...arguments);
|
|
16
|
-
this.ape = {};
|
|
17
|
-
}
|
|
18
|
-
static tryParseApeHeader(metadata, tokenizer, options) {
|
|
19
|
-
const apeParser = new APEv2Parser();
|
|
20
|
-
apeParser.init(metadata, tokenizer, options);
|
|
21
|
-
return apeParser.tryParseApeHeader();
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Calculate the media file duration
|
|
25
|
-
* @param ah ApeHeader
|
|
26
|
-
* @return {number} duration in seconds
|
|
27
|
-
*/
|
|
28
|
-
static calculateDuration(ah) {
|
|
29
|
-
let duration = ah.totalFrames > 1 ? ah.blocksPerFrame * (ah.totalFrames - 1) : 0;
|
|
30
|
-
duration += ah.finalFrameBlocks;
|
|
31
|
-
return duration / ah.sampleRate;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Calculates the APEv1 / APEv2 first field offset
|
|
35
|
-
* @param reader
|
|
36
|
-
* @param offset
|
|
37
|
-
*/
|
|
38
|
-
static async findApeFooterOffset(reader, offset) {
|
|
39
|
-
// Search for APE footer header at the end of the file
|
|
40
|
-
const apeBuf = Buffer.alloc(APEv2Token_1.TagFooter.len);
|
|
41
|
-
await reader.randomRead(apeBuf, 0, APEv2Token_1.TagFooter.len, offset - APEv2Token_1.TagFooter.len);
|
|
42
|
-
const tagFooter = APEv2Token_1.TagFooter.get(apeBuf, 0);
|
|
43
|
-
if (tagFooter.ID === 'APETAGEX') {
|
|
44
|
-
debug(`APE footer header at offset=${offset}`);
|
|
45
|
-
return { footer: tagFooter, offset: offset - tagFooter.size };
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
static parseTagFooter(metadata, buffer, options) {
|
|
49
|
-
const footer = APEv2Token_1.TagFooter.get(buffer, buffer.length - APEv2Token_1.TagFooter.len);
|
|
50
|
-
if (footer.ID !== preamble)
|
|
51
|
-
throw new Error('Unexpected APEv2 Footer ID preamble value.');
|
|
52
|
-
strtok3.fromBuffer(buffer);
|
|
53
|
-
const apeParser = new APEv2Parser();
|
|
54
|
-
apeParser.init(metadata, strtok3.fromBuffer(buffer), options);
|
|
55
|
-
return apeParser.parseTags(footer);
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Parse APEv1 / APEv2 header if header signature found
|
|
59
|
-
*/
|
|
60
|
-
async tryParseApeHeader() {
|
|
61
|
-
if (this.tokenizer.fileInfo.size && this.tokenizer.fileInfo.size - this.tokenizer.position < APEv2Token_1.TagFooter.len) {
|
|
62
|
-
debug(`No APEv2 header found, end-of-file reached`);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
const footer = await this.tokenizer.peekToken(APEv2Token_1.TagFooter);
|
|
66
|
-
if (footer.ID === preamble) {
|
|
67
|
-
await this.tokenizer.ignore(APEv2Token_1.TagFooter.len);
|
|
68
|
-
return this.parseTags(footer);
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
debug(`APEv2 header not found at offset=${this.tokenizer.position}`);
|
|
72
|
-
if (this.tokenizer.fileInfo.size) {
|
|
73
|
-
// Try to read the APEv2 header using just the footer-header
|
|
74
|
-
const remaining = this.tokenizer.fileInfo.size - this.tokenizer.position; // ToDo: take ID3v1 into account
|
|
75
|
-
const buffer = Buffer.alloc(remaining);
|
|
76
|
-
await this.tokenizer.readBuffer(buffer);
|
|
77
|
-
return APEv2Parser.parseTagFooter(this.metadata, buffer, this.options);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async parse() {
|
|
82
|
-
const descriptor = await this.tokenizer.readToken(APEv2Token_1.DescriptorParser);
|
|
83
|
-
if (descriptor.ID !== 'MAC ')
|
|
84
|
-
throw new Error('Unexpected descriptor ID');
|
|
85
|
-
this.ape.descriptor = descriptor;
|
|
86
|
-
const lenExp = descriptor.descriptorBytes - APEv2Token_1.DescriptorParser.len;
|
|
87
|
-
const header = await (lenExp > 0 ? this.parseDescriptorExpansion(lenExp) : this.parseHeader());
|
|
88
|
-
await this.tokenizer.ignore(header.forwardBytes);
|
|
89
|
-
return this.tryParseApeHeader();
|
|
90
|
-
}
|
|
91
|
-
async parseTags(footer) {
|
|
92
|
-
const keyBuffer = Buffer.alloc(256); // maximum tag key length
|
|
93
|
-
let bytesRemaining = footer.size - APEv2Token_1.TagFooter.len;
|
|
94
|
-
debug(`Parse APE tags at offset=${this.tokenizer.position}, size=${bytesRemaining}`);
|
|
95
|
-
for (let i = 0; i < footer.fields; i++) {
|
|
96
|
-
if (bytesRemaining < APEv2Token_1.TagItemHeader.len) {
|
|
97
|
-
this.metadata.addWarning(`APEv2 Tag-header: ${footer.fields - i} items remaining, but no more tag data to read.`);
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
// Only APEv2 tag has tag item headers
|
|
101
|
-
const tagItemHeader = await this.tokenizer.readToken(APEv2Token_1.TagItemHeader);
|
|
102
|
-
bytesRemaining -= APEv2Token_1.TagItemHeader.len + tagItemHeader.size;
|
|
103
|
-
await this.tokenizer.peekBuffer(keyBuffer, { length: Math.min(keyBuffer.length, bytesRemaining) });
|
|
104
|
-
let zero = util.findZero(keyBuffer, 0, keyBuffer.length);
|
|
105
|
-
const key = await this.tokenizer.readToken(new token_types_1.StringType(zero, 'ascii'));
|
|
106
|
-
await this.tokenizer.ignore(1);
|
|
107
|
-
bytesRemaining -= key.length + 1;
|
|
108
|
-
switch (tagItemHeader.flags.dataType) {
|
|
109
|
-
case APEv2Token_1.DataType.text_utf8: { // utf-8 text-string
|
|
110
|
-
const value = await this.tokenizer.readToken(new token_types_1.StringType(tagItemHeader.size, 'utf8'));
|
|
111
|
-
const values = value.split(/\x00/g);
|
|
112
|
-
for (const val of values) {
|
|
113
|
-
this.metadata.addTag(tagFormat, key, val);
|
|
114
|
-
}
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
case APEv2Token_1.DataType.binary: // binary (probably artwork)
|
|
118
|
-
if (this.options.skipCovers) {
|
|
119
|
-
await this.tokenizer.ignore(tagItemHeader.size);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
const picData = Buffer.alloc(tagItemHeader.size);
|
|
123
|
-
await this.tokenizer.readBuffer(picData);
|
|
124
|
-
zero = util.findZero(picData, 0, picData.length);
|
|
125
|
-
const description = picData.toString('utf8', 0, zero);
|
|
126
|
-
const data = Buffer.from(picData.slice(zero + 1));
|
|
127
|
-
this.metadata.addTag(tagFormat, key, {
|
|
128
|
-
description,
|
|
129
|
-
data
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
break;
|
|
133
|
-
case APEv2Token_1.DataType.external_info:
|
|
134
|
-
debug(`Ignore external info ${key}`);
|
|
135
|
-
await this.tokenizer.ignore(tagItemHeader.size);
|
|
136
|
-
break;
|
|
137
|
-
case APEv2Token_1.DataType.reserved:
|
|
138
|
-
debug(`Ignore external info ${key}`);
|
|
139
|
-
this.metadata.addWarning(`APEv2 header declares a reserved datatype for "${key}"`);
|
|
140
|
-
await this.tokenizer.ignore(tagItemHeader.size);
|
|
141
|
-
break;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
async parseDescriptorExpansion(lenExp) {
|
|
146
|
-
await this.tokenizer.ignore(lenExp);
|
|
147
|
-
return this.parseHeader();
|
|
148
|
-
}
|
|
149
|
-
async parseHeader() {
|
|
150
|
-
const header = await this.tokenizer.readToken(APEv2Token_1.Header);
|
|
151
|
-
// ToDo before
|
|
152
|
-
this.metadata.setFormat('lossless', true);
|
|
153
|
-
this.metadata.setFormat('container', 'Monkey\'s Audio');
|
|
154
|
-
this.metadata.setFormat('bitsPerSample', header.bitsPerSample);
|
|
155
|
-
this.metadata.setFormat('sampleRate', header.sampleRate);
|
|
156
|
-
this.metadata.setFormat('numberOfChannels', header.channel);
|
|
157
|
-
this.metadata.setFormat('duration', APEv2Parser.calculateDuration(header));
|
|
158
|
-
return {
|
|
159
|
-
forwardBytes: this.ape.descriptor.seekTableBytes + this.ape.descriptor.headerDataBytes +
|
|
160
|
-
this.ape.descriptor.apeFrameDataBytes + this.ape.descriptor.terminatingDataBytes
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
exports.APEv2Parser = APEv2Parser;
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APEv2Parser = void 0;
|
|
4
|
+
const debug_1 = require("debug");
|
|
5
|
+
const strtok3 = require("strtok3/lib/core");
|
|
6
|
+
const token_types_1 = require("token-types");
|
|
7
|
+
const util = require("../common/Util");
|
|
8
|
+
const BasicParser_1 = require("../common/BasicParser");
|
|
9
|
+
const APEv2Token_1 = require("./APEv2Token");
|
|
10
|
+
const debug = (0, debug_1.default)('music-metadata:parser:APEv2');
|
|
11
|
+
const tagFormat = 'APEv2';
|
|
12
|
+
const preamble = 'APETAGEX';
|
|
13
|
+
class APEv2Parser extends BasicParser_1.BasicParser {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(...arguments);
|
|
16
|
+
this.ape = {};
|
|
17
|
+
}
|
|
18
|
+
static tryParseApeHeader(metadata, tokenizer, options) {
|
|
19
|
+
const apeParser = new APEv2Parser();
|
|
20
|
+
apeParser.init(metadata, tokenizer, options);
|
|
21
|
+
return apeParser.tryParseApeHeader();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Calculate the media file duration
|
|
25
|
+
* @param ah ApeHeader
|
|
26
|
+
* @return {number} duration in seconds
|
|
27
|
+
*/
|
|
28
|
+
static calculateDuration(ah) {
|
|
29
|
+
let duration = ah.totalFrames > 1 ? ah.blocksPerFrame * (ah.totalFrames - 1) : 0;
|
|
30
|
+
duration += ah.finalFrameBlocks;
|
|
31
|
+
return duration / ah.sampleRate;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Calculates the APEv1 / APEv2 first field offset
|
|
35
|
+
* @param reader
|
|
36
|
+
* @param offset
|
|
37
|
+
*/
|
|
38
|
+
static async findApeFooterOffset(reader, offset) {
|
|
39
|
+
// Search for APE footer header at the end of the file
|
|
40
|
+
const apeBuf = Buffer.alloc(APEv2Token_1.TagFooter.len);
|
|
41
|
+
await reader.randomRead(apeBuf, 0, APEv2Token_1.TagFooter.len, offset - APEv2Token_1.TagFooter.len);
|
|
42
|
+
const tagFooter = APEv2Token_1.TagFooter.get(apeBuf, 0);
|
|
43
|
+
if (tagFooter.ID === 'APETAGEX') {
|
|
44
|
+
debug(`APE footer header at offset=${offset}`);
|
|
45
|
+
return { footer: tagFooter, offset: offset - tagFooter.size };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
static parseTagFooter(metadata, buffer, options) {
|
|
49
|
+
const footer = APEv2Token_1.TagFooter.get(buffer, buffer.length - APEv2Token_1.TagFooter.len);
|
|
50
|
+
if (footer.ID !== preamble)
|
|
51
|
+
throw new Error('Unexpected APEv2 Footer ID preamble value.');
|
|
52
|
+
strtok3.fromBuffer(buffer);
|
|
53
|
+
const apeParser = new APEv2Parser();
|
|
54
|
+
apeParser.init(metadata, strtok3.fromBuffer(buffer), options);
|
|
55
|
+
return apeParser.parseTags(footer);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse APEv1 / APEv2 header if header signature found
|
|
59
|
+
*/
|
|
60
|
+
async tryParseApeHeader() {
|
|
61
|
+
if (this.tokenizer.fileInfo.size && this.tokenizer.fileInfo.size - this.tokenizer.position < APEv2Token_1.TagFooter.len) {
|
|
62
|
+
debug(`No APEv2 header found, end-of-file reached`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const footer = await this.tokenizer.peekToken(APEv2Token_1.TagFooter);
|
|
66
|
+
if (footer.ID === preamble) {
|
|
67
|
+
await this.tokenizer.ignore(APEv2Token_1.TagFooter.len);
|
|
68
|
+
return this.parseTags(footer);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
debug(`APEv2 header not found at offset=${this.tokenizer.position}`);
|
|
72
|
+
if (this.tokenizer.fileInfo.size) {
|
|
73
|
+
// Try to read the APEv2 header using just the footer-header
|
|
74
|
+
const remaining = this.tokenizer.fileInfo.size - this.tokenizer.position; // ToDo: take ID3v1 into account
|
|
75
|
+
const buffer = Buffer.alloc(remaining);
|
|
76
|
+
await this.tokenizer.readBuffer(buffer);
|
|
77
|
+
return APEv2Parser.parseTagFooter(this.metadata, buffer, this.options);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async parse() {
|
|
82
|
+
const descriptor = await this.tokenizer.readToken(APEv2Token_1.DescriptorParser);
|
|
83
|
+
if (descriptor.ID !== 'MAC ')
|
|
84
|
+
throw new Error('Unexpected descriptor ID');
|
|
85
|
+
this.ape.descriptor = descriptor;
|
|
86
|
+
const lenExp = descriptor.descriptorBytes - APEv2Token_1.DescriptorParser.len;
|
|
87
|
+
const header = await (lenExp > 0 ? this.parseDescriptorExpansion(lenExp) : this.parseHeader());
|
|
88
|
+
await this.tokenizer.ignore(header.forwardBytes);
|
|
89
|
+
return this.tryParseApeHeader();
|
|
90
|
+
}
|
|
91
|
+
async parseTags(footer) {
|
|
92
|
+
const keyBuffer = Buffer.alloc(256); // maximum tag key length
|
|
93
|
+
let bytesRemaining = footer.size - APEv2Token_1.TagFooter.len;
|
|
94
|
+
debug(`Parse APE tags at offset=${this.tokenizer.position}, size=${bytesRemaining}`);
|
|
95
|
+
for (let i = 0; i < footer.fields; i++) {
|
|
96
|
+
if (bytesRemaining < APEv2Token_1.TagItemHeader.len) {
|
|
97
|
+
this.metadata.addWarning(`APEv2 Tag-header: ${footer.fields - i} items remaining, but no more tag data to read.`);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
// Only APEv2 tag has tag item headers
|
|
101
|
+
const tagItemHeader = await this.tokenizer.readToken(APEv2Token_1.TagItemHeader);
|
|
102
|
+
bytesRemaining -= APEv2Token_1.TagItemHeader.len + tagItemHeader.size;
|
|
103
|
+
await this.tokenizer.peekBuffer(keyBuffer, { length: Math.min(keyBuffer.length, bytesRemaining) });
|
|
104
|
+
let zero = util.findZero(keyBuffer, 0, keyBuffer.length);
|
|
105
|
+
const key = await this.tokenizer.readToken(new token_types_1.StringType(zero, 'ascii'));
|
|
106
|
+
await this.tokenizer.ignore(1);
|
|
107
|
+
bytesRemaining -= key.length + 1;
|
|
108
|
+
switch (tagItemHeader.flags.dataType) {
|
|
109
|
+
case APEv2Token_1.DataType.text_utf8: { // utf-8 text-string
|
|
110
|
+
const value = await this.tokenizer.readToken(new token_types_1.StringType(tagItemHeader.size, 'utf8'));
|
|
111
|
+
const values = value.split(/\x00/g);
|
|
112
|
+
for (const val of values) {
|
|
113
|
+
this.metadata.addTag(tagFormat, key, val);
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case APEv2Token_1.DataType.binary: // binary (probably artwork)
|
|
118
|
+
if (this.options.skipCovers) {
|
|
119
|
+
await this.tokenizer.ignore(tagItemHeader.size);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const picData = Buffer.alloc(tagItemHeader.size);
|
|
123
|
+
await this.tokenizer.readBuffer(picData);
|
|
124
|
+
zero = util.findZero(picData, 0, picData.length);
|
|
125
|
+
const description = picData.toString('utf8', 0, zero);
|
|
126
|
+
const data = Buffer.from(picData.slice(zero + 1));
|
|
127
|
+
this.metadata.addTag(tagFormat, key, {
|
|
128
|
+
description,
|
|
129
|
+
data
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
case APEv2Token_1.DataType.external_info:
|
|
134
|
+
debug(`Ignore external info ${key}`);
|
|
135
|
+
await this.tokenizer.ignore(tagItemHeader.size);
|
|
136
|
+
break;
|
|
137
|
+
case APEv2Token_1.DataType.reserved:
|
|
138
|
+
debug(`Ignore external info ${key}`);
|
|
139
|
+
this.metadata.addWarning(`APEv2 header declares a reserved datatype for "${key}"`);
|
|
140
|
+
await this.tokenizer.ignore(tagItemHeader.size);
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async parseDescriptorExpansion(lenExp) {
|
|
146
|
+
await this.tokenizer.ignore(lenExp);
|
|
147
|
+
return this.parseHeader();
|
|
148
|
+
}
|
|
149
|
+
async parseHeader() {
|
|
150
|
+
const header = await this.tokenizer.readToken(APEv2Token_1.Header);
|
|
151
|
+
// ToDo before
|
|
152
|
+
this.metadata.setFormat('lossless', true);
|
|
153
|
+
this.metadata.setFormat('container', 'Monkey\'s Audio');
|
|
154
|
+
this.metadata.setFormat('bitsPerSample', header.bitsPerSample);
|
|
155
|
+
this.metadata.setFormat('sampleRate', header.sampleRate);
|
|
156
|
+
this.metadata.setFormat('numberOfChannels', header.channel);
|
|
157
|
+
this.metadata.setFormat('duration', APEv2Parser.calculateDuration(header));
|
|
158
|
+
return {
|
|
159
|
+
forwardBytes: this.ape.descriptor.seekTableBytes + this.ape.descriptor.headerDataBytes +
|
|
160
|
+
this.ape.descriptor.apeFrameDataBytes + this.ape.descriptor.terminatingDataBytes
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exports.APEv2Parser = APEv2Parser;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CaseInsensitiveTagMap } from '../common/CaseInsensitiveTagMap';
|
|
2
|
-
export declare class APEv2TagMapper extends CaseInsensitiveTagMap {
|
|
3
|
-
constructor();
|
|
4
|
-
}
|
|
1
|
+
import { CaseInsensitiveTagMap } from '../common/CaseInsensitiveTagMap';
|
|
2
|
+
export declare class APEv2TagMapper extends CaseInsensitiveTagMap {
|
|
3
|
+
constructor();
|
|
4
|
+
}
|
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.APEv2TagMapper = void 0;
|
|
4
|
-
const CaseInsensitiveTagMap_1 = require("../common/CaseInsensitiveTagMap");
|
|
5
|
-
/**
|
|
6
|
-
* ID3v2.2 tag mappings
|
|
7
|
-
*/
|
|
8
|
-
const apev2TagMap = {
|
|
9
|
-
Title: 'title',
|
|
10
|
-
Artist: 'artist',
|
|
11
|
-
Artists: 'artists',
|
|
12
|
-
'Album Artist': 'albumartist',
|
|
13
|
-
Album: 'album',
|
|
14
|
-
Year: 'date',
|
|
15
|
-
Originalyear: 'originalyear',
|
|
16
|
-
Originaldate: 'originaldate',
|
|
17
|
-
Comment: 'comment',
|
|
18
|
-
Track: 'track',
|
|
19
|
-
Disc: 'disk',
|
|
20
|
-
DISCNUMBER: 'disk',
|
|
21
|
-
Genre: 'genre',
|
|
22
|
-
'Cover Art (Front)': 'picture',
|
|
23
|
-
'Cover Art (Back)': 'picture',
|
|
24
|
-
Composer: 'composer',
|
|
25
|
-
Lyrics: 'lyrics',
|
|
26
|
-
ALBUMSORT: 'albumsort',
|
|
27
|
-
TITLESORT: 'titlesort',
|
|
28
|
-
WORK: 'work',
|
|
29
|
-
ARTISTSORT: 'artistsort',
|
|
30
|
-
ALBUMARTISTSORT: 'albumartistsort',
|
|
31
|
-
COMPOSERSORT: 'composersort',
|
|
32
|
-
Lyricist: 'lyricist',
|
|
33
|
-
Writer: 'writer',
|
|
34
|
-
Conductor: 'conductor',
|
|
35
|
-
// 'Performer=artist (instrument)': 'performer:instrument',
|
|
36
|
-
MixArtist: 'remixer',
|
|
37
|
-
Arranger: 'arranger',
|
|
38
|
-
Engineer: 'engineer',
|
|
39
|
-
Producer: 'producer',
|
|
40
|
-
DJMixer: 'djmixer',
|
|
41
|
-
Mixer: 'mixer',
|
|
42
|
-
Label: 'label',
|
|
43
|
-
Grouping: 'grouping',
|
|
44
|
-
Subtitle: 'subtitle',
|
|
45
|
-
DiscSubtitle: 'discsubtitle',
|
|
46
|
-
Compilation: 'compilation',
|
|
47
|
-
BPM: 'bpm',
|
|
48
|
-
Mood: 'mood',
|
|
49
|
-
Media: 'media',
|
|
50
|
-
CatalogNumber: 'catalognumber',
|
|
51
|
-
MUSICBRAINZ_ALBUMSTATUS: 'releasestatus',
|
|
52
|
-
MUSICBRAINZ_ALBUMTYPE: 'releasetype',
|
|
53
|
-
RELEASECOUNTRY: 'releasecountry',
|
|
54
|
-
Script: 'script',
|
|
55
|
-
Language: 'language',
|
|
56
|
-
Copyright: 'copyright',
|
|
57
|
-
LICENSE: 'license',
|
|
58
|
-
EncodedBy: 'encodedby',
|
|
59
|
-
EncoderSettings: 'encodersettings',
|
|
60
|
-
Barcode: 'barcode',
|
|
61
|
-
ISRC: 'isrc',
|
|
62
|
-
ASIN: 'asin',
|
|
63
|
-
musicbrainz_trackid: 'musicbrainz_recordingid',
|
|
64
|
-
musicbrainz_releasetrackid: 'musicbrainz_trackid',
|
|
65
|
-
MUSICBRAINZ_ALBUMID: 'musicbrainz_albumid',
|
|
66
|
-
MUSICBRAINZ_ARTISTID: 'musicbrainz_artistid',
|
|
67
|
-
MUSICBRAINZ_ALBUMARTISTID: 'musicbrainz_albumartistid',
|
|
68
|
-
MUSICBRAINZ_RELEASEGROUPID: 'musicbrainz_releasegroupid',
|
|
69
|
-
MUSICBRAINZ_WORKID: 'musicbrainz_workid',
|
|
70
|
-
MUSICBRAINZ_TRMID: 'musicbrainz_trmid',
|
|
71
|
-
MUSICBRAINZ_DISCID: 'musicbrainz_discid',
|
|
72
|
-
Acoustid_Id: 'acoustid_id',
|
|
73
|
-
ACOUSTID_FINGERPRINT: 'acoustid_fingerprint',
|
|
74
|
-
MUSICIP_PUID: 'musicip_puid',
|
|
75
|
-
Weblink: 'website',
|
|
76
|
-
REPLAYGAIN_TRACK_GAIN: 'replaygain_track_gain',
|
|
77
|
-
REPLAYGAIN_TRACK_PEAK: 'replaygain_track_peak',
|
|
78
|
-
MP3GAIN_MINMAX: 'replaygain_track_minmax',
|
|
79
|
-
MP3GAIN_UNDO: 'replaygain_undo'
|
|
80
|
-
};
|
|
81
|
-
class APEv2TagMapper extends CaseInsensitiveTagMap_1.CaseInsensitiveTagMap {
|
|
82
|
-
constructor() {
|
|
83
|
-
super(['APEv2'], apev2TagMap);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
exports.APEv2TagMapper = APEv2TagMapper;
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APEv2TagMapper = void 0;
|
|
4
|
+
const CaseInsensitiveTagMap_1 = require("../common/CaseInsensitiveTagMap");
|
|
5
|
+
/**
|
|
6
|
+
* ID3v2.2 tag mappings
|
|
7
|
+
*/
|
|
8
|
+
const apev2TagMap = {
|
|
9
|
+
Title: 'title',
|
|
10
|
+
Artist: 'artist',
|
|
11
|
+
Artists: 'artists',
|
|
12
|
+
'Album Artist': 'albumartist',
|
|
13
|
+
Album: 'album',
|
|
14
|
+
Year: 'date',
|
|
15
|
+
Originalyear: 'originalyear',
|
|
16
|
+
Originaldate: 'originaldate',
|
|
17
|
+
Comment: 'comment',
|
|
18
|
+
Track: 'track',
|
|
19
|
+
Disc: 'disk',
|
|
20
|
+
DISCNUMBER: 'disk',
|
|
21
|
+
Genre: 'genre',
|
|
22
|
+
'Cover Art (Front)': 'picture',
|
|
23
|
+
'Cover Art (Back)': 'picture',
|
|
24
|
+
Composer: 'composer',
|
|
25
|
+
Lyrics: 'lyrics',
|
|
26
|
+
ALBUMSORT: 'albumsort',
|
|
27
|
+
TITLESORT: 'titlesort',
|
|
28
|
+
WORK: 'work',
|
|
29
|
+
ARTISTSORT: 'artistsort',
|
|
30
|
+
ALBUMARTISTSORT: 'albumartistsort',
|
|
31
|
+
COMPOSERSORT: 'composersort',
|
|
32
|
+
Lyricist: 'lyricist',
|
|
33
|
+
Writer: 'writer',
|
|
34
|
+
Conductor: 'conductor',
|
|
35
|
+
// 'Performer=artist (instrument)': 'performer:instrument',
|
|
36
|
+
MixArtist: 'remixer',
|
|
37
|
+
Arranger: 'arranger',
|
|
38
|
+
Engineer: 'engineer',
|
|
39
|
+
Producer: 'producer',
|
|
40
|
+
DJMixer: 'djmixer',
|
|
41
|
+
Mixer: 'mixer',
|
|
42
|
+
Label: 'label',
|
|
43
|
+
Grouping: 'grouping',
|
|
44
|
+
Subtitle: 'subtitle',
|
|
45
|
+
DiscSubtitle: 'discsubtitle',
|
|
46
|
+
Compilation: 'compilation',
|
|
47
|
+
BPM: 'bpm',
|
|
48
|
+
Mood: 'mood',
|
|
49
|
+
Media: 'media',
|
|
50
|
+
CatalogNumber: 'catalognumber',
|
|
51
|
+
MUSICBRAINZ_ALBUMSTATUS: 'releasestatus',
|
|
52
|
+
MUSICBRAINZ_ALBUMTYPE: 'releasetype',
|
|
53
|
+
RELEASECOUNTRY: 'releasecountry',
|
|
54
|
+
Script: 'script',
|
|
55
|
+
Language: 'language',
|
|
56
|
+
Copyright: 'copyright',
|
|
57
|
+
LICENSE: 'license',
|
|
58
|
+
EncodedBy: 'encodedby',
|
|
59
|
+
EncoderSettings: 'encodersettings',
|
|
60
|
+
Barcode: 'barcode',
|
|
61
|
+
ISRC: 'isrc',
|
|
62
|
+
ASIN: 'asin',
|
|
63
|
+
musicbrainz_trackid: 'musicbrainz_recordingid',
|
|
64
|
+
musicbrainz_releasetrackid: 'musicbrainz_trackid',
|
|
65
|
+
MUSICBRAINZ_ALBUMID: 'musicbrainz_albumid',
|
|
66
|
+
MUSICBRAINZ_ARTISTID: 'musicbrainz_artistid',
|
|
67
|
+
MUSICBRAINZ_ALBUMARTISTID: 'musicbrainz_albumartistid',
|
|
68
|
+
MUSICBRAINZ_RELEASEGROUPID: 'musicbrainz_releasegroupid',
|
|
69
|
+
MUSICBRAINZ_WORKID: 'musicbrainz_workid',
|
|
70
|
+
MUSICBRAINZ_TRMID: 'musicbrainz_trmid',
|
|
71
|
+
MUSICBRAINZ_DISCID: 'musicbrainz_discid',
|
|
72
|
+
Acoustid_Id: 'acoustid_id',
|
|
73
|
+
ACOUSTID_FINGERPRINT: 'acoustid_fingerprint',
|
|
74
|
+
MUSICIP_PUID: 'musicip_puid',
|
|
75
|
+
Weblink: 'website',
|
|
76
|
+
REPLAYGAIN_TRACK_GAIN: 'replaygain_track_gain',
|
|
77
|
+
REPLAYGAIN_TRACK_PEAK: 'replaygain_track_peak',
|
|
78
|
+
MP3GAIN_MINMAX: 'replaygain_track_minmax',
|
|
79
|
+
MP3GAIN_UNDO: 'replaygain_undo'
|
|
80
|
+
};
|
|
81
|
+
class APEv2TagMapper extends CaseInsensitiveTagMap_1.CaseInsensitiveTagMap {
|
|
82
|
+
constructor() {
|
|
83
|
+
super(['APEv2'], apev2TagMap);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.APEv2TagMapper = APEv2TagMapper;
|
|
87
87
|
//# sourceMappingURL=APEv2TagMapper.js.map
|