Skip to content

Instantly share code, notes, and snippets.

@darkyen
Created January 4, 2013 06:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save darkyen/4450483 to your computer and use it in GitHub Desktop.
Save darkyen/4450483 to your computer and use it in GitHub Desktop.
A testrun
var fs = require('fs');
var Buffer = require('buffer').Buffer;
// Mp3Id3Reader
// Supports Id3v2.3.0 fully
// TODO: Add id3v2.2.0
// TODO: Add id3v2.4.0
var id3Reader = function(){
var self = this;
function id3Size( buffer ) {
var integer = ( ( buffer[0] & 0x7F ) << 21 ) |
( ( buffer[1] & 0x7F ) << 14 ) |
( ( buffer[2] & 0x7F ) << 7 ) |
( buffer[3] & 0x7F );
return integer;
}
var callback = null;
var PIC_TYPE = ["Other","32x32 pixels 'file icon' (PNG only)","Other file icon","Cover (front)","Cover (back)","Leaflet page","Media (e.g. lable side of CD)","Lead artist/lead performer/soloist","Artist/performer","Conductor","Movie/video screen capture",
"A bright coloured fish", //<--- Wait what the f ?
"Illustration","Band/artist logotype","Publisher/Studio logotype","Band/Orchestra","Composer","Lyricist/text writer","Recording Location","During recording","During performance"];
var 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","AlternRock","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 American","Cabaret","New Wave","Psychadelic","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 capella","Euro-House","Dance Hall"];
var TAGS = {
"AENC": "Audio encryption",
"APIC": "Attached picture",
"COMM": "Comments",
"COMR": "Commercial frame",
"ENCR": "Encryption method registration",
"EQUA": "Equalization",
"ETCO": "Event timing codes",
"GEOB": "General encapsulated object",
"GRID": "Group identification registration",
"IPLS": "Involved people list",
"LINK": "Linked information",
"MCDI": "Music CD identifier",
"MLLT": "MPEG location lookup table",
"OWNE": "Ownership frame",
"PRIV": "Private frame",
"PCNT": "Play counter",
"POPM": "Popularimeter",
"POSS": "Position synchronisation frame",
"RBUF": "Recommended buffer size",
"RVAD": "Relative volume adjustment",
"RVRB": "Reverb",
"SYLT": "Synchronized lyric/text",
"SYTC": "Synchronized tempo codes",
"TALB": "Album",
"TBPM": "BPM",
"TCOM": "Composer",
"TCON": "Genre",
"TCOP": "Copyright message",
"TDAT": "Date",
"TDLY": "Playlist delay",
"TENC": "Encoded by",
"TEXT": "Lyricist",
"TFLT": "File type",
"TIME": "Time",
"TIT1": "Content group description",
"TIT2": "Title",
"TIT3": "Subtitle",
"TKEY": "Initial key",
"TLAN": "Language(s)",
"TLEN": "Length",
"TMED": "Media type",
"TOAL": "Original album",
"TOFN": "Original filename",
"TOLY": "Original lyricist",
"TOPE": "Original artist",
"TORY": "Original release year",
"TOWN": "File owner",
"TPE1": "Artist",
"TPE2": "Band",
"TPE3": "Conductor",
"TPE4": "Interpreted, remixed, or otherwise modified by",
"TPOS": "Part of a set",
"TPUB": "Publisher",
"TRCK": "Track number",
"TRDA": "Recording dates",
"TRSN": "Internet radio station name",
"TRSO": "Internet radio station owner",
"TSIZ": "Size",
"TSRC": "ISRC (international standard recording code)",
"TSSE": "Software/Hardware and settings used for encoding",
"TYER": "Year",
"TXXX": "User defined text information frame",
"UFID": "Unique file identifier",
"USER": "Terms of use",
"USLT": "Unsychronized lyric/text transcription",
"WCOM": "Commercial information",
"WCOP": "Copyright/Legal information",
"WOAF": "Official audio file webpage",
"WOAR": "Official artist/performer webpage",
"WOAS": "Official audio source webpage",
"WORS": "Official internet radio station homepage",
"WPAY": "Payment",
"WPUB": "Publishers official webpage",
"WXXX": "User defined URL link frame"
};
var special_tags = {
'APIC':function(raw){
var frame = {
txt_enc : raw.readUInt8(0)
}
var pos = raw.toString('ascii',1,(raw.length < 24)?raw.length:24).indexOf('\0');
frame.mime = raw.toString('ascii',1,pos+1);
pos += 2;
frame.type = PIC_TYPE[raw.readUInt8(pos++)] || 'unknown';
var desc = raw.toString('ascii',pos,pos+64); // Max 64 char comment
var desc_pos = desc.indexOf('\0');
frame.desc = desc.substr(0,desc_pos);
pos += desc_pos + 1 ;// /0 is the last character which wont be counted xP
frame.img = fs.writeFileSync('art2.'+frame.mime.split('/')[1],raw.slice(pos,raw.length),'binary'); // Replace the art with unique ID .
return frame;
},
'TRCK':function(raw){
return raw.toString('ascii').replace(/\u0000/g,'') * 1;
},
'TYER':function(raw){
return raw.toString('ascii').replace(/\u0000/g,'') *1;
}
}
function parseTags (raw_tags,callback){
var max = raw_tags.length;
var pos = 0;
var parsed_tags = [];
while( pos < max-10){
var TAG = {
NAME : raw_tags.toString('ascii',pos,pos+4),
SIZE : raw_tags.readUInt32BE(pos+4)
};
if( special_tags[TAG.NAME] !== undefined){
TAG.content = special_tags[TAG.NAME](raw_tags.slice(pos+10,pos+10+TAG.SIZE)) || 'FUCK IN COMPLETE THE FUCKING FUNCTION';
}else{
TAG.content = raw_tags.toString('utf8',pos+10,pos+10+TAG.SIZE).replace(/\u0000/g,'');
}
if( TAGS[TAG.NAME] !== undefined && TAG.NAME !== 'PRIV'){
parsed_tags.push(TAG);
}
pos += (10+TAG.SIZE);
}
callback(parsed_tags);
};
function beginRead(err,fd,extern){
if(err){
console.dir(err);
return;
}
var id3 = {};
var _ext = extern;
var header = new Buffer(10);
fs.read(fd,header,0,10,0,function(err,bytesRead,buff){
if( buff.toString('ascii',0,3) != 'ID3'){
console.log("Not an id3v2 ");
return;
}
id3.head = {
size:id3Size(buff.slice(6,10)),
ver:'2.'+buff.readUInt8(3)+'.'+buff.readUInt8(4)
};
var raw_tags = new Buffer(id3.head.size);
fs.read(fd,raw_tags,0,id3.head.size,null,function(){
if( _ext === false ){
fs.close(fd,function(){
parseTags( raw_tags,function(parsed_tags){
id3.tags = parsed_tags;
callback(id3);
});
});
}else{
parseTags( raw_tags , function( parsed_tags ){
id3.tags = parsed_tags;
callback(id3);
});
}
});
});
}
/*
* @API - PUBLIC
*/
self.read = function (file,_callback,fd){
callback = _callback;
if(fd === undefined){
fs.open(file,'r',beginRead);
}else{
beginRead(null,fd,true);
}
}
};
/* Singular Mp3 Reader */
// Loads of constants for binary reading
var ext = {
_single_bit:[0xff,0x7f,0x3f,0x1f,0x0f,0x07,0x03,0x01],
ver:{
frame:{
0:'MPEG 2.5',
1:'Reserved',
2:'MPEG 2',
3:'MPEG 1'
},
layer:{
0:'reserved',
1:'Layer III',
2:'Layer II',
3:'Layer I'
},
bitrate:{
/* Defines in Mpeg version */
/* Mpeg 1 */
3:{
/* defines the mpeg layer in the version */
/* Layer 3 */
1:[0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,-1],
/* Layer 2 */
2:[0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,-1],
/* Layer 1 */
3:[0,32,64,96,128,160,192,224,256,288,320,352,384,416,488,-1]
},
/* Mpeg version 2 */
2:{
/* Layer 1 */
3:[0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,-1],
/* Layer 2 */
2:[0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,-1],
/* Layer 3 */
1:[0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,-1]
},
/* Mpeg version 2.5 */
0:{
/* Layer 1 */
3:[0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,-1],
/* Layer 2 */
2:[0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,-1],
/* Layer 3 */
1:[0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,-1]
}
},
samplerate:{
3:[44100,48000,32000,-1], /* Mpeg 1 */
2:[22050,24000,16000,-1], /* Mpeg 2 */
0:[11025,12000,8000] /* Mpeg 2.5 */
},
channel:[
'Stereo',
'Joint Stereo',
'Dual Channel',
'Single Channel'
],
jsMode:{
1:[['off','off'],['on','off'],['off','on'],['on','on']],
2:[[4,31],[8,31],[12,31],[16,31]],
3:[[4,31],[8,31],[12,31],[16,31]]
},
Emphasis:[
'none',
'50/15ms',
'reserved',
'CCIT J.17'
],
slot:{
3:4,
2:1,
1:1
},
samplePerFrame:{
3:{
3:384,
2:1152,
1:1152
},
2:{
3:384,
2:1152,
1:576,
},
0:{
3:384,
2:1152,
1:576
}
}
}
}
Buffer.prototype.ptr = 0; /* The position of read head in bits */
Buffer.prototype.getBits = function (bits){
var ptr = this.ptr;
this.ptr += bits;
var start = Math.floor(ptr/8);
var end = Math.floor(this.ptr/8);
var No_of_Bytes = Math.floor(bits/8);
var _offset_start = ( ptr % 8);
var _offset_end = 8 - (this.ptr % 8);
if( _offset_end === 8){
end --;
}
var integer = 0;
integer = (this[start] & ext._single_bit[_offset_start]);
var i = start + 1;
do{
if(i < end )
integer = ( (integer << 8) | this[i++] );
}while(i < end-1 );
if( No_of_Bytes > 0){
integer = (integer <<((8 - _offset_end%8))) | (this[end]>>(_offset_end%8));
}else{
integer = integer >> (_offset_end%8);
}
return integer;
};
Buffer.prototype.skip = function(bits){
// done :-) // why wrapper , i dont know :P
this.ptr += bits;
}
function mp3Reader (){
'use_strict';
var file = '';
var meta = null;
var fd = null;
var self = this;
var frames = 0;
var Length = 0;
var buffer ={
head:new Buffer(4),
body:null
}
var iter = 0;
function readStream(){
fs.read(fd,buffer.head,0,4,null,function(err,bytesRead){
if(err || iter > 5){
return;
}
iter++;
var parsed = {};
/* 0000 0000 0000 0000 0000 0000 0000 0000 *.
* Sample
* F F F
* Mpeg Version = 20th and 19th bit from right;
* 0000 0000 0001 1000 0000 0000 0000 0000
*
* MPEG LAYER = 17th and 18th bit from right
* 0000 0000 0000 0110 0000 0000 0000 0000
* ----------0000 0110
*
* MPEG CRC
* 0000 0000 0000 0001 0000 0000 0000 0000
* 0000 0001 - 0x1;
*
* MPEG BITRATE INFORMATION
* 0000 0000 0000 0000 1111 0000 0000 0000
* ------------------- 1111 0000 = F0
*
* Sampler information
*
* 0000 0000 0000 0000 0000 1100 0000 0000
* ------------------- 0000 1100 - C
*
* Padding bit
* 0000 0000 0000 0000 0000 0010 0000 0000
* --------------------0000 0010 - 2 ;
*
* Priv - 8th bit;
*
* Channel Mode
*
* 0000 0000 0000 0000 0000 0000 1100 0000
* ------------------------------1100 0000 - C0
*
* JS ext
* 0011 0000 - 30
*
* (C)
* 0000 1000 - 8
* (Origninal)
* 0000 0100 - 4
*/
//
// 1000 - 8 , 1001 - 9 , 1010 - A , 1011 - B , 1100 - 12
if ( (buffer.head[0] & 0xff) !== 255 && (buffer.head[1] & 0xf0 >>> 4) !== 15 ){
console.log(Length);
return; /* End of mp3 */
}
var ver = ( buffer.head[1] & 0x18)>>>3,
layer = ( buffer.head[1]& 0x6 )>>>1,
crc = ( buffer.head[1]&0x1 ),
bitrate = ( buffer.head[2] & 0xF0 )>>>4,
samplerate = ( buffer.head[2] & 0xC)>>>2,
padding = ( buffer.head[2] & 0x2)>>>1,
priv = ( buffer.head[2]& 0x1 ),
channel = ( buffer.head[3] & 0xC0 )>>>6,
jsExt = ( buffer.head[3]& 0x30)>>> 4, /* joing stereo ext */
cpyrite = ( buffer.head[3]& 0x9)>>> 3,
orig = ( buffer.head[3] & 0x4 )>>>2,
Emph = ( buffer.head[3] & 0x3 ),
sample_per_frame = ext.ver.samplePerFrame[ver][layer],
frameSize = 144 * ( ext.ver.bitrate[ver][layer][bitrate]*1000 / ext.ver.samplerate[ver][samplerate] ) ;
frameSize = Math.floor(frameSize) + ( (!!padding)?ext.ver.slot[layer]:0 );
Length += sample_per_frame/ext.ver.samplerate[ver][samplerate];
/*
console.log('Version : ',ext.ver.frame[ver]);
console.log('Layer : ',ext.ver.layer[layer]);
console.log('Bitrate : ',ext.ver.bitrate[ver][layer][bitrate],'kbps');
console.log('CRC : ',!!crc);
console.log('SampleRate : ',ext.ver.samplerate[ver][samplerate]);
console.log('Channel : ',ext.ver.channel[channel]);
console.log('Mode Ext : ',(channel===1)? ext.ver.jsMode[layer][jsExt] :null);
console.log('Copyrighted : ',!!cpyrite);
console.log('Original : ',!!orig);
console.log('Emph : ',ext.ver.Emphasis[Emph]);
console.log('Padding : ',!!padding);
console.log('FrameSize :',frameSize);
// */
var body = new Buffer( frameSize - 4 ); // we already read the 4 bytes :P
fs.read(fd,body,0,frameSize - 4 ,null,function(err,bytesRead){
if(err){
console.dir(err);
return;
}
if(crc){
var checksum = body.getBits(16);
}
if(channel < 2){
/* Not single */
/* Mpeg Layer III */
if( layer === 1){
var main_data_end = body.getBits(9),
priv_bits = body.getBits(3),
scfsi = [[
body.getBits(1),
body.getBits(1),
body.getBits(1),
body.getBits(1),/* shifting this will be an overkill son */
],[
body.getBits(1),
body.getBits(1),
body.getBits(1),
body.getBits(1),/* shifting this will be an overkill son */
]],
grn = [
[{},{}],[{},{}]
];
for(var gr = 0 ; gr < 2 ; gr++ ){
for( var ch = 0 ; ch < 2 ; ch++){
grn[gr][ch].part2_3_length = body.getBits(12);
grn[gr][ch].bigvalues = body.getBits(9);
grn[gr][ch].global_gain = body.getBits(8);
grn[gr][ch].scalefac_comp = body.getBits(4);
grn[gr][ch].blocksplit_flag = !!(body.getBits(1));
if(grn[gr][ch].blocksplit_flag){
grn[gr][ch].blocktype = body.getBits(2);
grn[gr][ch].switch_point = body.getBits(1);
grn[gr][ch].region = [];
grn[gr][ch].sub_block_gain = [];
for(var region = 0 ; region < 2 ; region++){
grn[gr][ch].region[region] = body.getBits(5);
}
for(var win = 0 ; win < 3 ; win ++){
grn[gr][ch].sub_block_gain[win] = body.getBits(3);
}
}else{
grn[gr][ch].region = [];
grn[gr][ch].reg = [];
for(var region = 0 ; region < 3 ; region++){
grn[gr][ch].region[region] = body.getBits(5);
}
grn[gr][ch].reg = {
region_address1:body.getBits(4),
region_address2:body.getBits(3)
};
}
}
}
for(var gr = 0; gr < 2 ; gr++ ){
for(var ch =0; ch < 2 ;ch++){
if( grn[gr][ch].blocksplit_flag === 1 && grn[gr][ch].blocktype == 2){
grn[gr][ch].scalefac = [];
for(cb = 0; cb < grn[gr][ch].switch_point ; cb++){
grn[gr][ch].scalefac[cb] = body.getBits(1);
}
}
}
}
}
console.log('Main Data Length',main_data_end);
console.log('scfsi',scfsi);
console.dir(grn[0][0]);
}else{
/* Yeah now its single :-( */
}
readStream();
})
});
}
self.load = function ( _file ) {
file = _file;
fs.open(file,'r',function(err,_fd){
if(err){
return;
}
fd = _fd;
new id3Reader().read(file,function(tags){
meta = tags;
console.dir(tags);
readStream();
},fd);
});
}
};
var f1 = new mp3Reader();
f1.load('dah.mp3');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment