Skip to content

Instantly share code, notes, and snippets.

@san1t1
Created January 14, 2011 14:41
Show Gist options
  • Save san1t1/779682 to your computer and use it in GitHub Desktop.
Save san1t1/779682 to your computer and use it in GitHub Desktop.
get a JSON array of mp3 tags. Also gets pictures out. Pretty hacky.
var crypto = require("crypto");
var readId3 = function(buff){
var out = {};
if (String.fromCharCode(buff[0], buff[1], buff[2]) != "ID3"){
throw ("no id3 v2 tag")
}
out.version = "v2."+ buff[3] + "." + buff[4];
//flags
var flags = buff[5];
if (flags !== 0){throw ("tags with flags not supported");}
var size = (buff[6] << 24) | (buff[7] << 16) | (buff[8] << 8) | (buff[9]);
//console.log("id3 tag size is ", size);
var i = 10;
while (i-size<-255){
var tn = buff.toString("utf8", i,i+4);
if (tn.substring(0,1) != "T" && tn.substring(0,1) != "A"){break;}
//clearly this can be much greater than the total tag size.
//this is needed for the case where there is a picture. need to tidy this to identify accurately when we've finished reading tags.
//by checking for footer.
var framesize = (buff[i+4] << 24) | (buff[i+5] << 16) | (buff[i+6] << 8) | (buff[i+7]);
if (tn != "APIC"){
var tv = buff.toString("utf8", i+11,i+10+framesize);
out[tn] = tv;
}
else{
//console.log("found picture tag");
out.APIC = pic(buff.slice(i+11, i+10+framesize));
}
i= i+10+framesize;
}
out = readmp3(buff, out);
return out;
}
var pic = function(buff){
var out = {};
var i=0;
while (buff[i] != 0){i++;}
out.mime = buff.toString("utf8", 0, i);
// i don't care about this value. but interestingly, according to the spec, if this value is 17 (or hex 11), the picture
// is of type "A bright coloured fish" Who knew.
out.picType = buff[i++];
i++;
var j = i;
while (buff[i] != 0){i++;}
out.description = buff.toString("utf8",j,i);
out.picture = buff.slice(i+1);
out.hash = crypto.createHash("md5").update(out.picture).digest("hex");
return out;
};
var bitRates =[0,32000,40000,48000,56000,64000,80000,96000,112000,128000,160000,192000,224000,256000,320000,"bad"];
var sampleRates = [44100,48000,32000,"reserved"];
var channels = ["Stereo","Joint Stereo", "Dual", "Mono"]
var readmp3 = function(buff, out){
for (var i=0; i<buff.length; i++){
if (buff[i] == 255 && buff[i+1].toString(2).substring(0,7)=="1111101"){
var f = buff[i+1].toString(2);
if (f.substring(3,5) !="11" || f.substring(5,7) !="01"){
throw ("not an mp3 file, in probability ")
}
var f = buff[i+2].toString(2);
out.SampleRate = sampleRates[parseInt(f.substring(4,6),2)]
out.VBR = false;
if (isVBR(buff, i)){
out.VBR = true;
var x = i + (buff.toString('utf8', i, i+200).indexOf("Xing"))
/*for (j=1; j<200; j++){
console.log(j, buff[x+j], String.fromCharCode(buff[x+j]));
}*/
/*
var g = buff[x+7].toString(2);
console.log("flags", g);
var hasFrames = g.substring(0,1) == "1";
var hasBytes = g.substring(1,2) == "1";
var hasToc = g.substring(2,3) == "1";
var hasScale = g.substring(3,4) == "1";
var y = 0;
*/
x = x+8;
var framecount = (buff[x] << 24) | (buff[x+1] << 16) | (buff[x+2] << 8) | (buff[x+3]);
//nb this works for STEREO MP3
out.Duration = Math.floor((1152 * framecount)/out.SampleRate);
}
else{
out.Bitrate = bitRates[parseInt(f.substring(0,4),2)]
out.Duration = Math.floor((buff.length -i)/(out.Bitrate/8));
}
var f = buff[i+3].toString(2);
out.Channel = channels[parseInt(f.substring(0,2),2)];
break;
}
}
return out;
}
//nb only works if Xing frame is found - which is normal.
var isVBR = function(buff, i){
return buff.toString('utf8', i, i+100).indexOf("Xing") > -1;
}
//USAGE
var fs = require("fs");
fs.readFile("/Volumes/MEDIA/Music/Vocal/Bonnie Prince Billy/Little Joya/02 - Joya.mp3", function(err, data){
var tags = readId3(data);
if (tags.APIC){
var ext=".jpg";
switch (tags.APIC.mime){
case "image/png":
ext = ".png";
break;
case "image/gif":
ext=".gif";
break;
}
//i like writing filenames using the md5 hash - means i can guarantee uniqueness
//you may wish to use tags.APIC.description which is normally the name of the file used to create the tag.
fs.writeFile(tags.APIC.hash +".gif", tags.APIC.picture, function(err){
delete tags.APIC.picture;
console.log(JSON.stringify(tags));
});
}
});
@san1t1
Copy link
Author

san1t1 commented Jan 15, 2011

Made my head hurt by going through MP3 header frames to dig out bit rate, sample rate, and calculate duration.
Needs some stress testing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment