Lire ID3 v2. 4 tags avec natif Chrome Javascript / FileReader / DataView
, Basé sur la réponse de ebidel, on peut lire les tags id3v1 en utilisant jDataView:
document.querySelector('input[type="file"]').onchange = function (e) {
var reader = new FileReader();
reader.onload = function (e) {
var dv = new jDataView(this.result);
// "TAG" starts at byte -128 from EOF.
// See http://en.wikipedia.org/wiki/ID3
if (dv.getString(3, dv.byteLength - 128) == 'TAG') {
var title = dv.getString(30, dv.tell());
var artist = dv.getString(30, dv.tell());
var album = dv.getString(30, dv.tell());
var year = dv.getString(4, dv.tell());
} else {
// no ID3v1 data found.
}
};
reader.readAsArrayBuffer(this.files[0]);
};
Chrome et d'autres navigateurs ont maintenant implémenté DataView (Je ne m'intéresse qu'à Chrome). Je suis curieux si quelqu'un sait comment:
- lire les balises à l'aide du DataView natif
- lecture des balises id3 v2.4 (y compris L'image APIC 'coverart')
Le fait est que je n'ai aucune expérience avec les fichiers binaires, et je ne sais totalement pas comment sauter au position correcte de l'étiquette, ou ce que sont little endian et long endian (ou autre). J'ai juste besoin d'un exemple pour une balise-disons le titre, la balise TIT2
, qui, j'espère, m'aide à comprendre comment passer à la bonne position et à lire les autres balises aussi:
function readID3() {
//https://developer.mozilla.org/en-US/docs/Web/API/DataView
//and the position
//http://id3.org/id3v2.4.0-frames
//var id3={};
//id3.TIT2=new DataView(this.result,?offset?,?length?)
/*
?
var a=new DataView(this.result);
console.dir(String.fromCharCode(a.getUint8(0)));
?
*/
}
function readFile() {
var a = new FileReader();
a.onload = readID3;
a.readAsArrayBuffer(this.files[0]);
}
fileBox.addEventListener('change', readFile, false);
Voici le JSFiddle.
Mise à JOUR
J'ai ajouté getString
afin que je puisse lire la première ligne et vérifier si elle contient ID3.
Maintenant, j'ai besoin de trouvez la position de la première balise (TIT2) et la longueur 'variable' de cette chaîne et vérifiez également si c'est la version 2.4.
//Header
//ID3v2/file identifier "ID3"
//ID3v2 version $04 00
//ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
//ID3v2 size 4 * %0xxxxxxx
Possible externe sources:
Https://developer.mozilla.org/en-US/docs/Web/API/DataView
Http://id3.org/id3v2.4.0-frames
Http://id3.org/id3v2.4.0-structure
Http://blog.nihilogic.dk/2008/08/reading-id3-tags-with-javascript.html
Http://ericbidelman.tumblr.com/post/8343485440/reading-mp3-id3-tags-in-javascript
Https://github.com/aadsm/JavaScript-ID3-Reader
J'utilise le PHP getid3 lib pour le moment...
3 réponses
, en Utilisant le code que j'ai trouvé ici: http://www.ulduzsoft.com/2012/07/parsing-id3v2-tags-in-the-mp3-files/, je l'ai traduit en Javascript ici: http://jsfiddle.net/eb7rrbw4/
Voici le code tel que je l'ai écrit là:
DataView.prototype.getChar=function(start) {
return String.fromCharCode(this.getUint8(start));
};
DataView.prototype.getString=function(start,length) {
for(var i=0,v='';i<length;++i) {
v+=this.getChar(start+i);
}
return v;
};
DataView.prototype.getInt=function(start) {
return (this.getUint8(start) << 21) | (this.getUint8(start+1) << 14) | (this.getUint8(start+2) << 7) | this.getUint8(start+3);
};
function readID3(){
var a=new DataView(this.result);
// Parse it quickly
if ( a.getString(0,3)!="ID3" )
{
return false;
}
// True if the tag is pre-V3 tag (shorter headers)
var TagVersion = a.getUint8(3);
// Check the version
if ( TagVersion < 0 || TagVersion > 4 )
{
return false;
}
// Get the ID3 tag size and flags; see 3.1
var tagsize = a.getInt(6)+10;
//(a.getUint8(9) & 0xFF) | ((a.getUint8(8) & 0xFF) << 7 ) | ((a.getUint8(7) & 0xFF) << 14 ) | ((a.getUint8(6) & 0xFF) << 21 ) + 10;
var uses_synch = (a.getUint8(5) & 0x80) != 0 ? true : false;
var has_extended_hdr = (a.getUint8(5) & 0x40) != 0 ? true : false;
var headersize=0;
// Read the extended header length and skip it
if ( has_extended_hdr )
{
var headersize = a.getInt(10);
//(a.getUint8(10) << 21) | (a.getUint8(11) << 14) | (a.getUint8(12) << 7) | a.getUint8(13);
}
// Read the whole tag
var buffer=new DataView(a.buffer.slice(10+headersize,tagsize));
// Prepare to parse the tag
var length = buffer.byteLength;
// Recreate the tag if desynchronization is used inside; we need to replace 0xFF 0x00 with 0xFF
if ( uses_synch )
{
var newpos = 0;
var newbuffer = new DataView(new ArrayBuffer(tagsize));
for ( var i = 0; i < tagsize; i++ )
{
if ( i < tagsize - 1 && (buffer.getUint8(i) & 0xFF) == 0xFF && buffer.getUint8(i+1) == 0 )
{
newbuffer.setUint8(newpos++,0xFF);
i++;
continue;
}
newbuffer.setUint8(newpos++,buffer.getUint8(i));
}
length = newpos;
buffer = newbuffer;
}
// Set some params
var pos = 0;
var ID3FrameSize = TagVersion < 3 ? 6 : 10;
var m_title;
var m_artist;
// Parse the tags
while ( true )
{
var rembytes = length - pos;
// Do we have the frame header?
if ( rembytes < ID3FrameSize )
break;
// Is there a frame?
if ( buffer.getChar(pos) < 'A' || buffer.getChar(pos) > 'Z' )
break;
// Frame name is 3 chars in pre-ID3v3 and 4 chars after
var framename;
var framesize;
if ( TagVersion < 3 )
{
framename = buffer.getString(pos,3);
framesize = ((buffer.getUint8(pos+5) & 0xFF) << 8 ) | ((buffer.getUint8(pos+4) & 0xFF) << 16 ) | ((buffer.getUint8(pos+3) & 0xFF) << 24 );
}
else
{
framename = buffer.getString(pos,4);
framesize = buffer.getInt(pos+4);
//(buffer.getUint8(pos+7) & 0xFF) | ((buffer.getUint8(pos+6) & 0xFF) << 8 ) | ((buffer.getUint8(pos+5) & 0xFF) << 16 ) | ((buffer.getUint8(pos+4) & 0xFF) << 24 );
}
if ( pos + framesize > length )
break;
if ( framename== "TPE1" || framename== "TPE2" || framename== "TPE3" || framename== "TPE" )
{
if ( m_artist == null )
m_artist = parseTextField( buffer, pos + ID3FrameSize, framesize );
}
if ( framename== "TIT2" || framename== "TIT" )
{
if ( m_title == null )
m_title = parseTextField( buffer, pos + ID3FrameSize, framesize );
}
pos += framesize + ID3FrameSize;
continue;
}
console.log(m_title,m_artist);
return m_title != null || m_artist != null;
}
function parseTextField( buffer, pos, size )
{
if ( size < 2 )
return null;
var charcode = buffer.getUint8(pos);
//TODO string decoding
/*if ( charcode == 0 )
charset = Charset.forName( "ISO-8859-1" );
else if ( charcode == 3 )
charset = Charset.forName( "UTF-8" );
else
charset = Charset.forName( "UTF-16" );
return charset.decode( ByteBuffer.wrap( buffer, pos + 1, size - 1) ).toString();*/
return buffer.getString(pos+1,size-1);
}
, Vous devriez voir le titre et l'auteur dans le journal de la console. Regardez la fonction d'analyse de texte, cependant, où l'encodage détermine la façon de lire la chaîne. (recherche de TODO). Aussi je ne l'ai pas testé avec des en-têtes étendus ou uses_synch vrai ou tag de la version 3.
, Vous pouvez essayer d'utiliser id3 analyseur sur github.
Voici votre violon mis à jour qui enregistre l'objet tags dans la console
Avec le id3.js inclus, tout ce que vous devez faire dans votre code est:
function readFile(){
id3(this.files[0], function(err, tags) {
console.log(tags);
})
}
document.getElementsByTagName('input')[0].addEventListener('change',readFile,false);
Et voici le tags
objet créé par id3
:
{
"title": "Stairway To Heaven",
"album": "Stairway To Heaven",
"artist": "Led Zeppelin",
"year": "1999",
"v1": {
"title": "Stairway To Heaven",
"artist": "Led Zeppelin",
"album": "Stairway To Heaven",
"year": "1999",
"comment": "Classic Rock",
"track": 13,
"version": 1.1,
"genre": "Other"
},
"v2": {
"version": [3, 0],
"title": "Stairway To Heaven",
"album": "Stairway To Heaven",
"comments": "Classic Rock",
"publisher": "Virgin Records"
}
}
Espérons que cela aide!
Réponse Partiellement Correcte (il lit correctement utf8 formaté id3v2.4.0 y compris la couverture)
Les choses que j'ai posées dans ma question fonctionnent probablement maintenant.
Je voulais une fonction minimale très brute pour gérer uniquement id3v2. 4. 0 et analyser également l'image jointe.
Avec l'aide de @ Siderite Zackwehdex, dont la réponse est marquée comme correcte, j'ai compris la partie importante du code qui manquait.
Comme j'ai eu le temps de jouer avec j'ai fait diverses modifications au code.
Tout d'abord désolé pour le script compressé mais j'ai un meilleur aperçu du code global. il est plus facile pour moi. si vous avez des questions sur le code, demandez simplement.
Quoi qu'il en soit, j'ai supprimé le uses_synch
... il est vraiment difficile de trouver un fichier qui utilise la synchro. Même chose pour le has_extended_hdr
.J'ai aussi retirer le support pour id3v2.0.0 à id3v2.2.0. J'ai ajouté une vérification de version, celle-ci fonctionne avec toutes les subversions id3v2.
La sortie de la fonction principale contient un tableau avec toutes les balises, à l'intérieur, vous pouvez également trouver la Version id3v2.Enfin, mais je suppose utile pour développer, j'ai ajouté un objet Cadre personnalisé qui contient des fonctions personnalisées pour les cadres autres que textFrames. La fonction now only à l'intérieur convertit l'image/cover/APIC en une chaîne base64 facile à utiliser. Ce faisant, le tableau peut être stocké sous forme de chaîne JSON.
Alors que pour certains d'entre vous la compatibilité est importante, l'en-tête ou la synchronisation exended mentionnés ci-dessus sont en fait les plus petits problème.
Problèmes
L'encodage doit être UTF-8 sinon vous obtenez des rembourrages de texte étranges et certaines images ne sont analysées que partiellement. fondamentalement cassé.
Je veux éviter l'utilisation de la bibliothèque externe ou même une très grande fonction juste pour ça ... il doit y avoir une solution simple et intelligente pour gérer correctement l'encodage. ISO-8859-1, UTF-8, UTF-16 .. big endian... quoi... #00 vs #00 00 ..
Si cela est fait, le support peut être amélioré exponentiel.
J'espère que certains d'entre vous ont une solution pour que.
CODE
DataView.prototype.str=function(a,b,c,d){//start,length,placeholder,placeholder
b=b||1;c=0;d='';for(;c<b;)d+=String.fromCharCode(this.getUint8(a+c++));return d
}
DataView.prototype.int=function(a){//start
return (this.getUint8(a)<<21)|(this.getUint8(a+1)<<14)|
(this.getUint8(a+2)<<7)|this.getUint8(a+3)
}
var frID3={
'APIC':function(x,y,z,q){
var b=0,c=['',0,''],d=1,e,b64;
while(b<3)e=x.getUint8(y+z+d++),c[b]+=String.fromCharCode(e),
e!=0||(b+=b==0?(c[1]=x.getUint8(y+z+d),2):1);
b64='data:'+c[0]+';base64,'+
btoa(String.fromCharCode.apply(null,new Uint8Array(x.buffer.slice(y+z+++d,q))));
return {mime:c[0],description:c[2],type:c[1],base64:b64}
}
}
function readID3(a,b,c,d,e,f,g,h){
if(!(a=new DataView(this.result))||a.str(0,3)!='ID3')return;
g={Version:'ID3v2.'+a.getUint8(3)+'.'+a.getUint8(4)};
a=new DataView(a.buffer.slice(10+((a.getUint8(5)&0x40)!=0?a.int(10):0),a.int(6)+10));
b=a.byteLength;c=0;d=10;
while(true){
f=a.str(c);e=a.int(c+4);
if(b-c<d||(f<'A'||f>'Z')||c+e>b)break;
g[h=a.str(c,4)]=frID3[h]?frID3[h](a,c,d,e):a.str(c+d,e);
c+=e+d;
}
console.log(g);
}
DÉMO