Created
January 14, 2011 14:41
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
}); | |
} | |
}); |
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
This is for node.js