使用原生 Chrome Javascript/FileReader/DataView 读取 id3 v2.4 标签
Posted
技术标签:
【中文标题】使用原生 Chrome Javascript/FileReader/DataView 读取 id3 v2.4 标签【英文标题】:Read id3 v2.4 tags with native Chrome Javascript/FileReader/DataView 【发布时间】:2013-12-11 07:54:24 【问题描述】:根据ebidel的回答,可以使用jDataView读取id3v1标签:
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 图像'coverart')
关键是我对二进制文件没有经验,完全不知道如何跳转到正确的标签位置,或者什么是小端和长端(或其他)。我只需要一个标签的例子——比如说标题,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。
更新
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 库...
http://getid3.sourceforge.net/
http://getid3.sourceforge.net/source2/module.tag.id3v2.phps
【问题讨论】:
这有帮助吗? github.com/antimatter15/js-id3v2 我正在寻找原生函数...反物质使用了很多 polyfills...这使得它非常缓慢且不稳定。它在许多浏览器上都运行良好...但我只需要它用于 chrome .我只想将最新的 js 1.7+ 用于 ajax filereader dataview 等重要功能......反物质的代码适用于一个文件......但如果你需要处理多个文件,这不是一个好主意。跨度> 加上使用 chrome,您可以使用持久存储,从而操作大文件。有了更多知识,您还可以将这些标签写入文件。还可以考虑 mp4 格式。 我在 Node.js 中使用它取得了很大的成功,也许你可以通过 browserify 运行它? github.com/leetreveil/musicmetadata 因为它需要一个可读流,所以你必须找到一种方法将数据打包到 ReadableStream 中。您可以使用 XHR 获取 mp3 并将 responseType 设置为 'arraybuffer' 以使用原始字节。 这方面的好处是它可以在客户端运行......如果客户端可以做到这一点,为什么要在服务器上使用节点来计算呢???? 【参考方案1】:您可以尝试使用id3 parser on github。
Here's your updated fiddle that logs the tags object in the console
包含id3.js 后,您只需在代码中执行以下操作:
function readFile()
id3(this.files[0], function(err, tags)
console.log(tags);
)
document.getElementsByTagName('input')[0].addEventListener('change',readFile,false);
这里是id3
创建的tags
对象:
"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"
希望这会有所帮助!
【讨论】:
***.com/a/36346510/2450730 我希望我们可以进一步改进这个最小功能【参考方案2】:使用我在这里找到的代码: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);
您应该在控制台日志中看到标题和作者。不过,请查看 parse text 函数,其中的编码决定了读取字符串的方式。 (搜索待办事项)。此外,我还没有使用扩展标头或 uses_synch true 或标记版本 3 对其进行测试。
【讨论】:
***.com/a/36346510/2450730 我希望我们可以进一步改进这个最小功能【参考方案3】:部分正确答案(正确读取 utf8 格式的 id3v2.4.0,包括封面)
我在问题中提出的问题现在可能有效。
我想要一个非常粗糙的最小函数集来仅处理 id3v2.4.0 & 并解析附加的图像。
在@Siderite Zackwehdex 的帮助下,答案被标记为正确,我理解了缺少的代码的重要部分。
因为我有一些时间玩它,所以我对代码进行了各种修改。
首先对压缩脚本感到抱歉,但我对整个代码有更好的了解。对我来说更容易。如果您对代码有任何疑问,请直接询问。
无论如何,我删除了uses_synch
... 真的很难找到使用同步的文件。 has_extended_hdr
也一样。我也删除了对 id3v2.0.0 到 id3v2.2.0 的支持。我添加了一个版本检查,它适用于所有 id3v2 子版本。
主函数输出包含一个包含所有标签的数组,在里面你还可以找到 id3v2 Version.Last,但我想扩展很有用,我添加了一个自定义 FRAME 对象,其中包含除 textFrames 之外的 FRAMES 自定义函数。现在唯一的内部函数将图像/封面/APIC 转换为易于使用的 base64 字符串。这样做可以将数组存储为 JSON 字符串。
虽然对你们中的一些人来说兼容性很重要,但上述扩展标头或同步实际上是最小的问题。
问题
编码必须是 UTF-8 否则你会得到奇怪的文本填充和 某些图像仅被部分解析。基本坏了。
我想避免使用外部库甚至是一个非常大的函数……需要一些智能简单的解决方案来正确处理编码。 ISO-8859-1,UTF-8,UTF-16 .. big endian... 随便... #00 vs #00 00 ..
如果这样做,支持可以成倍提高。
我希望你们中的一些人对此有解决方案。
代码
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);
演示
https://jsfiddle.net/2awq6pz7/
【讨论】:
以上是关于使用原生 Chrome Javascript/FileReader/DataView 读取 id3 v2.4 标签的主要内容,如果未能解决你的问题,请参考以下文章
如何在台式机上调试 Android 的原生浏览器(不是 Chrome)?
使用chrome开发者工具在原生安卓应用中调试WebView
使用Chrome开发者工具研究JavaScript里函数的原生实现