环境
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);
}
答案 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);
}