135 lines
6.3 KiB
JavaScript
135 lines
6.3 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.hasID3v1Header = exports.ID3v1Parser = exports.Genres = void 0;
|
|
const initDebug = require("debug");
|
|
const util = require("../common/Util");
|
|
const Token = require("token-types");
|
|
const BasicParser_1 = require("../common/BasicParser");
|
|
const APEv2Parser_1 = require("../apev2/APEv2Parser");
|
|
const debug = initDebug('music-metadata:parser:ID3v1');
|
|
/**
|
|
* ID3v1 Genre mappings
|
|
* Ref: https://de.wikipedia.org/wiki/Liste_der_ID3v1-Genres
|
|
*/
|
|
exports.Genres = [
|
|
"Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop",
|
|
"Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "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",
|
|
"Alt. 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 Rap", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American",
|
|
"Cabaret", "New Wave", "Psychedelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal",
|
|
"Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock",
|
|
"Folk", "Folk/Rock", "National Folk", "Swing", "Fast-Fusion", "Bebob", "Latin", "Revival",
|
|
"Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock",
|
|
"Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour",
|
|
"Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus",
|
|
"Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore",
|
|
"Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo",
|
|
"A Cappella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House",
|
|
"Hardcore", "Terror", "Indie", "BritPop", "Negerpunk", "Polsk Punk", "Beat",
|
|
"Christian Gangsta Rap", "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian",
|
|
"Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop", "Synthpop",
|
|
"Abstract", "Art Rock", "Baroque", "Bhangra", "Big Beat", "Breakbeat", "Chillout",
|
|
"Downtempo", "Dub", "EBM", "Eclectic", "Electro", "Electroclash", "Emo", "Experimental",
|
|
"Garage", "Global", "IDM", "Illbient", "Industro-Goth", "Jam Band", "Krautrock",
|
|
"Leftfield", "Lounge", "Math Rock", "New Romantic", "Nu-Breakz", "Post-Punk", "Post-Rock",
|
|
"Psytrance", "Shoegaze", "Space Rock", "Trop Rock", "World Music", "Neoclassical", "Audiobook",
|
|
"Audio Theatre", "Neue Deutsche Welle", "Podcast", "Indie Rock", "G-Funk", "Dubstep",
|
|
"Garage Rock", "Psybient"
|
|
];
|
|
/**
|
|
* Spec: http://id3.org/ID3v1
|
|
* Wiki: https://en.wikipedia.org/wiki/ID3
|
|
*/
|
|
const Iid3v1Token = {
|
|
len: 128,
|
|
/**
|
|
* @param buf Buffer possibly holding the 128 bytes ID3v1.1 metadata header
|
|
* @param off Offset in buffer in bytes
|
|
* @returns ID3v1.1 header if first 3 bytes equals 'TAG', otherwise null is returned
|
|
*/
|
|
get: (buf, off) => {
|
|
const header = new Id3v1StringType(3).get(buf, off);
|
|
return header === "TAG" ? {
|
|
header,
|
|
title: new Id3v1StringType(30).get(buf, off + 3),
|
|
artist: new Id3v1StringType(30).get(buf, off + 33),
|
|
album: new Id3v1StringType(30).get(buf, off + 63),
|
|
year: new Id3v1StringType(4).get(buf, off + 93),
|
|
comment: new Id3v1StringType(28).get(buf, off + 97),
|
|
// ID3v1.1 separator for track
|
|
zeroByte: Token.UINT8.get(buf, off + 127),
|
|
// track: ID3v1.1 field added by Michael Mutschler
|
|
track: Token.UINT8.get(buf, off + 126),
|
|
genre: Token.UINT8.get(buf, off + 127)
|
|
} : null;
|
|
}
|
|
};
|
|
class Id3v1StringType extends Token.StringType {
|
|
constructor(len) {
|
|
super(len, "binary");
|
|
}
|
|
get(buf, off) {
|
|
let value = super.get(buf, off);
|
|
value = util.trimRightNull(value);
|
|
value = value.trim();
|
|
return value.length > 0 ? value : undefined;
|
|
}
|
|
}
|
|
class ID3v1Parser extends BasicParser_1.BasicParser {
|
|
static getGenre(genreIndex) {
|
|
if (genreIndex < exports.Genres.length) {
|
|
return exports.Genres[genreIndex];
|
|
}
|
|
return undefined; // ToDO: generate warning
|
|
}
|
|
async parse() {
|
|
if (!this.tokenizer.fileInfo.size) {
|
|
debug('Skip checking for ID3v1 because the file-size is unknown');
|
|
return;
|
|
}
|
|
if (this.options.apeHeader) {
|
|
this.tokenizer.ignore(this.options.apeHeader.offset - this.tokenizer.position);
|
|
const apeParser = new APEv2Parser_1.APEv2Parser();
|
|
apeParser.init(this.metadata, this.tokenizer, this.options);
|
|
await apeParser.parseTags(this.options.apeHeader.footer);
|
|
}
|
|
const offset = this.tokenizer.fileInfo.size - Iid3v1Token.len;
|
|
if (this.tokenizer.position > offset) {
|
|
debug('Already consumed the last 128 bytes');
|
|
return;
|
|
}
|
|
const header = await this.tokenizer.readToken(Iid3v1Token, offset);
|
|
if (header) {
|
|
debug("ID3v1 header found at: pos=%s", this.tokenizer.fileInfo.size - Iid3v1Token.len);
|
|
for (const id of ["title", "artist", "album", "comment", "track", "year"]) {
|
|
if (header[id] && header[id] !== "")
|
|
this.addTag(id, header[id]);
|
|
}
|
|
const genre = ID3v1Parser.getGenre(header.genre);
|
|
if (genre)
|
|
this.addTag('genre', genre);
|
|
}
|
|
else {
|
|
debug("ID3v1 header not found at: pos=%s", this.tokenizer.fileInfo.size - Iid3v1Token.len);
|
|
}
|
|
}
|
|
addTag(id, value) {
|
|
this.metadata.addTag('ID3v1', id, value);
|
|
}
|
|
}
|
|
exports.ID3v1Parser = ID3v1Parser;
|
|
async function hasID3v1Header(reader) {
|
|
if (reader.fileSize >= 128) {
|
|
const tag = Buffer.alloc(3);
|
|
await reader.randomRead(tag, 0, tag.length, reader.fileSize - 128);
|
|
return tag.toString('binary') === 'TAG';
|
|
}
|
|
return false;
|
|
}
|
|
exports.hasID3v1Header = hasID3v1Header;
|