Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

musicmetadata

Package Overview
Dependencies
Maintainers
1
Versions
60
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

musicmetadata - npm Package Compare versions

Comparing version 0.2.2 to 0.2.3

filestest.js

151

lib/common.js

@@ -5,3 +5,3 @@ var strtok = require('strtok');

exports.detectMediaType = function (header) {
//default to id3v1.1 if we cannot detect any other tags
// default to id3v1.1 if we cannot detect any other tags
var tag = 'id3v1';

@@ -24,32 +24,17 @@ if ('ID3' === header.slice(0, 3)) {

stream.Stream.prototype.onRealEnd = function(callback) {
stream.Stream.prototype.onRealEnd = function (callback) {
var called = false;
this.on('end', function() {
if (!called) callback();
called = true;
});
})
this.on('close', function() {
if (!called) callback();
called = true;
});
};
exports.joinBuffers = function(buffers, totalLength) {
var result = new Buffer(totalLength);
var pos = 0
for (var i=0; i < buffers.length; i++) {
buffers[i].copy(result, pos);
pos += buffers[i].length;
}
return result;
})
}
exports.readVorbisPicture = function(buffer) {
var picture = {},
offset = 0;
exports.readVorbisPicture = function (buffer) {
var picture = {};
var offset = 0;

@@ -75,6 +60,5 @@ picture.type = PICTURE_TYPE[strtok.UINT32_BE.get(buffer, 0)];

exports.removeUnsyncBytes = function(buffer) {
var readI = 0,
writeI = 0;
exports.removeUnsyncBytes = function (buffer) {
var readI = 0;
var writeI = 0;
while (readI < buffer.length -1) {

@@ -84,22 +68,16 @@ if (readI !== writeI) {

}
readI += (buffer[readI] === 0xFF && buffer[readI + 1] === 0) ? 2 : 1;
writeI++;
}
if (readI < buffer.length) {
buffer[writeI++] = buffer[readI++];
buffer[writeI++] = buffer[readI++];
}
return buffer.slice(0, writeI);
};
}
exports.findZero = function(buffer, start, end, encoding) {
exports.findZero = function (buffer, start, end, encoding) {
var i = start;
if (encoding === 'utf16') {
while (buffer[i] !== 0 || buffer[i+1] !== 0) {
if (i >= end) {
return end;
}
if (i >= end) return end;
i++;

@@ -109,19 +87,15 @@ }

while (buffer[i] !== 0) {
if (i >= end) {
return end;
}
if (i >= end) return end;
i++;
}
}
return i;
};
}
exports.isBitSetAt = function(b, offset, bit) {
exports.isBitSetAt = function (b, offset, bit) {
return (b[offset] & (1 << bit)) !== 0;
};
}
var decodeString = exports.decodeString = function(b, encoding, start, end) {
var decodeString = exports.decodeString = function (b, encoding, start, end) {
var text = '';
if (encoding == 'utf16') {

@@ -133,16 +107,12 @@ text = readUTF16String(b.slice(start, end));

}
return {
text : text,
length : end - start
};
};
return { text : text, length : end - start }
}
exports.parseGenre = function(origVal) {
//match everything inside parentheses
exports.parseGenre = function (origVal) {
// match everything inside parentheses
var split = origVal.trim().split(/\((.*?)\)/g)
.filter(function(val) { return val !== ''; });
.filter(function (val) { return val !== ''; });
var array = [];
for (var i=0; i < split.length; i++) {
for (var i = 0; i < split.length; i++) {
var cur = split[i];

@@ -152,41 +122,48 @@ if (!isNaN(parseInt(cur))) cur = GENRES[cur];

}
return array.join('/');
}
var readUTF16String = function readUTF16String(bytes) {
var ix = 0,
offset1 = 1,
offset2 = 0,
maxBytes = bytes.length;
function swapBytes (buffer) {
var l = buffer.length;
if (l & 0x01) {
throw new Error('Buffer length must be even');
}
for (var i = 0; i < l; i += 2) {
var a = buffer[i];
buffer[i] = buffer[i+1];
buffer[i+1] = a;
}
return buffer;
}
var readUTF16String = exports.readUTF16String = function (bytes) {
// bom detection (big endian)
if (bytes[0] === 0xFE && bytes[1] === 0xFF) {
ix = 2;
offset1 = 0;
offset2 = 1;
} else if (bytes[0] === 0xFF && bytes[1] === 0xFE) {
ix = 2;
bytes = swapBytes(bytes);
}
return bytes.toString('utf16le');
}
var str = '';
for (var j = 0; ix < maxBytes; j++) {
var byte1 = bytes[ix + offset1],
byte2 = bytes[ix + offset2],
word1 = (byte1 << 8) + byte2;
ix += 2;
strtok.UINT24_BE = {
len: 3,
get: function(buf, off) {
return (((buf[off] << 8) + buf[off + 1]) << 8) + buf[off + 2];
}
}
if (word1 === 0x0000) {
break;
} else if (byte1 < 0xD8 || byte1 >= 0xE0) {
str += String.fromCharCode(word1);
} else {
var byte3 = bytes[ix+offset1],
byte4 = bytes[ix+offset2],
word2 = (byte3 << 8) + byte4;
ix += 2;
str += String.fromCharCode(word1, word2);
}
strtok.BITSET = {
len: 1,
get: function(buf, off, bit) {
return (buf[off] & (1 << bit)) !== 0;
}
return str;
};
}
strtok.INT32SYNCSAFE = {
len: 4,
get: function(buf, off) {
return buf[off + 3] & 0x7f | ((buf[off + 2]) << 7) | ((buf[off + 1]) << 14) | ((buf[off]) << 21);
}
}
var PICTURE_TYPE = exports.PICTURE_TYPE = [

@@ -214,3 +191,3 @@ "Other",

"Publisher/Studio logotype"
];
]

@@ -239,2 +216,2 @@ var GENRES = exports.GENRES = [

'Christian Rock','Merengue','Salsa','Thrash Metal','Anime','JPop','Synthpop'
];
]

@@ -1,28 +0,33 @@

/* jshint node:true, sub:true, globalstrict:true */
"use strict";
var util = require('util');
var events = require('events');
var strtok = require('strtok');
var common = require('./common');
var DataDecoder = function(data) {
module.exports = function (stream, callback) {
var currentState = startState;
strtok.parse(stream, function (v, cb) {
try {
currentState = currentState.parse(callback, v);
} catch (exception) {
currentState = finishedState;
callback('done', exception);
}
return currentState.getExpectedType();
})
}
var DataDecoder = function (data) {
this.data = data;
this.offset = 0;
};
}
DataDecoder.prototype.readInt32 = function() {
DataDecoder.prototype.readInt32 = function () {
var value = strtok.UINT32_LE.get(this.data, this.offset);
this.offset += 4;
return value;
};
}
DataDecoder.prototype.readStringUtf8 = function() {
DataDecoder.prototype.readStringUtf8 = function () {
var len = this.readInt32();
var value = this.data.toString('utf8', this.offset, this.offset + len);
this.offset += len;
return value;

@@ -32,17 +37,17 @@ };

var finishedState = {
parse: function(context) {
parse: function (callback) {
return this;
},
getExpectedType: function() {
getExpectedType: function () {
return strtok.DONE;
}
};
}
var BlockDataState = function(type, length, nextStateFactory) {
var BlockDataState = function (type, length, nextStateFactory) {
this.type = type;
this.length = length;
this.nextStateFactory = nextStateFactory;
};
}
BlockDataState.prototype.parse = function(context, data) {
BlockDataState.prototype.parse = function (callback, data) {
if (this.type === 4) {

@@ -59,18 +64,18 @@ var decoder = new DataDecoder(data);

split = comment.split('=');
context.emit(split[0].toUpperCase(), split[1]);
callback(split[0].toUpperCase(), split[1]);
}
} else if (this.type === 6) {
var picture = common.readVorbisPicture(data);
context.emit('METADATA_BLOCK_PICTURE', picture);
callback('METADATA_BLOCK_PICTURE', picture);
}
return this.nextStateFactory();
};
}
BlockDataState.prototype.getExpectedType = function() {
BlockDataState.prototype.getExpectedType = function () {
return new strtok.BufferType(this.length);
};
}
var blockHeaderState = {
parse: function(context, data) {
parse: function (callback, data) {
var header = {

@@ -80,26 +85,25 @@ lastBlock: (data[0] & 0x80) == 0x80,

length: strtok.UINT24_BE.get(data, 1)
};
}
var followingStateFactory = header.lastBlock ? function() {
context.emit('done');
callback('done');
return finishedState;
} : function() {
return blockHeaderState;
};
}
return new BlockDataState(header.type, header.length, followingStateFactory);
},
getExpectedType: function() {
getExpectedType: function () {
return new strtok.BufferType(4);
}
};
}
var idState = {
parse: function(context, data) {
parse: function (callback, data) {
if (data !== 'fLaC') {
throw new Error('expected flac header but was not found');
}
return blockHeaderState;
},
getExpectedType: function() {
getExpectedType: function () {
return new strtok.StringType(4);

@@ -110,33 +114,8 @@ }

var startState = {
parse: function(context) {
parse: function (callback) {
return idState;
},
getExpectedType: function() {
getExpectedType: function () {
return strtok.DONE;
}
};
var Flac = module.exports = function(stream) {
events.EventEmitter.call(this);
this.stream = stream;
this.currentState = startState;
this.parse();
};
util.inherits(Flac, events.EventEmitter);
Flac.prototype.parse = function() {
var self = this;
strtok.parse(self.stream, function(v, cb) {
try {
self.currentState = self.currentState.parse(self, v);
} catch (exception) {
self.currentState = finishedState;
self.emit('done', exception);
}
return self.currentState.getExpectedType();
});
};
}
var util = require('util');
var events = require('events');
var common = require('./common');
var Id3v1 = module.exports = function(stream) {
events.EventEmitter.call(this);
this.stream = stream;
this.parse();
};
util.inherits(Id3v1, events.EventEmitter);
Id3v1.prototype.parse = function() {
var self = this,
endData = null;
self.stream.on('data', function(data) {
module.exports = function (stream, callback) {
var endData = null;
stream.on('data', function (data) {
endData = data;
});
self.stream.onRealEnd(function() {
parse(endData);
});
function parse(data) {
stream.onRealEnd(function () {
try {
var offset = data.length - 128;
var header = data.toString('ascii', offset, offset += 3);
var offset = endData.length - 128;
var header = endData.toString('ascii', offset, offset += 3);
if (header !== 'TAG') {

@@ -35,31 +17,30 @@ throw new Error('Expected id3v1.1 header but was not found.');

var title = data.toString('ascii', offset, offset += 30);
self.emit('title', title.trim().replace(/\x00/g, ''));
var title = endData.toString('ascii', offset, offset += 30);
callback('title', title.trim().replace(/\x00/g, ''));
var artist = data.toString('ascii', offset, offset += 30);
self.emit('artist', artist.trim().replace(/\x00/g, ''));
var artist = endData.toString('ascii', offset, offset += 30);
callback('artist', artist.trim().replace(/\x00/g, ''));
var album = data.toString('ascii', offset, offset += 30);
self.emit('album', album.trim().replace(/\x00/g, ''));
var album = endData.toString('ascii', offset, offset += 30);
callback('album', album.trim().replace(/\x00/g, ''));
var year = data.toString('ascii', offset, offset += 4);
self.emit('year', year.trim().replace(/\x00/g, ''));
var year = endData.toString('ascii', offset, offset += 4);
callback('year', year.trim().replace(/\x00/g, ''));
var comment = data.toString('ascii', offset, offset += 28);
self.emit('comment', comment.trim().replace(/\x00/g, ''));
var comment = endData.toString('ascii', offset, offset += 28);
callback('comment', comment.trim().replace(/\x00/g, ''));
var track = data[data.length - 2];
self.emit('track', track);
var track = endData[endData.length - 2];
callback('track', track);
if (data[data.length - 1] in common.GENRES) {
var genre = common.GENRES[data[data.length - 1]];
self.emit('genre', genre);
if (endData[endData.length - 1] in common.GENRES) {
var genre = common.GENRES[endData[endData.length - 1]];
callback('genre', genre);
}
self.emit('done');
callback('done');
} catch (exception) {
self.emit('done', exception);
callback('done', exception);
}
}
});
}

@@ -1,128 +0,114 @@

var util = require('util');
var events = require('events');
var strtok = require('strtok');
var fs = require('fs');
var parser = require('./id3v2_frames');
var common = require('./common');
var Id3v2 = module.exports = function(stream) {
events.EventEmitter.call(this);
this.stream = stream;
this.parse();
};
util.inherits(Id3v2, events.EventEmitter);
Id3v2.prototype.parse = function() {
var self = this;
strtok.parse(self.stream, function(v, cb) {
module.exports = function (stream, callback) {
strtok.parse(stream, function (v, cb) {
try {
if (!v) {
cb.position = 'header';
cb.state = 0;
return new strtok.BufferType(10);
}
if (cb.position === 'header') {
if (v.toString('ascii', 0, 3) !== 'ID3') {
throw new Error('expected id3 header but was not found');
}
switch (cb.state) {
case -1: // skip
cb.state = 2;
return readFrameHeader(cb.header.major);
case 0: // header
if (v.toString('ascii', 0, 3) !== 'ID3') {
throw new Error('expected id3 header but was not found');
}
cb.header = {
version: '2.' + v[3] + '.' + v[4],
major: v[3],
unsync: strtok.BITSET.get(v, 5, 7),
xheader: strtok.BITSET.get(v, 5, 6),
xindicator: strtok.BITSET.get(v, 5, 5),
footer: strtok.BITSET.get(v, 5, 4),
size: strtok.INT32SYNCSAFE.get(v, 6)
};
cb.header = {
version: '2.' + v[3] + '.' + v[4],
major: v[3],
unsync: strtok.BITSET.get(v, 5, 7),
xheader: strtok.BITSET.get(v, 5, 6),
xindicator: strtok.BITSET.get(v, 5, 5),
footer: strtok.BITSET.get(v, 5, 4),
size: strtok.INT32SYNCSAFE.get(v, 6)
};
if (cb.header.xheader) {
cb.position = 'xheader';
return strtok.UINT32_BE;
}
if (cb.header.xheader) {
cb.state = 1;
return strtok.UINT32_BE;
}
//expect the first frames header next
cb.position = 'frameheader';
switch (cb.header.major) {
case 2:
return new strtok.BufferType(6);
case 3:
case 4:
return new strtok.BufferType(10);
default:
throw new Error('header version is incorrect');
}
}
// expect the first frames header next
cb.state = 2;
return readFrameHeader(cb.header.major);
if (cb.position === 'xheader') {
cb.position = 'frameheader';
//TODO: this will not work because we do not detect raw objects
//our code will fail on mp3's with xheaders
return new strtok.BufferType(v); //skip xheader
}
case 1: // xheader
cb.state = -1;
return new strtok.BufferType(v - 4);
if (cb.position === 'frameheader') {
cb.position = 'framedata';
var header = cb.frameHeader = {};
case 2: // frameheader
var header = cb.frameHeader = {};
switch (cb.header.major) {
case 2:
header.id = v.toString('ascii', 0, 3);
header.length = strtok.UINT24_BE.get(v, 3, 6);
break;
case 3:
header.id = v.toString('ascii', 0, 4);
header.length = strtok.UINT32_BE.get(v, 4, 8);
header.flags = readFrameFlags(v.slice(8, 10));
break;
case 4:
header.id = v.toString('ascii', 0, 4);
header.length = strtok.INT32SYNCSAFE.get(v, 4, 8);
header.flags = readFrameFlags(v.slice(8, 10));
break;
}
switch (cb.header.major) {
case 2:
header.id = v.toString('ascii', 0, 3);
header.length = strtok.UINT24_BE.get(v, 3, 6);
break;
case 3:
header.id = v.toString('ascii', 0, 4);
header.length = strtok.UINT32_BE.get(v, 4, 8);
header.flags = readFrameFlags(v.slice(8, 10));
break;
case 4:
header.id = v.toString('ascii', 0, 4);
header.length = strtok.INT32SYNCSAFE.get(v, 4, 8);
header.flags = readFrameFlags(v.slice(8, 10));
break;
}
// Last frame. Check first char is a letter, bit of defensive programming
if (header.id === '' || header.id === '\u0000\u0000\u0000\u0000'
|| 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.search(header.id[0]) === -1) {
callback('done');
return strtok.DONE;
}
cb.state++;
return new strtok.BufferType(header.length);
// Last frame. Check first char is a letter, bit of defensive programming
if (header.id === '' || header.id === '\u0000\u0000\u0000\u0000' || 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.search(header.id[0]) === -1) {
self.emit('done');
return strtok.DONE;
}
return new strtok.BufferType(header.length);
case 3: // framedata
cb.state = 2; // frameheader up next
var frame, encoding;
switch (cb.header.major) {
case 2:
frame = parser.readData(v, cb.frameHeader.id, null, cb.header.major);
callback(cb.frameHeader.id, frame);
return new strtok.BufferType(6);
case 3:
case 4:
if (cb.frameHeader.flags.format.unsync) {
v = common.removeUnsyncBytes(v);
}
if (cb.frameHeader.flags.format.data_length_indicator) {
v = v.slice(4, v.length);
}
frame = parser.readData(v, cb.frameHeader.id, cb.frameHeader.flags, cb.header.major);
callback(cb.frameHeader.id, frame);
return new strtok.BufferType(10);
}
}
if (cb.position === 'framedata') {
cb.position = 'frameheader';
var frame, encoding;
switch (cb.header.major) {
case 2:
frame = parser.readData(v, cb.frameHeader.id, null, cb.header.major);
self.emit(cb.frameHeader.id, frame);
return new strtok.BufferType(6);
case 3:
case 4:
if (cb.frameHeader.flags.format.unsync) {
v = common.removeUnsyncBytes(v);
}
if (cb.frameHeader.flags.format.data_length_indicator) {
v = v.slice(4, v.length); //TODO: do we need to do something with this?
}
frame = parser.readData(v, cb.frameHeader.id, cb.frameHeader.flags, cb.header.major);
self.emit(cb.frameHeader.id, frame);
return new strtok.BufferType(10);
}
}
} catch (exception) {
self.emit('done', exception);
callback('done', exception);
return strtok.DONE;
}
});
};
})
}
function readFrameFlags(b) {
function readFrameHeader (majorVer) {
switch (majorVer) {
case 2:
return new strtok.BufferType(6);
case 3:
case 4:
return new strtok.BufferType(10);
default:
throw new Error('header version is incorrect');
}
}
function readFrameFlags (b) {
return {

@@ -141,3 +127,3 @@ status: {

}
};
};
}
}

@@ -1,95 +0,78 @@

var util = require('util');
var events = require('events');
var strtok = require('strtok');
var common = require('./common');
var fs = require('fs');
var Id4 = module.exports = function(stream) {
events.EventEmitter.call(this);
this.stream = stream;
this.parse();
};
module.exports = function (stream, callback) {
strtok.parse(stream, function (v, cb) {
try {
// we can stop processing atoms once we get to the end of the ilst atom
if (cb.metaAtomsTotalLength >= cb.atomContainerLength - 8) {
callback('done');
return strtok.DONE;
}
util.inherits(Id4, events.EventEmitter);
Id4.prototype.parse = function() {
var self = this;
strtok.parse(self.stream, function(v, cb) {
try {
//the very first thing we expect to see is the first atom's length
// the very first thing we expect to see is the first atom's length
if (!v) {
cb.metaAtomsTotalLength = 0;
cb.position = 'atomlength';
cb.state = 0;
return strtok.UINT32_BE;
}
if (cb.position === 'skip') {
cb.position = 'atomlength';
return strtok.UINT32_BE;
}
switch (cb.state) {
case -1: // skip
cb.state = 0;
return strtok.UINT32_BE;
if (cb.position === 'atomlength') {
cb.position = 'atomname';
cb.atomLength = v;
return new strtok.StringType(4, 'binary');
}
case 0: // atom length
cb.atomLength = v;
cb.state++;
return new strtok.StringType(4, 'binary');
if (cb.position === 'atomname') {
cb.atomName = v;
case 1: // atom name
cb.atomName = v;
//meta has 4 bytes padding at the start (skip)
if (v === 'meta') {
cb.position = 'skip';
return new strtok.BufferType(4);
}
// meta has 4 bytes padding at the start (skip)
if (v === 'meta') {
cb.state = -1; // what to do for skip?
return new strtok.BufferType(4);
}
if (!~CONTAINER_ATOMS.indexOf(v)) {
cb.position = (cb.atomContainer === 'ilst') ? 'ilstatom' : 'skip';
return new strtok.BufferType(cb.atomLength - 8);
}
if (!~CONTAINER_ATOMS.indexOf(v)) {
// whats the num for ilst?
cb.state = (cb.atomContainer === 'ilst') ? 2 : -1;
return new strtok.BufferType(cb.atomLength - 8);
}
//dig into container atoms
cb.atomContainer = v;
cb.atomContainerLength = cb.atomLength;
cb.position = 'atomlength';
return strtok.UINT32_BE;
}
// dig into container atoms
cb.atomContainer = v;
cb.atomContainerLength = cb.atomLength;
cb.state--;
return strtok.UINT32_BE;
//we can stop processing atoms once we get to the end of the ilst atom
if (cb.metaAtomsTotalLength >= cb.atomContainerLength - 8) {
self.emit('done');
return strtok.DONE;
}
//only process atoms that fall under the ilst atom (metadata)
if (cb.position === 'ilstatom') {
cb.metaAtomsTotalLength += cb.atomLength;
var result = processMetaAtom(v, cb.atomName, cb.atomLength - 8);
if (result.length > 0) {
for (var i = 0; i < result.length; i++) {
self.emit(cb.atomName, result[i]);
case 2: // ilst atom
cb.metaAtomsTotalLength += cb.atomLength;
var result = processMetaAtom(v, cb.atomName, cb.atomLength - 8);
if (result.length > 0) {
for (var i = 0; i < result.length; i++) {
callback(cb.atomName, result[i]);
}
}
}
cb.position = 'atomlength';
return strtok.UINT32_BE;
cb.state = 0;
return strtok.UINT32_BE;
}
//if we ever get this this point something bad has happened
// if we ever get this this point something bad has happened
throw new Error('error parsing');
} catch (exception) {
self.emit('done', exception);
callback('done', exception);
return strtok.DONE;
}
});
};
})
}
function processMetaAtom(data, atomName, atomLength) {
function processMetaAtom (data, atomName, atomLength) {
var result = [];
var offset = 0;
//ignore proprietary iTunes atoms (for now)
// ignore proprietary iTunes atoms (for now)
if (atomName == '----') return result;

@@ -101,3 +84,3 @@

var content = (function processMetaDataAtom(data, type, atomName) {
var content = (function processMetaDataAtom (data, type, atomName) {
switch (type) {

@@ -140,8 +123,4 @@ case 'text':

'21': 'uint8'
};
}
var CONTAINER_ATOMS = [
'moov',
'udta',
'meta',
'ilst'];
var CONTAINER_ATOMS = ['moov', 'udta', 'meta', 'ilst'];

@@ -1,1 +0,181 @@

module.exports = require('./musicmetadata.js');
var util = require('util');
var events = require('events');
var common = require('./common');
var strtok = require('strtok');
var MusicMetadata = module.exports = function (stream) {
events.EventEmitter.call(this);
this.stream = stream;
this.parse();
};
util.inherits(MusicMetadata, events.EventEmitter);
MusicMetadata.prototype.parse = function () {
this.metadata = {
title: '',
artist: [],
albumartist: [],
album: '',
year: 0,
track: { no: 0, of: 0 },
genre: [],
disk: { no: 0, of: 0 },
picture: {}
}
this.aliased = {};
var self = this;
this.stream.once('data', function (result) {
var tag = common.detectMediaType(result.toString('binary'));
require('./' + tag)(self.stream, self.readEvents.bind(self));
// re-emitting the first data chunk so the
// parser picks the stream up from the start
self.stream.emit('data', result);
});
};
MusicMetadata.prototype.readEvents = function (event, value) {
// We only emit aliased events once the 'done' event has been raised,
// this is because an alias like 'artist' could have values split
// over many data chunks.
if (event === 'done') {
for (var alias in this.aliased) {
if (this.aliased.hasOwnProperty(alias)) {
var val;
if (alias === 'title' || alias === 'album' || alias === 'year') {
val = this.aliased[alias][0];
} else {
val = this.aliased[alias];
}
this.emit(alias, val);
if (this.metadata.hasOwnProperty(alias)) {
this.metadata[alias] = val;
}
}
}
this.emit('metadata', this.metadata);
this.emit('done', value);
return;
}
// lookup alias
var alias;
for (var i = 0; i < MAPPINGS.length; i++) {
for (var j = 0; j < MAPPINGS[i].length; j++) {
var cur = MAPPINGS[i][j];
if (cur.toUpperCase() === event.toUpperCase()) {
alias = MAPPINGS[i][0];
break;
}
}
}
// emit original event & value
if (event !== alias) {
this.emit(event, value);
}
// we need to do something special for these events
// TODO: parseInt will return NaN for strings
if (event === 'TRACKTOTAL' || event === 'DISCTOTAL') {
var evt;
if (event === 'TRACKTOTAL') evt = 'track';
if (event === 'DISCTOTAL') evt = 'disk';
var cleaned = parseInt(value)
if (!this.aliased.hasOwnProperty(evt)) {
this.aliased[evt] = { no: 0, of: cleaned };
} else {
this.aliased[evt]['of'] = cleaned;
}
}
// if the event has been aliased then we need to clean it before
// it is emitted to the user. e.g. genre (20) -> Electronic
if (alias) {
var cleaned = value;
if (alias === 'genre') cleaned = common.parseGenre(value);
if (alias === 'picture') cleaned = cleanupPicture(value);
if (alias === 'track' || alias === 'disk') {
cleaned = cleanupTrack(value);
if (this.aliased[alias]) {
this.aliased[alias].no = cleaned.no;
return;
} else {
this.aliased[alias] = cleaned;
return;
}
}
// many tagging libraries use forward slashes to separate artists etc
// within a string, this code separates those strings into an array
if (cleaned.constructor === String) {
// limit to these three aliases, we don't want to be splitting anything else
if (alias === 'artist' || alias === 'albumartist' || alias === 'genre') {
cleaned = cleaned.split('/');
if (cleaned.length === 1) cleaned = cleaned[0];
}
}
// if we haven't previously seen this tag then
// initialize it to an array, ready for values to be entered
if (!this.aliased.hasOwnProperty(alias)) {
this.aliased[alias] = [];
}
if (cleaned.constructor === Array) {
this.aliased[alias] = cleaned;
} else {
this.aliased[alias].push(cleaned);
}
}
}
function cleanupArtist (origVal) {
return origVal.split('/');
}
// TODO: a string of 1of1 would fail to be converted
// converts 1/10 to no : 1, of : 10
// or 1 to no : 1, of : 0
function cleanupTrack (origVal) {
var split = origVal.toString().split('/');
var number = parseInt(split[0], 10) || 0;
var total = parseInt(split[1], 10) || 0;
return { no: number, of: total }
}
function cleanupPicture (picture) {
var newFormat;
if (picture.format) {
var split = picture.format.toLowerCase().split('/');
newFormat = (split.length > 1) ? split[1] : split[0];
if (newFormat === 'jpeg') newFormat = 'jpg';
} else {
newFormat = 'jpg';
}
return { format: newFormat, data: picture.data }
}
// mappings for common metadata types(id3v2.3, id3v2.2, id4, vorbis, APEv2)
var MAPPINGS = [
['title', 'TIT2', 'TT2', '©nam', 'TITLE'],
['artist', 'TPE1', 'TP1', '©ART', 'ARTIST'],
['albumartist', 'TPE2', 'TP2', 'aART', 'ALBUMARTIST', 'ENSEMBLE'],
['album', 'TALB', 'TAL', '©alb', 'ALBUM'],
['year', 'TDRC', 'TYER', 'TYE', '©day', 'DATE', 'Year'],
['comment', 'COMM', 'COM', '©cmt', 'COMMENT'],
['track', 'TRCK', 'TRK', 'trkn', 'TRACKNUMBER', 'Track'],
['disk', 'TPOS', 'TPA', 'disk', 'DISCNUMBER', 'Disk'],
['genre', 'TCON', 'TCO', '©gen', 'gnre', 'GENRE'],
['picture', 'APIC', 'PIC', 'covr', 'METADATA_BLOCK_PICTURE',
'Cover Art (Front)', 'Cover Art (Back)'],
['composer', 'TCOM', 'TCM', '©wrt', 'COMPOSER']
];

@@ -1,31 +0,15 @@

var util = require('util');
var events = require('events');
var common = require('./common');
var strtok = require('strtok');
var MonkeysAudio = module.exports = function(stream) {
events.EventEmitter.call(this);
this.stream = stream;
this.parse();
};
module.exports = function (stream, callback) {
var bufs = [];
util.inherits(MonkeysAudio, events.EventEmitter);
MonkeysAudio.prototype.parse = function() {
var self = this,
bufs = [],
dataLen = 0;
//TODO: need to be able to parse the tag if its at the start of the file
this.stream.on('data', function(data) {
// TODO: need to be able to parse the tag if its at the start of the file
stream.on('data', function (data) {
bufs.push(data);
dataLen += data.length;
});
})
this.stream.onRealEnd(parse);
function parse() {
stream.onRealEnd(function () {
try {
var buffer = common.joinBuffers(bufs, dataLen);
var buffer = Buffer.concat(bufs);
var offset = buffer.length - 32;

@@ -77,13 +61,13 @@

}
self.emit(key, value);
callback(key, value);
}
self.emit('done');
callback('done');
return strtok.DONE;
} catch (exception) {
self.emit('done', exception);
callback('done', exception);
return strtok.DONE;
}
}
})
}

@@ -7,122 +7,108 @@ var fs = require('fs');

var Ogg = module.exports = function (stream) {
events.EventEmitter.call(this);
this.stream = stream;
this.parse();
};
util.inherits(Ogg, events.EventEmitter);
Ogg.prototype.parse = function () {
var self = this;
module.exports = function (stream, callback) {
var innerStream = new events.EventEmitter();
try {
// top level parser that handles the parsing of pages
strtok.parse(self.stream, function (v, cb) {
// top level parser that handles the parsing of pages
strtok.parse(stream, function (v, cb) {
try {
if (!v) {
cb.commentsRead = 0;
cb.position = 'header'; //read first OggS header
cb.state = 0;
return new strtok.BufferType(27);
}
if (cb.position === 'header') {
cb.header = {
type: v.toString('utf-8', 0, 4),
version: v[4],
packet_flag: v[5],
pcm_sample_pos: 'not_implemented',
stream_serial_num: strtok.UINT32_LE.get(v, 14),
page_number: strtok.UINT32_LE.get(v, 18),
check_sum: strtok.UINT32_LE.get(v, 22),
segments: v[26]
};
switch (cb.state) {
case 0: // header
cb.header = {
type: v.toString(0, 4),
version: v[4],
packet_flag: v[5],
pcm_sample_pos: 'not_implemented',
stream_serial_num: strtok.UINT32_LE.get(v, 14),
page_number: strtok.UINT32_LE.get(v, 18),
check_sum: strtok.UINT32_LE.get(v, 22),
segments: v[26]
}
cb.state++;
return new strtok.BufferType(cb.header.segments);
//read segment table
cb.position = 'segments';
return new strtok.BufferType(cb.header.segments);
}
case 1: // segments
var pageLen = 0;
for (var i = 0; i < v.length; i++) {
pageLen += v[i];
}
cb.state++;
return new strtok.BufferType(pageLen);
if (cb.position === 'segments') {
var pageLen = 0;
for (var i = 0; i < v.length; i++) {
pageLen += v[i];
}
cb.position = 'page_data';
return new strtok.BufferType(pageLen);
case 2: // page data
if (cb.header.page_number >= 1) {
innerStream.emit('data', new Buffer(v));
}
cb.state = 0;
return new strtok.BufferType(27);
}
if (cb.position === 'page_data') {
if (cb.header.page_number >= 1) {
innerStream.emit('data', new Buffer(v));
}
cb.position = 'header';
return new strtok.BufferType(27);
}
})
} catch (exception) {
callback('done', exception);
return strtok.DONE;
}
})
// Second level parser that handles the parsing of metadata.
// The top level parser emits data that this parser should
// handle.
strtok.parse(innerStream, function (v, cb) {
// Second level parser that handles the parsing of metadata.
// The top level parser emits data that this parser should
// handle.
strtok.parse(innerStream, function (v, cb) {
try {
if (!v) {
cb.position = 'type'; //read first OggS header
cb.commentsRead = 0;
cb.state = 0;
return new strtok.BufferType(7);
}
if (cb.position === 'type') {
cb.position = 'vendor_length';
return strtok.UINT32_LE;
}
switch (cb.state) {
case 0: // type
cb.state++;
return strtok.UINT32_LE;
if (cb.position === 'vendor_length') {
cb.position = 'vendor_string';
return new strtok.StringType(v);
}
case 1: // vendor length
cb.state++;
return new strtok.StringType(v);
if (cb.position === 'vendor_string') {
cb.position = 'user_comment_list_length';
return strtok.UINT32_LE;
}
case 2: // vendor string
cb.state++;
return strtok.UINT32_LE;
if (cb.position === 'user_comment_list_length') {
cb.commentsLength = v;
cb.position = 'comment_length';
return strtok.UINT32_LE;
}
case 3: // user comment list length
cb.commentsLength = v;
cb.state++;
return strtok.UINT32_LE;
if (cb.position === 'comment_length') {
cb.position = 'comment';
return new strtok.StringType(v);
}
case 4: // comment length
cb.state++;
return new strtok.StringType(v);
if (cb.position === 'comment') {
cb.commentsRead = cb.commentsRead || 0;
cb.commentsRead++;
case 5: // comment
cb.commentsRead++;
var idx = v.indexOf('=');
var key = v.slice(0, idx).toUpperCase();
var value = v.slice(idx+1);
var idx = v.indexOf('=');
var key = v.slice(0, idx).toUpperCase();
var value = v.slice(idx+1);
if (key === 'METADATA_BLOCK_PICTURE') {
value = common.readVorbisPicture(new Buffer(value, 'base64'));
}
self.emit(key, value);
if (key === 'METADATA_BLOCK_PICTURE') {
value = common.readVorbisPicture(new Buffer(value, 'base64'));
}
if (cb.commentsRead === cb.commentsLength) {
self.emit('done');
return strtok.DONE;
}
callback(key, value);
cb.position = 'comment_length';
return strtok.UINT32_LE;
if (cb.commentsRead === cb.commentsLength) {
callback('done');
return strtok.DONE;
}
cb.state--; // back to comment length
return strtok.UINT32_LE;
}
})
} catch (exception) {
self.emit('done', exception);
return strtok.DONE;
}
} catch (exception) {
callback('done', exception);
return strtok.DONE;
}
})
}
{
"name": "musicmetadata",
"description": "Music metadata library for node, using pure Javascript.",
"version": "0.2.2",
"version": "0.2.3",
"author": "Lee Treveil",

@@ -6,0 +6,0 @@ "dependencies": {

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc