Initial working version
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
||||
import { BasicParser } from '../common/BasicParser';
|
||||
import { IRandomReader } from '../type';
|
||||
/**
|
||||
* ID3v1 Genre mappings
|
||||
* Ref: https://de.wikipedia.org/wiki/Liste_der_ID3v1-Genres
|
||||
*/
|
||||
export declare const Genres: string[];
|
||||
export declare class ID3v1Parser extends BasicParser {
|
||||
private static getGenre;
|
||||
parse(): Promise<void>;
|
||||
private addTag;
|
||||
}
|
||||
export declare function hasID3v1Header(reader: IRandomReader): Promise<boolean>;
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
"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;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { CommonTagMapper } from "../common/GenericTagMapper";
|
||||
export declare class ID3v1TagMapper extends CommonTagMapper {
|
||||
constructor();
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ID3v1TagMapper = void 0;
|
||||
const GenericTagMapper_1 = require("../common/GenericTagMapper");
|
||||
/**
|
||||
* ID3v1 tag mappings
|
||||
*/
|
||||
const id3v1TagMap = {
|
||||
title: 'title',
|
||||
artist: 'artist',
|
||||
album: 'album',
|
||||
year: 'year',
|
||||
comment: 'comment',
|
||||
track: 'track',
|
||||
genre: 'genre'
|
||||
};
|
||||
class ID3v1TagMapper extends GenericTagMapper_1.CommonTagMapper {
|
||||
constructor() {
|
||||
super(['ID3v1'], id3v1TagMap);
|
||||
}
|
||||
}
|
||||
exports.ID3v1TagMapper = ID3v1TagMapper;
|
||||
Reference in New Issue
Block a user