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:

  1. lire les balises à l'aide du DataView natif
  2. 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

Http://jsfiddle.net/s492L/3/

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...

Http://getid3.sourceforge.net/

Http://getid3.sourceforge.net/source2/module.tag.id3v2.phps

36
demandé sur Community 2013-11-26 13:08:21

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.

2
répondu Siderite Zackwehdex 2016-03-29 17:48:15

, 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!

3
répondu Vikram Deshmukh 2015-05-28 17:13:55

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

Https://jsfiddle.net/2awq6pz7/

0
répondu cocco 2016-04-28 22:48:28