Подтвердить что ты не робот

Прочитайте теги id3 v2.4 с собственным Chrome Javascript/FileReader/DataView

На основе ответа ebidel можно прочитать теги id3v1, используя 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 и другие браузеры теперь внедрили DataView (меня интересует только Chrome). Мне интересно, если кто-то знает, как:

  • Чтение тегов с использованием встроенного DataView
  • Чтение тегов id3 v2.4 (включая обложку изображения APIC)

Дело в том, что у меня нет опыта работы с бинарными файлами, и я полностью не знаю, как перейти к правильной позиции тега, или о том, что такое маленький endian и long endian (или что-то еще). Мне просто нужен пример для одного тега - скажем название, тег TIT2, который, я надеюсь, поможет мне понять, как перейти в правильное положение и прочитать другие теги:

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);

Вот JSFiddle.


UPDATE

http://jsfiddle.net/s492L/3/

Я добавил getString, чтобы я мог прочитать первую строку и проверить, содержит ли она ID3. Теперь мне нужно найти позицию первого тега (TIT2) и длину переменной этой строки, а также проверить, является ли она версией 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

Возможные внешние источники:

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

Я использую PHP getid3 lib в данный момент...

http://getid3.sourceforge.net/

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

4b9b3361

Ответ 1

Используя код, который я нашел здесь: http://www.ulduzsoft.com/2012/07/parsing-id3v2-tags-in-the-mp3-files/, я перевел его в Javascript здесь: http://jsfiddle.net/eb7rrbw4/

Вот код, который я написал там:

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);
}

Вы должны увидеть заголовок и автора в журнале консоли. Однако посмотрите на текстовую функцию разбора, где кодировка определяет способ чтения строки. (поиск TODO). Также я не тестировал его с расширенными заголовками или use_synch true или тегом версии 3.

Ответ 2

Вы можете попробовать использовать парсер id3 в github.

Здесь вы обновили скрипт, который регистрирует объект тегов в консоли

При включенном id3.js все, что вам нужно сделать в коде:

function readFile(){
   id3(this.files[0], function(err, tags) {
       console.log(tags);
   })
}
document.getElementsByTagName('input')[0].addEventListener('change',readFile,false);

И вот объект tags, созданный 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"
  }
}

Надеюсь, это поможет!

Ответ 3

Частично правильный ответ (он корректно читает utf8 в формате id3v2.4.0, включая обложку)

То, что я задал в своем вопросе, вероятно, сейчас работает.

Мне нужен был очень грубый минимальный набор функций для обработки только id3v2.4.0, а также для синтаксического анализа прикрепленного изображения.

С помощью @Siderite Zackwehdex, ответ которого отмечен как правильный, я понял важную часть кода, который отсутствовал.

Поскольку у меня было время, чтобы играть с ним, я вносил различные изменения в код.

Прежде всего, извините сжатый script, но у меня есть лучший обзор общего кода. мне это легче. если у вас есть вопросы о коде, просто спросите.

Во всяком случае, я удалил uses_synch... очень сложно найти файл, который использует синхронизацию. То же самое для has_extended_hdr. Я также удалю поддержку id3v2.0.0 до id3v2.2.0. Я добавил проверку версии, что работает со всеми подвозами id3v2.

Основной вывод функции содержит массив со всеми тегами, внутри вы также можете найти id3v2 Version.Last, но я думаю, что полезно развернуть, я добавил пользовательский объект FRAME, который содержит пользовательские функции для FRAMES, отличных от textFrames. Теперь единственная функция внутри конвертирует изображение/обложку/APIC в простую в использовании base64 строку. Таким образом, массив можно сохранить как строку JSON.

Хотя для некоторых из вас важна совместимость, вышеупомянутый выделенный заголовок или синхронизация на самом деле является самой маленькой проблемой.

<сильные > ПРОБЛЕМЫ

Кодировка должна быть UTF-8, иначе вы получите странные текстовые paddings и  некоторые изображения обрабатываются только частично. в основном сломан.

Я хочу избежать использования внешней библиотеки или даже действительно большой функции только для этого... там должно быть какое-то умное простое решение для правильной обработки кодировки. ISO-8859-1, UTF-8, UTF-16.. big endian... безотносительно... # 00 vs # 00 00..

Если это будет сделано, поддержка может быть увеличена экспоненциально.

Я надеюсь, что у некоторых из вас есть решение для этого.

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);
}

DEMO

https://jsfiddle.net/2awq6pz7/