zip文件中的节点存档器不完整文件

时间:2019-07-20 10:53:24

标签: node.js node-archiver

环境

node v10.16.0  
npm 6.9.0  
"archiver": "^3.0.3",
windows 10  

期望的行为

使用node-archiver将几个音频文件(从api请求中接收)压缩为一个zip文件,将其发送回客户端,然后从服务器中删除音频文件和zip文件。

实际行为

一切工作都包括将音频文件写入服务器,将它们添加到zip文件,将zip文件发送回客户端以及从服务器中删除文件。

唯一的问题是,从已下载的zip文件播放音频文件时,它们实际上是空的(即大小只有1或2kb)。

无需使用存档器,文件就可以毫无问题地写入服务器。

我实现存档程序的方式基于以下示例:

https://github.com/archiverjs/node-archiver

我尝试过的

var text_chunk_array = ["string1", "string2", "string3"];

// BEGIN map over the array and make api call for each value  
var promises = text_chunk_array.map(text_chunk => {

    return new Promise((resolve, reject) => {

        var textToSpeech = new TextToSpeechV1({
            iam_apikey: iam_apikey,
            url: tts_service_url
        });

        var synthesizeParams = {
            text: text_chunk,
            accept: 'audio/mp3',
            voice: 'en-US_AllisonV3Voice'
        };

        textToSpeech.synthesize(synthesizeParams, (err, audio) => {
            if (err) {
                console.log("oopsie, an error occurred: " + err);
                return reject(err);
            }
            resolve(audio);
        });

    });
});
// END map over the array and make api call for each value  

try {

    // wait for all results to be returned  
    var audio_files = await Promise.all(promises);
    var audio_files_length = audio_files.length;

    // write each result to an audio file  
    audio_files.forEach((audio, i) => {
        var write_stream = fs.createWriteStream(`${relPath}_${i}.mp3`);
        audio.pipe(write_stream);
    });

    // everything is good until this point    

    // archiver setup is based on examples here:  
    // https://github.com/archiverjs/node-archiver
    // create a file to stream archive data to  
    var output = fs.createWriteStream(`${relPath}.zip`);
    var archive = archiver('zip', {
        zlib: { level: 9 } // sets the compression level  
    });

    // listen for all archive data to be written
    // 'close' event is fired only when a file descriptor is involved
    output.on('close', function() {
        console.log(archive.pointer() + ' total bytes');
        console.log('archiver has been finalized and the output file descriptor has closed.');

        // download the zip file (using absPath)  
        res.download(`${absPath}.zip`, (err) => {
            if (err) {
                console.log(err);
            }
            // for each audio file  
            for (let i = 0; i < audio_files_length; i++) {
                // delete the audio file (using relPath)  
                fs.unlink(`${relPath}_${i}.mp3`, (err) => {
                    if (err) {
                        console.log(err);
                    }
                    console.log(`AUDIO FILE ${i} REMOVED!`);
                });
            }
            // delete the zip file (using relPath)
            fs.unlink(`${relPath}.zip`, (err) => {
                if (err) {
                    console.log(err);
                }
                console.log(`ZIP FILE REMOVED!`);
            });

        });

    });

    // This event is fired when the data source is drained no matter what was the data source.
    // It is not part of this library but rather from the NodeJS Stream API.
    // @see: https://nodejs.org/api/stream.html#stream_event_end
    output.on('end', function() {
        console.log('Data has been drained');

    });

    // good practice to catch warnings (ie stat failures and other non-blocking errors)
    archive.on('warning', function(err) {
        if (err.code === 'ENOENT') {
            // log warning
        } else {
            // throw error
            throw err;
        }
    });

    // good practice to catch this error explicitly
    archive.on('error', function(err) {
        throw err;
    });

    // pipe archive data to the file
    archive.pipe(output);

    // for each audio file created  
    for (let i = 0; i < audio_files_length; i++) {
        // append a file
        archive.file(`${relPath}_${i}.mp3`, { name: `${file_name}_${i}.mp3` });
    }

    // finalize the archive (ie we are done appending files but streams have to finish yet)
    // 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
    archive.finalize();

} catch (err) {
    console.log("oh dear, there was an error: " + err);
}

1 个答案:

答案 0 :(得分:0)

可能的解决方案

我调整了原始代码,这样就不会首先将单个文件写入服务器,而是只是迭代并附加了音频响应本身(我认为是Buffer对象-{ {3}}的类型为NodeJS.ReadableStream|FileObject|Buffer,我觉得很困惑)

audio_files.forEach((audio, i) => {
    archive.append(audio, { name: `${file_name}_${i}.mp3` });
});

效果更好-播放zip文件中的音频文件,但我的蜘蛛侠意识告诉我它们有些问题,音频播放头有些跳动,有些文件的末尾可能会被截断第一次播放。因此,仍然不确定这是否是正确的解决方案。

为了解决这种小截断,我通过api在每个音频文件的开头和结尾添加了1500ms暂停,只是为了确保保留所有音频。

下面的完整解决方案:

var text_chunk_array = ["string1", "string2", "string3"];

// BEGIN map over the array and make api call for each value  
var promises = text_chunk_array.map(text_chunk => {

    return new Promise((resolve, reject) => {

        var textToSpeech = new TextToSpeechV1({
            iam_apikey: iam_apikey,
            url: tts_service_url
        });

        var synthesizeParams = {
            text: text_chunk,
            accept: 'audio/mp3',
            voice: 'en-US_AllisonV3Voice'
        };

        textToSpeech.synthesize(synthesizeParams, (err, audio) => {
            if (err) {
                console.log("oopsie, an error occurred: " + err);
                return reject(err);
            }
            resolve(audio);
        });

    });
});
// END map over the array and make api call for each value  

try {

    // wait for all results to be returned  
    var audio_files = await Promise.all(promises);
    var audio_files_length = audio_files.length;

    // archiver setup is based on examples here:  
    // https://github.com/archiverjs/node-archiver
    // create a file to stream archive data to  
    var output = fs.createWriteStream(`${relPath}.zip`);
    var archive = archiver('zip', {
        zlib: { level: 9 } // sets the compression level  
    });

    // for each audio file    
    audio_files.forEach((audio, i) => {
        // append the buffer to the archive, per archiver examples
        archive.append(audio, { name: `${file_name}_${i}.mp3` });
    });

    // listen for all archive data to be written
    // 'close' event is fired only when a file descriptor is involved
    output.on('close', function() {
        console.log(archive.pointer() + ' total bytes');
        console.log('archiver has been finalized and the output file descriptor has closed.');

        // download the zip file (using absPath)  
        res.download(`${absPath}.zip`, (err) => {
            if (err) {
                console.log(err);
            }
            // delete the zip file (using relPath)
            fs.unlink(`${relPath}.zip`, (err) => {
                if (err) {
                    console.log(err);
                }
                console.log(`ZIP FILE REMOVED!`);
            });

        });

    });

    // good practice to catch warnings (ie stat failures and other non-blocking errors)
    archive.on('warning', function(err) {
        if (err.code === 'ENOENT') {
            // log warning
        } else {
            // throw error
            throw err;
        }
    });

    // good practice to catch this error explicitly
    archive.on('error', function(err) {
        throw err;
    });

    // pipe archive data to the file
    archive.pipe(output);

    // finalize the archive (ie we are done appending files but streams have to finish yet)
    // 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
    archive.finalize();

} catch (err) {
    console.log("oh dear, there was an error: " + err);
}