Initial working version

This commit is contained in:
Samuel Kent
2022-12-22 20:22:22 +11:00
parent ce9675a1cc
commit ced7fa5092
902 changed files with 150252 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
var utils = require('./utils');
function checkMagicId3v1(view) {
var id3Magic = utils.readBytes(view, view.byteLength - 128, 3);
//"TAG"
return id3Magic[0] === 84 && id3Magic[1] === 65 && id3Magic[2] === 71;
}
module.exports = function(buffer) {
//read last 128 bytes
var view = utils.createView(buffer);
if (!checkMagicId3v1(view)) {
return null;
}
function trim(value) {
return value.replace(/[\s\u0000]+$/, '');
}
try {
var offset = view.byteLength - 128 + 3,
readAscii = utils.readAscii;
var title = readAscii(view, offset, 30),
artist = readAscii(view, offset + 30, 30),
album = readAscii(view, offset + 60, 30),
year = readAscii(view, offset + 90, 4);
offset += 94;
var comment = readAscii(view, offset, 28),
track = null;
offset += 28;
if (view.getUint8(offset) === 0) {
//next byte is the track
track = view.getUint8(offset + 1);
} else {
comment += readAscii(view, offset, 2);
}
offset += 2;
var genre = view.getUint8(offset);
return {
title: trim(title),
artist: trim(artist),
album: trim(album),
year: trim(year),
comment: trim(comment),
track: track,
genre: genre
};
} catch (e) {
return null;
}
};
+124
View File
@@ -0,0 +1,124 @@
var utils = require('./utils');
function checkMagicId3(view, offset) {
var id3Magic = utils.readBytes(view, offset, 3);
//"ID3"
return id3Magic[0] === 73 && id3Magic[1] === 68 && id3Magic[2] === 51;
}
function getUint28(view, offset) {
var sizeBytes = utils.readBytes(view, offset, 4);
var mask = 0xfffffff;
return ((sizeBytes[0] & mask) << 21) |
((sizeBytes[1] & mask) << 14) |
((sizeBytes[2] & mask) << 7) |
(sizeBytes[3] & mask);
}
//http://id3.org/id3v2.3.0
//http://id3.org/id3v2.4.0-structure
//http://id3.org/id3v2.4.0-frames
module.exports = function(buffer) {
var view = utils.createView(buffer);
if (!checkMagicId3(view, 0)) {
return null;
}
var offset = 3;
//var majorVersion = view.getUint8(offset);
offset += 2;
var flags = view.getUint8(offset);
offset++;
var size = getUint28(view, offset);
offset += 4;
var extendedHeader = (flags & 128) > 0;
if (extendedHeader) {
offset += getUint28(view, offset);
}
function readFrame(offset) {
try {
var id = utils.readAscii(view, offset, 4);
var size = getUint28(view, offset + 4);
offset += 10; //+2 more for flags we don't care about
if (id[0] !== 'T') {
return {
id: id,
size: size + 10
};
}
var encoding = view.getUint8(offset),
data = '';
if (encoding <= 3) {
offset++;
if (encoding === 3) {
//UTF8 - null terminated
data = utils.readUtf8(view, offset, size - 1);
} else {
//ISO-8859-1, UTF-16, UTF-16BE
//UTF-16 and UTF-16BE are $FF $00 terminated
//ISO is null terminated
//screw these encodings, read it as ascii
data = utils.readAscii(view, offset, size - 1);
}
} else {
//no encoding info, read it as ascii
data = utils.readAscii(view, offset, size);
}
//id3v2.4 is supposed to have encoding terminations, but sometimes
//they don't? meh.
data = utils.trimNull(data);
return {
id: id,
size: size + 10,
content: data
};
} catch (e) {
return null;
}
}
var idMap = {
TALB: 'album',
TCOM: 'composer',
TIT1: 'title',
TIT2: 'title',
TPE1: 'artist',
TRCK: 'track',
TSSE: 'encoder',
TDRC: 'year',
TCON: 'genre'
};
var endOfTags = offset + size,
frames = {};
while (offset < endOfTags) {
var frame = readFrame(offset);
if (!frame) {
break;
}
offset += frame.size;
if (!frame.content) {
continue;
}
var id = idMap[frame.id] || frame.id;
if (id === 'TXXX') {
var nullByte = frame.content.indexOf('\u0000');
id = frame.content.substring(0, nullByte);
frames[id] = frame.content.substring(nullByte + 1);
} else {
frames[id] = frames[frame.id] = frame.content;
}
}
return frames;
};
+79
View File
@@ -0,0 +1,79 @@
var utils = require('./utils');
/**
* See http://www.ietf.org/rfc/rfc3533.txt
* @param {Buffer|ArrayBuffer} buffer
*/
module.exports = function(buffer) {
var view = utils.createView(buffer);
function parsePage(offset, withPacket) {
if (view.byteLength < offset + 27) {
return null;
}
var numPageSegments = view.getUint8(offset + 26),
segmentTable = utils.readBytes(view, offset + 27, numPageSegments),
headerSize = 27 + numPageSegments;
if (!segmentTable.length) {
return null;
}
var
pageSize = headerSize + segmentTable.reduce(function(cur, next) {
return cur + next;
}),
length = headerSize + 1 + 'vorbis'.length,
packetView = null;
if (withPacket) {
packetView = utils.createView(new ArrayBuffer(pageSize - length));
utils.readBytes(view, offset + length, pageSize - length, packetView);
}
return {
pageSize: pageSize,
packet: packetView
};
}
function parseComments(packet) {
try {
var vendorLength = packet.getUint32(0, true),
commentListLength = packet.getUint32(4 + vendorLength, true),
comments = {},
offset = 8 + vendorLength,
map = {
tracknumber: 'track'
};
for (var i = 0; i < commentListLength; i++) {
var commentLength = packet.getUint32(offset, true),
comment = utils.readUtf8(packet, offset + 4, commentLength),
equals = comment.indexOf('='),
key = comment.substring(0, equals).toLowerCase();
comments[map[key] || key] = comments[key] = utils.trimNull(comment.substring(equals + 1));
offset += 4 + commentLength;
}
return comments;
} catch (e) {
//all exceptions are just malformed/truncated data, so we just ignore them
return null;
}
}
var id = parsePage(0);
if (!id) {
return null;
}
var commentHeader = parsePage(id.pageSize, true);
if (!commentHeader) {
return null;
}
return parseComments(commentHeader.packet);
};
+69
View File
@@ -0,0 +1,69 @@
function toArrayBuffer(buffer) {
var arrayBuffer = new ArrayBuffer(buffer.length);
var view = new Uint8Array(arrayBuffer);
for (var i = 0; i < buffer.length; ++i) {
view[i] = buffer[i];
}
return arrayBuffer;
}
module.exports = {
trimNull: function(s) {
return s.replace(/\u0000+$/, '');
},
createView: function(buffer) {
if (typeof(Buffer) !== 'undefined' && buffer instanceof Buffer) {
//convert nodejs buffers to ArrayBuffer
buffer = toArrayBuffer(buffer);
}
if (!(buffer instanceof ArrayBuffer)) {
throw new Error('Expected instance of Buffer or ArrayBuffer');
}
return new DataView(buffer);
},
readBytes: function(view, offset, length, target) {
if (offset + length < 0) {
return [];
}
var bytes = [];
var max = Math.min(offset + length, view.byteLength);
for (var i = offset; i < max; i++) {
var value = view.getUint8(i);
bytes.push(value);
if (target) {
target.setUint8(i - offset, value);
}
}
return bytes;
},
readAscii: function(view, offset, length) {
if (view.byteLength < offset + length) {
return '';
}
var s = '';
for (var i = 0; i < length; i++) {
s += String.fromCharCode(view.getUint8(offset + i));
}
return s;
},
readUtf8: function(view, offset, length) {
if (view.byteLength < offset + length) {
return '';
}
var buffer = view.buffer.slice(offset, offset + length);
//http://stackoverflow.com/a/17192845 - convert byte array to UTF8 string
var encodedString = String.fromCharCode.apply(null, new Uint8Array(buffer));
return decodeURIComponent(escape(encodedString));
}
};