Прочитайте теги 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
Ответы
Ответ 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/