id3v2
Version:
Reads ID3v2 metadata
376 lines • 12.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var fs_1 = require("fs");
var iconv = require("iconv-lite");
/*
* Reading of ID3 tag based on http://id3.org/
*/
var ID3v1Genres = [
'Blues',
'Classic Rock',
'Country',
'Dance',
'Disco',
'Funk',
'Grunge',
'Hip-Hop',
'Jazz',
'Metal',
'New Age',
'Oldies',
'Other',
'Pop',
'Rhythm and Blues',
'Rap',
'Reggae',
'Rock',
'Techno',
'Industrial',
'Alternative',
'Ska',
'Death Metal',
'Pranks',
'Soundtrack',
'Euro-Techno',
'Ambient',
'Trip-Hop',
'Vocal',
'Jazz & Funk',
'Fusion',
'Trance',
'Classical',
'Instrumental',
'Acid',
'House',
'Game',
'Sound Clip',
'Gospel',
'Noise',
'Alternative Rock',
'Bass',
'Soul',
'Punk',
'Space',
'Meditative',
'Instrumental Pop',
'Instrumental Rock',
'Ethnic',
'Gothic',
'Darkwave',
'Techno-Industrial',
'Electronic',
'Pop-Folk',
'Eurodance',
'Dream',
'Southern Rock',
'Comedy',
'Cult',
'Gangsta',
'Top 40',
'Christian Rap',
'Pop/Funk',
'Jungle',
'Native US',
'Cabaret',
'New Wave',
'Psychedelic',
'Rave',
'Showtunes',
'Trailer',
'Lo-Fi',
'Tribal',
'Acid Punk',
'Acid Jazz',
'Polka',
'Retro',
'Musical',
'Rock ’n’ Roll',
'Hard Rock'
];
var ID3HeaderOffsets;
(function (ID3HeaderOffsets) {
ID3HeaderOffsets[ID3HeaderOffsets["MAGIC"] = 0] = "MAGIC";
ID3HeaderOffsets[ID3HeaderOffsets["MAJOR_VERSION"] = 3] = "MAJOR_VERSION";
ID3HeaderOffsets[ID3HeaderOffsets["MINOR_VERSION"] = 4] = "MINOR_VERSION";
ID3HeaderOffsets[ID3HeaderOffsets["FLAGS"] = 5] = "FLAGS";
ID3HeaderOffsets[ID3HeaderOffsets["SIZE"] = 6] = "SIZE";
ID3HeaderOffsets[ID3HeaderOffsets["END_OF_HEADER"] = 10] = "END_OF_HEADER";
})(ID3HeaderOffsets || (ID3HeaderOffsets = {}));
var ID3HeaderFlags;
(function (ID3HeaderFlags) {
ID3HeaderFlags[ID3HeaderFlags["Unsynchronisation"] = 128] = "Unsynchronisation";
ID3HeaderFlags[ID3HeaderFlags["Extended"] = 64] = "Extended";
ID3HeaderFlags[ID3HeaderFlags["Experimental"] = 32] = "Experimental";
ID3HeaderFlags[ID3HeaderFlags["Footer"] = 16] = "Footer";
ID3HeaderFlags[ID3HeaderFlags["Others"] = 15] = "Others"; // 00001111
})(ID3HeaderFlags || (ID3HeaderFlags = {}));
var ID3ExtendedHeaderOffsets;
(function (ID3ExtendedHeaderOffsets) {
ID3ExtendedHeaderOffsets[ID3ExtendedHeaderOffsets["SIZE"] = 0] = "SIZE";
ID3ExtendedHeaderOffsets[ID3ExtendedHeaderOffsets["NUMBER_OF_FLAGS"] = 4] = "NUMBER_OF_FLAGS";
ID3ExtendedHeaderOffsets[ID3ExtendedHeaderOffsets["FLAGS"] = 5] = "FLAGS";
// END_OF_HEADER depends on NUMBER_OF_FLAGS
})(ID3ExtendedHeaderOffsets || (ID3ExtendedHeaderOffsets = {}));
var ID3FrameOffsets;
(function (ID3FrameOffsets) {
ID3FrameOffsets[ID3FrameOffsets["ID"] = 0] = "ID";
ID3FrameOffsets[ID3FrameOffsets["SIZE"] = 4] = "SIZE";
ID3FrameOffsets[ID3FrameOffsets["FLAGS"] = 8] = "FLAGS";
ID3FrameOffsets[ID3FrameOffsets["END_OF_HEADER"] = 10] = "END_OF_HEADER";
})(ID3FrameOffsets || (ID3FrameOffsets = {}));
var ID3FrameFlags;
(function (ID3FrameFlags) {
ID3FrameFlags[ID3FrameFlags["Grouping"] = 64] = "Grouping";
ID3FrameFlags[ID3FrameFlags["Compression"] = 8] = "Compression";
ID3FrameFlags[ID3FrameFlags["Encryption"] = 4] = "Encryption";
ID3FrameFlags[ID3FrameFlags["Unsynchronisation"] = 2] = "Unsynchronisation";
ID3FrameFlags[ID3FrameFlags["DataLengthIndicator"] = 1] = "DataLengthIndicator"; // 00000000 00000001
})(ID3FrameFlags || (ID3FrameFlags = {}));
var hasFlag = function (flags, flag) { return (flags & flag) === flag; };
var unsyncedLength = function (input) {
return (input & 127) +
((input & (127 << 8)) >> 1) +
((input & (127 << 16)) >> 1) +
((input & (127 << 24)) >>> 1);
};
var isID3 = function (buffer) {
return buffer
.slice(ID3HeaderOffsets.MAGIC, ID3HeaderOffsets.MAGIC + 3)
.toString() === 'ID3';
};
var isID3v24 = function (buffer) {
return buffer.readIntBE(ID3HeaderOffsets.MAJOR_VERSION, 1) <= 4;
};
var getID3HeaderFlags = function (buffer) {
var flags = buffer.readIntBE(ID3HeaderOffsets.FLAGS, 1);
return {
unsynchronisation: hasFlag(flags, ID3HeaderFlags.Unsynchronisation),
extendedHeader: hasFlag(flags, ID3HeaderFlags.Extended),
experimental: hasFlag(flags, ID3HeaderFlags.Experimental),
footer: hasFlag(flags, ID3HeaderFlags.Footer),
others: (flags & ID3HeaderFlags.Others) !== 0
};
};
var isPadding = function (buffer) { return buffer.readUInt32BE(0) === 0; };
var getID3FrameFlags = function (buffer) {
var flags = buffer.readInt16BE(ID3FrameOffsets.FLAGS);
return {
grouping: hasFlag(flags, ID3FrameFlags.Grouping),
compression: hasFlag(flags, ID3FrameFlags.Compression),
encryption: hasFlag(flags, ID3FrameFlags.Encryption),
unsynchronisation: hasFlag(flags, ID3FrameFlags.Unsynchronisation),
dataLengthIndicator: hasFlag(flags, ID3FrameFlags.DataLengthIndicator)
};
};
// tslint:disable-next-line cyclomatic-complexity
var getFrameData = function (buffer) {
var name = buffer
.slice(ID3FrameOffsets.ID, ID3FrameOffsets.ID + 4)
.toString();
var length = unsyncedLength(buffer.readInt32BE(ID3FrameOffsets.SIZE));
var encoding = (function () {
var value = buffer.readInt8(ID3FrameOffsets.END_OF_HEADER);
switch (value) {
case 0:
return 'ISO-8859-1';
case 1:
return 'UTF-16';
case 2:
return 'UTF-16';
case 3:
return 'UTF-8';
}
return '';
})();
var data;
switch (name) {
case 'TXXX':
{
var idx = buffer.indexOf(0, ID3FrameOffsets.END_OF_HEADER + 1);
var description = iconv.decode(buffer.slice(ID3FrameOffsets.END_OF_HEADER + 1, idx), encoding);
data = {
description: description,
value: iconv.decode(buffer.slice(ID3FrameOffsets.END_OF_HEADER + 1 + description.length + 1, ID3FrameOffsets.END_OF_HEADER + length), encoding)
};
}
break;
case 'TPOS':
case 'TLEN':
case 'TYER':
case 'TSSE':
case 'TCON':
case 'TRCK':
case 'TALB':
case 'TIT2':
case 'TDRC':
case 'TPE1':
case 'TPE2':
data = iconv.decode(buffer.slice(ID3FrameOffsets.END_OF_HEADER + 1, ID3FrameOffsets.END_OF_HEADER + length), encoding);
break;
case 'POPM':
{
var idx = buffer.indexOf(0, ID3FrameOffsets.END_OF_HEADER);
var email = buffer
.slice(ID3FrameOffsets.END_OF_HEADER, idx)
.toString();
var rating = buffer.readUInt8(idx + 1);
// TODO: counter
data = {
email: email,
rating: rating,
counter: undefined
};
}
break;
case 'UFID':
{
var idx = buffer.indexOf(0, ID3FrameOffsets.END_OF_HEADER);
var ownerIdentifier = buffer
.slice(ID3FrameOffsets.END_OF_HEADER, idx)
.toString();
data = {
ownerIdentifier: ownerIdentifier,
identifier: buffer.slice(ID3FrameOffsets.END_OF_HEADER + ownerIdentifier.length, length)
};
}
break;
}
return {
name: name,
length: length,
flags: getID3FrameFlags(buffer),
data: data
};
};
var ID3v2 = /** @class */ (function () {
// tslint:disable-next-line cyclomatic-complexity
function ID3v2(path) {
this.frames = {};
var buffer = fs_1.readFileSync(path);
if (!isID3(buffer) || !isID3v24(buffer)) {
return;
}
this.flags = getID3HeaderFlags(buffer);
if (this.flags.others) {
return;
}
var size = unsyncedLength(buffer.readInt32BE(ID3HeaderOffsets.SIZE));
var startOfFrame = ID3HeaderOffsets.END_OF_HEADER;
if (this.flags.extendedHeader) {
var extendedHeaderBuffer = buffer.slice(ID3HeaderOffsets.END_OF_HEADER);
var extendedHeaderSize = unsyncedLength(extendedHeaderBuffer.readInt32BE(ID3ExtendedHeaderOffsets.SIZE));
startOfFrame += extendedHeaderSize;
}
while (startOfFrame < size) {
var frameBuffer = buffer.slice(startOfFrame);
if (isPadding(frameBuffer)) {
break;
}
var frame = getFrameData(frameBuffer);
if (this.frames[frame.name]) {
if (!Array.isArray(this.frames[frame.name])) {
this.frames[frame.name] = [this.frames[frame.name]];
}
this.frames[frame.name].push(frame);
}
else {
this.frames[frame.name] = frame;
}
startOfFrame += ID3FrameOffsets.END_OF_HEADER + frame.length;
}
}
ID3v2.prototype.getFrameData = function (name) {
var frame = this.frames[name];
if (Array.isArray(frame)) {
return frame.map(function (entry) { return entry.data; });
}
return frame ? frame.data : undefined;
};
Object.defineProperty(ID3v2.prototype, "ufid", {
get: function () {
return this.getFrameData('UFID');
},
enumerable: true,
configurable: true
});
Object.defineProperty(ID3v2.prototype, "genre", {
get: function () {
var genre = this.getFrameData('TCON');
if (genre) {
var idx = parseInt(genre.replace(/[\(\)]/g, ''), 10);
if (idx <= ID3v1Genres.length) {
genre = ID3v1Genres[idx];
}
}
return genre;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ID3v2.prototype, "track", {
get: function () {
return this.getFrameData('TRCK');
},
enumerable: true,
configurable: true
});
Object.defineProperty(ID3v2.prototype, "album", {
get: function () {
return this.getFrameData('TALB');
},
enumerable: true,
configurable: true
});
Object.defineProperty(ID3v2.prototype, "title", {
get: function () {
return this.getFrameData('TIT2');
},
enumerable: true,
configurable: true
});
Object.defineProperty(ID3v2.prototype, "year", {
get: function () {
return this.getFrameData('TDRC') || this.getFrameData('TYER');
},
enumerable: true,
configurable: true
});
Object.defineProperty(ID3v2.prototype, "artist", {
get: function () {
return this.getFrameData('TPE1');
},
enumerable: true,
configurable: true
});
Object.defineProperty(ID3v2.prototype, "popularimeter", {
get: function () {
return this.getFrameData('POPM');
},
enumerable: true,
configurable: true
});
Object.defineProperty(ID3v2.prototype, "length", {
get: function () {
return this.getFrameData('TLEN');
},
enumerable: true,
configurable: true
});
Object.defineProperty(ID3v2.prototype, "set", {
get: function () {
return this.getFrameData('TPOS');
},
enumerable: true,
configurable: true
});
Object.defineProperty(ID3v2.prototype, "text", {
get: function () {
return this.getFrameData('TXXX');
},
enumerable: true,
configurable: true
});
return ID3v2;
}());
exports.ID3v2 = ID3v2;
//# sourceMappingURL=index.js.map