在JavaScript中压缩Blob

12

我需要使用ajax将一个blob发送给服务器,但它可能会变得相当大,我想减少上传时间。我已经尝试了jszip,但只给我返回一个空文件。我也尝试了btoa(),但编码后的值实际上只是[object Blob]而不是实际的blob数据。有什么方法可以压缩blob?

这是我用于jszip的代码:

var zip = new JSZip();
zip.file("recording.wav", blobFile);
var content = zip.generate();
我将“content”附加到一个FormData对象中并将其发送到服务器。 在服务器端,我解码了POST数据(从base64)。 zip文件打开得很好,但是 recording.wav 是一个长度为0的文件。
此外,我尝试使用在这里找到的LZW实现。 这是我用于压缩它的附加代码:
var compressed;
var reader = new FileReader();
reader.onload = function(event){
   compressed = LZW.compress(event.target.result);
};
reader.readAsText(blobFile);

然而,解压缩后返回null。


1
你尝试过如何使用jszip吗?如果大小是个问题,我不认为btoa是你想要使用的,它会产生Base64数据并使事情变得更大。 - mu is too short
问题已编辑以显示如何使用jszip。 - Fibericon
这是一个未定义类型的 Blob 对象。 - Fibericon
我尝试使用FileReader的readAsBinaryString()函数,但是压缩后的zip文件比未压缩的文件还要大。此外,生成的 recording.wav 不是一个有效的音频文件。 - Fibericon
你有没有尝试手动压缩一个 .wav 文件来确定是否值得花费精力进行压缩?许多文件格式在内部已经被压缩,因此对它们进行压缩是没有用的;这在音频、视频和其他“自然大型”格式中非常普遍。 - mu is too short
显示剩余2条评论
2个回答

13
注意:像音频文件这样的压缩最好使用专门针对该类型数据的算法,可能是一些有损压缩的算法。然而,知道在下面提供的合理无损实现的寻找之艰难,我非常担心很难找到一个在特定的数据类型中满足您需求的好的JavaScript实现。
无论如何,我也需要在JavaScript中进行压缩/解压缩,并且需要相同的算法在客户端(浏览器)和服务器端(node.js)上工作,并且需要处理非常大的文件。我曾经尝试过jszip,还尝试过LZW算法以及至少五六个其他算法,但都不能满足要求。我不记得每一个具体的问题是什么,但可以说惊人地难以在JavaScript中找到一个既好又快速的压缩/解压缩器,它可以在服务器和客户端上同时工作并处理大文件!我尝试了至少十几种不同的压缩算法实现,最终选择了这个——它从未让我失望!

更新

这是原始来源: https://code.google.com/p/jslzjb/source/browse/trunk/Iuppiter.js?r=2

由一个名叫Bear的人编写 - 感谢你,无论你是谁,你是最棒的。 这是LZJB: http://en.wikipedia.org/wiki/LZJB


更新2

  1. 修正了缺少分号的问题-不再出现对象无法使用函数错误。
  2. 此实现在数据长度小于约80个字符时停止工作。因此,我更新了示例以反映这一点。
  3. 意识到此版本传递的对象实际上公开了base64编码/解码方法,因此......
  4. 目前正在研究我们可以针对特定的Blob类型做些什么 - 例如,图像与音频等最佳方法是什么,这对JS开发者总体有用...将在此处更新所发现的内容。

更新 3

有一个比我下面发布的更好的 Iuppiter 原始源代码包装器。它由 cscott 编写,在这里可以在 github 上找到: https://github.com/cscott/lzjb

我将切换到这个包装器,因为它也支持流。

以下是在 Node.js 中使用 wav 文件的示例。但在复制示例之前,请让我先告诉你一个可怕的消息,至少对于我尝试的这个 wav 文件来说:

63128 Jun 19 14:09 beep-1.wav 
63128 Jun 19 17:47 beep-2.wav
89997 Jun 19 17:47 beep-2.wav.compressed 

它成功地再生了wav文件(并播放了)。然而,压缩后的文件似乎比原始文件还要大。好吧。无论如何,尝试在您的数据上运行可能是一个不错的选择,您永远不知道,也许会有好运气。以下是我使用的代码:

var fs = require('fs');
var lzjb = require('lzjb');

fs.readFile('beep-1.wav', function(err, wav){

    // base 64 first
    var encoded = wav.toString('base64');
    // then utf8 - you  don't want to go utf-8 directly
    var data = new Buffer(encoded, 'utf8');
    // now compress
    var compressed = lzjb.compressFile(data, null, 9);
    // the next two lines are unnecessary, but to see what kind of
    // size is written to disk  to compare with the original binary file
    var compressedBuffer = new Buffer(compressed, 'binary');
    fs.writeFile('beep-2.wav.compressed', compressedBuffer, 'binary', function(err) {});
    // decompress
    var uncompressed = lzjb.decompressFile(compressed);
    // decode from utf8 back to base64
    var encoded2 = new Buffer(uncompressed).toString('utf8');
    // decode back to binary original from base64
    var decoded = new Buffer(encoded2, 'base64');
    // write it out, make sure it is identical
    fs.writeFile('beep-2.wav', decoded, function(err) {});

});

在一天结束时,我认为对于大多数未被结果base64编码破坏的二进制数据实现任何压缩级别都将变得太困难。终端控制字符的日子仍然困扰着我们。您可以尝试升级到不同的基础,但这也有其风险和问题。
例如,请参阅: 什么是最有效的二进制到文本编码? 以及这个: 为什么人们不使用base128?

有一件事,就是在接受答案之前,请务必在您的 Blob 上尝试它,我主要用它来压缩 utf-8,并且我希望确保它适用于您特定的数据。

无论如何,这就是它!

/**
$Id: Iuppiter.js 3026 2010-06-23 10:03:13Z Bear $

Copyright (c) 2010 Nuwa Information Co., Ltd, and individual contributors.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice,
     this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.

  3. Neither the name of Nuwa Information nor the names of its contributors
     may be used to endorse or promote products derived from this software
     without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

$Author: Bear $
$Date: 2010-06-23 18:03:13 +0800 (星期三, 23 六月 2010) $
$Revision: 3026 $
*/
var fastcompressor = {};
(function (k) {
    k.toByteArray = function (c) {
        var h = [],
            b, a;
        for (b = 0; b < c.length; b++) a = c.charCodeAt(b), 127 >= a ? h.push(a) : (2047 >= a ? h.push(a >> 6 | 192) : (65535 >= a ? h.push(a >> 12 | 224) : (h.push(a >> 18 | 240), h.push(a >> 12 & 63 | 128)), h.push(a >> 6 & 63 | 128)), h.push(a & 63 | 128));
        return h
    };
    k.Base64 = {
        CA: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
        CAS: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
        IA: Array(256),
        IAS: Array(256),
        init: function () {
            var c;
            for (c = 0; 256 > c; c++) k.Base64.IA[c] = -1, k.Base64.IAS[c] = -1;
            c = 0;
            for (iS = k.Base64.CA.length; c < iS; c++) k.Base64.IA[k.Base64.CA.charCodeAt(c)] = c, k.Base64.IAS[k.Base64.CAS.charCodeAt(c)] = c;
            k.Base64.IA["="] = k.Base64.IAS["="] = 0
        },
        encode: function (c, h) {
            var b, a, d, e, m, g, f, l, j;
            b = h ? k.Base64.CAS : k.Base64.CA;
            d = c.constructor == Array ? c : k.toByteArray(c);
            e = d.length;
            m = 3 * (e / 3);
            g = (e - 1) / 3 + 1 << 2;
            a = Array(g);
            for (l = f = 0; f < m;) j = (d[f++] & 255) << 16 | (d[f++] & 255) << 8 | d[f++] & 255, a[l++] = b.charAt(j >> 18 & 63), a[l++] = b.charAt(j >> 12 & 63), a[l++] = b.charAt(j >> 6 & 63), a[l++] = b.charAt(j & 63);
            f = e - m;
            0 < f && (j = (d[m] &
                255) << 10 | (2 == f ? (d[e - 1] & 255) << 2 : 0), a[g - 4] = b.charAt(j >> 12), a[g - 3] = b.charAt(j >> 6 & 63), a[g - 2] = 2 == f ? b.charAt(j & 63) : "=", a[g - 1] = "=");
            return a.join("")
        },
        decode: function (c, h) {
            var b, a, d, e, m, g, f, l, j, p, q, n;
            b = h ? k.Base64.IAS : k.Base64.IA;
            c.constructor == Array ? (d = c, m = !0) : (d = k.toByteArray(c), m = !1);
            e = d.length;
            g = 0;
            for (f = e - 1; g < f && 0 > b[d[g]];) g++;
            for (; 0 < f && 0 > b[d[f]];) f--;
            l = "=" == d[f] ? "=" == d[f - 1] ? 2 : 1 : 0;
            a = f - g + 1;
            j = 76 < e ? ("\r" == d[76] ? a / 78 : 0) << 1 : 0;
            e = (6 * (a - j) >> 3) - l;
            a = Array(e);
            q = p = 0;
            for (eLen = 3 * (e / 3); p < eLen;) n = b[d[g++]] << 18 | b[d[g++]] <<
                12 | b[d[g++]] << 6 | b[d[g++]], a[p++] = n >> 16 & 255, a[p++] = n >> 8 & 255, a[p++] = n & 255, 0 < j && 19 == ++q && (g += 2, q = 0);
            if (p < e) {
                for (j = n = 0; g <= f - l; j++) n |= b[d[g++]] << 18 - 6 * j;
                for (b = 16; p < e; b -= 8) a[p++] = n >> b & 255
            }
            if (m) return a;
            for (n = 0; n < a.length; n++) a[n] = String.fromCharCode(a[n]);
            return a.join("")
        }
    };
    k.Base64.init();
    NBBY = 8;
    MATCH_BITS = 6;
    MATCH_MIN = 3;
    MATCH_MAX = (1 << MATCH_BITS) + (MATCH_MIN - 1);
    OFFSET_MASK = (1 << 16 - MATCH_BITS) - 1;
    LEMPEL_SIZE = 256;
    k.compress = function (c) {
        var h = [],
            b, a = 0,
            d = 0,
            e, m, g = 1 << NBBY - 1,
            f, l, j = Array(LEMPEL_SIZE);
        for (b = 0; b < LEMPEL_SIZE; b++) j[b] =
            3435973836;
        c = c.constructor == Array ? c : k.toByteArray(c);
        for (b = c.length; a < b;) {
            if ((g <<= 1) == 1 << NBBY) {
                if (d >= b - 1 - 2 * NBBY) {
                    f = b;
                    for (d = a = 0; f; f--) h[d++] = c[a++];
                    break
                }
                g = 1;
                m = d;
                h[d++] = 0
            }
            if (a > b - MATCH_MAX) h[d++] = c[a++];
            else if (e = (c[a] + 13 ^ c[a + 1] - 13 ^ c[a + 2]) & LEMPEL_SIZE - 1, l = a - j[e] & OFFSET_MASK, j[e] = a, e = a - l, 0 <= e && e != a && c[a] == c[e] && c[a + 1] == c[e + 1] && c[a + 2] == c[e + 2]) {
                h[m] |= g;
                for (f = MATCH_MIN; f < MATCH_MAX && c[a + f] == c[e + f]; f++);
                h[d++] = f - MATCH_MIN << NBBY - MATCH_BITS | l >> NBBY;
                h[d++] = l;
                a += f
            } else h[d++] = c[a++]
        }
        return h
    };
    k.decompress = function (c,
        h) {
        var b, a = [],
            d, e = 0,
            m = 0,
            g, f, l = 1 << NBBY - 1,
            j;
        b = c.constructor == Array ? c : k.toByteArray(c);
        for (d = b.length; e < d;) {
            if ((l <<= 1) == 1 << NBBY) l = 1, f = b[e++];
            if (f & l)
                if (j = (b[e] >> NBBY - MATCH_BITS) + MATCH_MIN, g = (b[e] << NBBY | b[e + 1]) & OFFSET_MASK, e += 2, 0 <= (g = m - g))
                    for (; 0 <= --j;) a[m++] = a[g++];
                else break;
                else a[m++] = b[e++]
        }
        if (!("undefined" == typeof h ? 0 : h)) {
            for (b = 0; b < m; b++) a[b] = String.fromCharCode(a[b]);
            a = a.join("")
        }
        return a
    }
})(fastcompressor);

如果我的记忆没错... 这是如何使用它的:

var compressed = fastcompressor.compress("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); // data less than this length poses issues.
var decompressed = fastcompressor.decompress(compressed);

Rgds....Hoonto/Matt

此外,我发布的内容是经过压缩但美化过的,并且稍作修改以便使用。请查看上面更新中的链接获取原始内容。

我现在正在处理这个。看起来很有前途,但在完成之前我不会知道结果。 - Fibericon
很好,希望它能够奏效,但请记住LZJB并不总是提供最佳压缩,但它非常快(最初用于Sun Microsystems ZFS)。此外,这里发布的版本和Iupiter版本都有base64编码/解码方法。但我没有在参数对象上公开它们。另外,正如您所知,base64编码可能会增加相当多的大小,因此编码后的压缩有时可能并不多。我们真正需要的是几个与此相同速度和灵活性的良好有损压缩算法,以完善JS的压缩/解压库。 - Matt Mullens
我无法使用您粘贴的代码(对象不是函数错误),但我确实使用了您链接的网站上的代码。问题是,它不接受blob。在Iuppiter.compress()中使用blob作为参数会返回一个空数组。您是如何传递blob的? - Fibericon
啊,是的 - 这就是你需要进行base64编码的地方,根据你的blob数据类型,你可能会失去相当多的压缩效率 - 如果是这种情况,你可能需要继续寻找更适合该类型数据的压缩算法,但问题在于Javascript作为浏览器中的语言,由于各种原因,它对于执行压缩算法来说是一种相当糟糕的语言,这也可能是我们看到缺乏这些实现的原因。尝试对数据进行base64编码,然后将其作为字符串传递给编码函数。 - Matt Mullens
如果您知道数据类型,例如wav,并且有一个本地实现的压缩器可以有效地克服base64编码膨胀(例如LossyWAV),并且可以使用llvm(http://llvm.org/)进行编译,那么您可能能够使用emscripten(https://github.com/kripken/emscripten/wiki)执行端口到Javascript。在这个类别中一些惊人的成功包括Qt库和llvm本身到Javascript的端口。所以这可能是一个选项。 - Matt Mullens
显示剩余7条评论

1

JS Zip会正常工作,只需更正您的语法即可。

function create_zip() {
    var zip = new JSZip();
    zip.add("recording.wav", blobfile);//here you have to give blobFile in the form of raw bits >> convert it in json notation.. or stream .. 
    zip.add("hello2.txt", "Hello Second World\n");//this is optional..
    content = zip.generate();
    location.href="data:application/zip;base64," + content;
}

你也可以添加多个文件...
只需将zip.file改为zip.add,
然后zip.generate()就会完成其余的工作,就像你已经做过的那样。
或者,参考旧帖子中的JavaScript最后一部分,并且如果你可以利用NativeBridge,则它将非常有帮助。在这篇文章中,用户使用Objective C记录了对象,你可以忽略它,但是使用JavaScript和Socket发送了该对象,你可以/可以利用...
我希望这样可以... :)

1
嗨@MarmiK,你能否更具体地说明一下你所提到的blogfile,并给出任何示例。 - Eduardo La Hoz Miranda
1
请告诉我您需要澄清哪个部分,我已经简要回答了问题。JS zip是一个库(只是为了澄清,我相信您知道它!),使用该库可以轻松地进行压缩。 - MarmiK
谢谢,@MarkmiK。通过查看jsziputils和数据的binary:true选项,我已经解决了我的问题。 - Eduardo La Hoz Miranda

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接