简体   繁体   English

添加id3标签html5文件系统api

[英]adding id3 tags html5 filesystem api

I have scenario, where i am building a podcast web applications that allows to listen and store .mp3 podcast file. 我有一个场景,其中我正在构建一个播客Web应用程序,该应用程序允许侦听和存储.mp3播客文件。

I am trying to implement a basic web interface where someone can add the entire id3 tag from the client side (the file will be stored locally on the client side : this client is not like the everyone client but just the guy who gets the raw podcast file without any id3 tag preferably). 我正在尝试实现一个基本的Web界面,有人可以从客户端添加整个id3标签 (该文件将存储在客户端本地:此客户端与每个客户端都不一样,而只是获得原始播客的家伙文件,最好不要包含任何id3标签)。 He then hosts this one page locally adds the correct id3 tags and then copies these .mp3 do a WebDav folder. 然后,他在本地托管此页面,在本地添加正确的id3标签,然后将这些.mp3复制到WebDav文件夹中。

I do understand that edits needs to be done at server, but it would really helpful if it all can be done locally on the browser. 我确实知道需要在服务器上进行编辑,但是如果所有这些都可以在浏览器上本地完成,那将真的很有帮助。

Off course there is no ready library to edit files , so i decided to use the HTML5 filesystem api, ie drop the file into the virtual file system , edit it there are then copy it back to the local system. 当然,没有现成的库可以编辑文件,因此我决定使用HTML5文件系统api,即将文件拖放到虚拟文件系统中,在其中进行编辑,然后将其复制回本地系统。 (for copying there is a ready library FileSaver.js) . (要复制,有一个现成的库FileSaver.js)。

I have been able to do the following: 1) associate the mp3 file dropped at a drop zone to filesystem api using webkitGetAsEntry 我已经能够执行以下操作:1)使用webkitGetAsEntry将在放置区域放置的mp3文件与文件系统api相关联

2) copy this file then to the file system api. 2)然后将此文件复制到文件系统api。

part of the code looks like: 部分代码如下所示:

function onDrop(e) 
{
    e.preventDefault();
    e.stopPropagation();

    var items = e.dataTransfer.items;
    var files = e.dataTransfer.files;

    for (var i = 0, item; item = items[i]; ++i) 
    {
        // Skip this one if we didn't get a file.
        if (item.kind != 'file') {
            continue;
        }

        var entry = item.webkitGetAsEntry();

        if (entry.isFile) 
        {
            // Copy the dropped entry into local filesystem.
            entry.copyTo(cwd, null, function(copiedEntry) {
            //setLoadingTxt({txt: DONE_MSG});
            renderMp3Writer(entry);

My confusion is how do i add the entire id3 tag ? 我的困惑是如何添加整个 id3标签? . I am lost at this point as i am not sure about: 由于不确定,我现在迷路了:

1) can we add the entire id3 tag into the file from the fileWriter method? 1)我们可以从fileWriter方法将整个id3标签添加到文件中吗? 2) If yes would this be a binary edit or how?? 2)如果是,这将是一个二进制编辑或如何? .

Any Help would be useful. 任何帮助将是有用的。 tried the below but i am guessing i am wrong. 尝试了以下内容,但我想我错了。

var blob1 = new Blob(['ID3hTIT2ga'], {type: 'audio/mp3'});
fileWriter.write(blob1);

You need to build a ID3 buffer, then create a buffer large enough to hold both the ID3 and MP3 file, insert the ID3 and append the MP3 data. 您需要构建一个ID3缓冲区,然后创建一个足以容纳ID3和MP3文件的缓冲区,插入ID3并附加MP3数据。

For this you need the ID3 specification and use typed arrays with DataView to build your array. 为此,您需要ID3规范,并将带类型的数组与DataView一起使用来构建您的数组。

The ID3 overall structure is defined like this (see link above): ID3的总体结构是这样定义的(请参见上面的链接):

 +-----------------------------+
 |      Header (10 bytes)      |
 +-----------------------------+
 |       Extended Header       |
 | (variable length, OPTIONAL) |
 +-----------------------------+
 |   Frames (variable length)  |
 +-----------------------------+
 |           Padding           |
 | (variable length, OPTIONAL) |
 +-----------------------------+
 | Footer (10 bytes, OPTIONAL) |
 +-----------------------------+

At this point the buffer length is unknown so you need to do this in steps. 此时,缓冲区长度是未知的,因此您需要分步执行此操作。 There are several ways to do this, you can build up small buffer segments for each field, then sum them up into a single buffer. 有几种方法可以执行此操作,您可以为每个字段建立较小的缓冲区段,然后将它们汇总为一个缓冲区。 Or you can make a larger buffer you know can hold all the fields you want to include and copy the sum of field from that buffer to the final one. 或者,您可以制作一个更大的缓冲区,知道可以容纳要包含的所有字段,并将字段总和从该缓冲区复制到最后一个。

The latter tends to be simpler and as we're dealing with very small sizes this could be the best way (considering that each fragment in the first approach has their overheads). 后者往往更简单,并且由于我们正在处理非常小的尺寸,因此这可能是最好的方法(考虑到第一种方法中的每个片段都有其开销)。

So the first thing you need to do is to define the header. 因此,您需要做的第一件事就是定义标题。 The header is defined this way: 标头是通过以下方式定义的:

ID3v2/file identifier      "ID3"
ID3v2 version              $04 00
ID3v2 flags                %abcd0000  (note: bit-representation)
ID3v2 size             4 * %0xxxxxxx  (note: bit-representation/mask)

ID3 and version are fixed values (other versions exists of course, but lets follow the current). ID3和版本是固定值(当然也存在其他版本,但请遵循当前版本)。

You can probably ignore most of the flags, if not all, by setting them to 0. But check the docs for your use-case, for example if you want to use extended headers. 通过将它们设置为0,您可能会忽略大多数标志(如果不是全部的话)。但是请检查文档以了解用例,例如,如果要使用扩展标头。

Size is defined: 大小定义:

The ID3v2 tag size is stored as a 32 bit synchsafe integer (section 6.2), making a total of 28 effective bits (representing up to 256MB). ID3v2标签大小存储为32位同步安全整数(第6.2节),总共有28个有效位(最多256MB)。

The ID3v2 tag size is the sum of the byte length of the extended ID3v2标签大小是扩展字节长度的总和
header, the padding and the frames after unsynchronisation. 标头,填充和不同步后的帧。 If a 如果一个
footer is present this equals to ('total size' - 20) bytes, otherwise ('total size' - 10) bytes. 存在页脚,等于('total size'-20)个字节,否则等于('total size'-10)个字节。

An example how you can build your buffer. 一个如何构建缓冲区的示例。 First define a buffer big enough to hold all the data as well as a DataView: 首先定义一个足以容纳所有数据和DataView的缓冲区:

var id3buffer = new ArrayBuffer(1024),    // 1kb "space"
    view = new DataView(id3buffer);

The DataView defaults to big-endian which is perfect, so all we need to do now is to fill in the data where it should be. DataView默认为big-endian,这是完美的选择,因此我们现在要做的就是在应有的位置填充数据。 We can make a few helper methods to help us move position at the same time as we write. 我们可以制定一些辅助方法来帮助我们在编写文字的同时移动位置。 Positions for DataView are byte-bound: DataView的位置是字节绑定的:

 var pos = 0;    // global start position

function setU8(value) {
    view.setUint8(pos++, value)
}

function setU16(value) {
    view.setUint16(pos, value);
    pos += 2;
}

function setU32(value) {
    view.setUint32(pos, value);
    pos += 4;
}

etc. you can make helpers to write text unicode strings (see TextEncoder for example) and so forth. 等等。您可以使助手编写文本unicode字符串(例如,参见TextEncoder )等等。

To define the header, we can write in the "magic" word ID3. 要定义标题,我们可以写“魔术”字ID3。 You could convert a string, or since it's only 3 bytes also just write it straight-forward. 您可以转换一个字符串,或者因为它只有3个字节,也可以直接将其写入。 ID3 = 0x494433 in hex so: ID3 = 0x494433以十六进制表示,因此:

setU8(0x49);     // at pos 0
setU8(0x44);     // at pos 1
setU8(0x33);     // at pos 2

Since we made a wrapper we don't need to worry about the buffer position. 因为我们做了包装器,所以我们不必担心缓冲区的位置。

Then write in version (according to spec v.2.4.0 uses 0x0400 not using major version (2)): 然后写入版本(根据规范v.2.4.0使用0x0400而不使用主要版本(2)):

setU16(0x0400);  // default is big-endian so this works

Now you can continue with flags and size (see specs). 现在,您可以继续标记和大小(请参阅规格)。

When the ID3 header is filled up pos will now hold the total length. 当ID3标头填满后, pos现在将保留总长度。 So make a new buffer for ID3 tag and MP3 buffer: 因此,为ID3标签和MP3缓冲区新建一个缓冲区:

var mp3 = new ArrayBuffer(pos + mp3Buffer.byteLength),
    view8 = new Uint8Array(mp3);

The view8 view will allow us to do a simple copy to destination: view8视图将使我们能够简单地复制到目标:

// create a segment from the tag buffer that will fit target:
var segment = new Uint8Array(view.buffer, 0, n); // replace n with actual length
view8.set(segment, 0);
view8.set(mp3buffer, pos);

If everything went OK you now have a MP3 with a ID3 tag (remember to check for existing ID3s - you need to scan to to end). 如果一切顺利,您现在将拥有一个带有ID3标签的MP3(请记住要检查现有的ID3,您需要扫描到末尾)。

You can now send the ArrayBuffer to server, or convert to Blob for IndexedDB, or to an Object-URL if you want to present a link for download (none shown here as answer is becoming out-of-scope). 现在,您可以将ArrayBuffer发送到服务器,或转换为Blob for IndexedDB,或转换为Object-URL(如果要提供下载链接)(此处未显示,因为答案已超出范围)。

This should be enough to get you started - as said, you need to study the specs. 这应该足以让您入门-如前所述,您需要研究规格。 If you're not familiar with typed array , check those out as well. 如果您不熟悉类型化数组 ,请也检查一下。

Also see the site for other resources (frames etc.). 另请参见该网站以获取其他资源 (框架等)。

Sync-safe values 同步安全值

"MP3" files uses frames which starts with 11 bits, all set to 1. If the size field of the header happen to contain 11 bits set to 1, the decoder could mistakenly interpret it as sound data. “ MP3”文件使用以11位开头的帧,所有帧均设置为1。如果标头的size字段恰好包含设置为1的11位,则解码器可能会错误地将其解释为声音数据。 To avoid this the concept of sync-safe integers are used making sure that each byte's MSB (most signicant bit, bit 7) always is set to 0. The bit is moved to the left, the next byte is shifted one bit, for ID3 tag 4 times (hence the 4x %01111111). 为避免这种情况,使用了同步安全整数的概念,请确保将每个字节的MSB(最高有效位,位7)始终设置为0。对于ID3,该位向左移动,下一个字节向后移一位。标签4次(因此为4x%01111111)。

Here is how to encode and decode sync-safe integers using JavaScript (from Wikipedia C/C++ source ): 这是使用JavaScript(来自Wikipedia C / C ++的源代码 )对同步安全整数进行编码和解码的方法:

 // test values var value = 0xfffffff, sync = intToSyncsafe(value); document.write("<pre>Original size: 0x" + value.toString(16) + "<br>"); document.write("Synch-safe : 0x" + sync.toString(16) + "<br>"); document.write("Decoded value: 0x" + syncsafeToInt(sync).toString(16) + "</pre>"); function intToSyncsafe(value) { var out, mask = 0x7f; while(mask ^ 0x7fffffff) { out = value & ~mask; out <<= 1; out |= value & mask; mask = ((mask + 1) << 8) - 1; value = out; } return out } function syncsafeToInt(value) { var out = 0, mask = 0x7F000000; while (mask) { out >>= 1; out |= value & mask; mask >>= 8; } return out; } 

The sync-safe value would show the bits like: &b01111111011111110111111101111111 for the example value used in the demo above. 同步安全值将显示如下位: &b01111111011111110111111101111111 ,用于上面演示中的示例值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM