乐趣区

如何通过一个Txt文件批量生成MP3并压缩成Zipvue-node

看看效果叭

解压的文件

上传的文件格式

 测试 1 ||| 测试 1 的文字
测试 2 ||| 测试 2 的文字
测试 3 ||| 测试 3 的文字
测试 4 ||| 测试 4 的文字
测试 5 ||| 测试 5 的文字 

实现的逻辑如下

  • 上传文件
  • 解析 txt
  • 发送内容至百度语音合成
  • 生成文件夹放置本次合成的 mp3 文件,并压缩成 zip
  • 发送 zip 的地址给前端

上传文件

使用了 element-ui 的 el-upload 组件

<el-upload
      v-loading="loading"
      class="upload-demo"
      drag
      ref="upload"
      action="#"
      accept=".txt"
      :before-upload="onBeforeUploadImage"
      :http-request="UploadImage"
      :on-change="fileChange"
      :file-list="fileList"
    >
    <i class="el-icon-upload"></i>
    <div class="el-upload__text">
        将文件拖到此处,或
        <em> 点击上传 </em>
    </div>
    <div class="el-upload__tip" slot="tip"> 只能上传 txt 文件,且不超过 1M</div>
</el-upload>

在上传之前判断上传的文件是否符合要求

onBeforeUploadImage(file) {
    const isTxt = file.type === "text/plain";
    const isLt1M = file.size / 1024 / 1024 < 1;
    if (!isTxt) {this.$message.error("上传文件只能是 txt 格式!");
    }
    if (!isLt1M) {this.$message.error("上传文件大小不能超过 1MB!");
    }
    return isTxt && isLt1M;
}

一次只上传一个文件,在文件列表更新时先清除之前的文件

fileChange(file) {let obj = this.onBeforeUploadImage(file.raw);
    if (obj) {this.$refs.upload.clearFiles(); 
        this.fileList = [{name: file.name, url: file.url}];
    }
}

上传的主要函数

UploadImage(param) {
    this.loading = true;
    const formData = new FormData();
    formData.append("file", param.file);
    this.$axios({
        url: process.env.VUE_APP_BASE_API + "api/txtToMp3",
        method: "post",
        data: formData
    })
    .then(response => {if (response.data.code == 0) {
            this.loading = false;
            this.dialogVisible = true;
            this.url = response.data.data.url;
        }
    })
    .catch(error => {console.log(error);
    });
}

node 代码

用到的依赖项

const formidable = require("formidable"); // 获取上传的 txt,并保存
const path = require("path"); 
const AipSpeech = require("baidu-aip-sdk").speech; // 百度语音合成 sdk
const fs = require("fs"); 
const compressing = require("compressing"); // 压缩文件夹用 

接口代码

router.post("/txtToMp3", async function (req, res, next) {let form = new formidable.IncomingForm();
  form.encoding = "utf-8"; // 编码
  form.uploadDir = path.join(__dirname + "/../txt"); // 保存上传文件地址
  form.keepExtensions = true; // 保留后缀

  form.parse(req, function (err, fields, files) {
    let filename;
    filename = files.file.name;

    let nameArray = filename.split("."); // 分割
    let type = nameArray[nameArray.length - 1];
    let name = "";
    for (let i = 0; i < nameArray.length - 1; i++) {name = name + nameArray[i];
    }
    let date = new Date();
    let time = "_" + date.getTime();
    let avatarName = name + time + "." + type;
    let newPath = form.uploadDir + "/" + avatarName;
    fs.renameSync(files.file.path, newPath); // 移动文件
    fs.readFile(newPath, "utf-8", function (err, data) {if (err) {console.log(err);
        new Result(null, "读取失败").fail(res);
      } else {
        let client = new AipSpeech(
          0,
          "百度语音合成 key",
          "百度语音合成 secret"
        );

        let resultData = data.split("\n");
        let number = resultData.length;
        let formTime = new Date().getTime();
        let mp3FileDir = path.join(__dirname + "/../mp3_" + formTime);
        fs.mkdirSync(mp3FileDir);
        for (let i in resultData) {setTimeout(function(){if (resultData[i].indexOf("|||") != -1) {let text = resultData[i].split("|||")[1];
                // 语音合成,保存到本地文件
                client.text2audio(text, { spd: 4, per: 4}).then(function (result) {if (result.data) {let time = resultData[i].split("|||")[0] + "_voice";
                      let avatarName_mp3 = mp3FileDir + "/" + time + ".mp3";
                      fs.writeFileSync(avatarName_mp3, result.data);
                      number--;
                      if (number == 0) {
                        let zipFileName = "zip/mp3_" + formTime + ".zip";
                        compressing.zip
                          .compressDir(mp3FileDir, zipFileName)
                          .then(() => {
                            let item = {url: zipFileName,};
                            new Result(item, "压缩成功").success(res);
                          })
                          .catch((err) => {new Result(null, "压缩失败").fail(res);
                          });
                      }
                    } else {
                      // 合成服务发生错误
                      new Result(null, "合成失败").fail(res);
                    }
                  },
                  function (err) {console.log(err);
                  }
                );
              } else {new Result(null, "文件格式错误").fail(res);
              }   
            },i * 20)
        }
      }
    });
  });
});

PS:

在 node 部分,在判断需要合成的文件是否全部完成时,我是通过 number 的值等于 0 判断完成,不知道大佬们有啥好方法不?

退出移动版