学会用GPT-SoVITS语音克隆,你就是群里最靓的仔

前言

游戏副本打不过,大佬不带怎么办?

群里打字问问题,直接冷场怎么办?

压力怪不断骚扰,队友不帮怎么办?

公司老板审批慢,已读不回怎么办?

售后敷衍不退款,只发红包怎么办?

教学视频没人看,腼腆害羞怎么办?

俗话说得好,女留微信男自强,但如果学会了用GPT-SoVITS语音克隆,就能1分钟内实现将文本转成指定人的语音(TTS),不是声优请不起,而是语音克隆更有性价比,叫一声哥哥,就能让群友主动帮你解决所有疑难杂症!无论是在面对高难度的游戏副本,还是在群聊中遭遇冷场,亦或是在技术交流中感到力不从心,GPT-SoVITS 语音克隆技术都将成为你的得力助手。

本文将通过简单的实践演示,让你轻松掌握一种新颖而引人入胜的沟通方式,使你在社交中游刃有余,让你不再因为技术问题而束手无策,不再因为弱小在群里尴尬无言,更不再畏惧孤军奋战对抗压力怪,用一种崭新的方式改变人际交往关系,为你的社交体验赋予更丰富的色彩与乐趣。

阶段一:训练TTS模型

安装依赖

前往GPT-SoVITS地址,按照README提示,根据自身情况安装依赖(本文以MacOS M1为例子)

仓库更新比较频繁,请仔细阅读README,如果卡住无法继续,可以先去仓库issue搜报错,实在不行再去提issue,基本当天就能有回复

毕竟每个人遇到的报错都不尽相同:没装python、没装conda、没装pretrained models、github连不上...

bash 复制代码
cd Desktop
git clone https://github.com/RVC-Boss/GPT-SoVITS.git
cd GPT-SoVITS

conda create -n GPTSoVits python=3.9
conda activate GPTSoVits
bash install.sh
brew install ffmpeg

pip install -r requirements.txt
pip uninstall torch torchaudio
pip3 install --pre torch torchaudio --index-url https://download.pytorch.org/whl/nightly/cpu

启动项目

复制代码
python3 webui.py

准备人物音频文件

复制代码
/Users/USER_NAME/Desktop/GPT-SoVITS/DATA/Nemesis/20240127-203452.wav

去噪(音频如果没有背景噪音可以直接下一步)

勾选"是否开启UVR5-WebUI",稍等片刻会自动开启一个新页面,点击转换

输出的去噪文件在output/uvr5_opt,新建一个DATA文件夹,里面再建一个模型名文件夹Nemesis,把降噪后的音频文件放进去

复制代码
/Users/USER_NAME/Desktop/GPT-SoVITS/DATA/Nemesis/20240127-203452.wav.reformatted.wav_main_vocal.wav

语音切割

"音频自动切分输入路径" 填 DATA/Nemesis,点击"开启语音切割"

切割后的分片音频文件在/Users/USER_NAME/Desktop/GPT-SoVITS/output/slicer_opt

批量ASR

"批量ASR(中文only)输入文件夹路径" 输入 output/slicer_opt,点开启离线批量ASR

会生成一个list文件/Users/taoxu/Desktop/GPT-SoVITS/output/asr_opt/slicer_opt.list

打标

".list标注文件的路径" 输入output/asr_opt/slicer_opt.list

再勾选 "是否开启打标WebUI",会自动打开一个新页面

这里面是切片语音文件和语音文件台词的一一映射,检查有没有需要修改的,和需要加标点断句的,处理完后点击"Save File"然后关闭这个页面

训练集格式化

从 "0-前置数据集获取工具" 切换到 "1-GPT-SoVITS-TTS"

输入*实验/模型名:Nemesis

*文本标注文件:./output/asr_opt/slicer_opt.list

*训练集音频文件目录:./output/slicer_opt

点击 "开启一键三连",等待结束

微调训练

分别点击 "开启SoVITS训练",结束后点击 "开启GPT训练"

如果顺利的话,会分别在GPT_weightsSoVITS_weights文件夹下,创建出对应的GPT模型和SoVITS模型

注意:总训练轮数 ÷ 保存频率=生成模型的数量

GPT模型数量 = 15 ÷ 5 = 3

SoVITS模型数量 = 8 ÷ 4 = 2

markdown 复制代码
- GPT_weights
    - GPT_weights/Nemesis-e5.ckpt
    - GPT_weights/Nemesis-e10.ckpt
    - GPT_weights/Nemesis-e15.ckpt
- SoVITS_weights
    - SoVITS_weights/Nemesis_e4_s48.pth
    - SoVITS_weights/Nemesis_e8_s96.pth

e代表训练轮数,s代表训练步数,不一定数值越高,生成音频的效果就越好

如果实在效果不理想,可以提高GPT训练的总轮数,比如从15提到40,再点击"开启GPT训练",会直接从16轮训练

TTS推理

最后切换到 "1C-推理",勾选 "是否开启TTS推理WebUI",等待自动打开新页面

点击 "刷新模型路径",下拉列表选择刚才生成的GPT模型和SoVITS模型(e代表)

上传无噪音的参考音频和文本(影响最终语气),可以直接用之前切割音频时生成的文件

输入 "需要合成的文本" 和 "需要合成的语种",点击 "合成语音",最后在右侧生成语音,可以点击播放听效果,如果不满意可以更改GPT模型和SoVITS模型组合 or 提高GPT模型训练步数 or 选择更清晰的音频从头来过

阶段二:封装本地TTS脚本

虽然已经训练好了模型,后续可以一劳永逸直接用,基本上只需要输入最终想要的文本,就能直接生成语音文件,但总不可能每次启动项目都要上传参考音频,填参考音频文本吧,还有这个生成的音频文件肯定是存在于电脑硬盘里的,那么随着用的次数变多,是不是意味着硬盘空间会越用越少呢,甚至都不知道文件存在哪,想删都删不掉。

既然已经跑通了一次完整的流程,那么有没有什么方法能来给这个固定机械的流程提效加速呢,答案就是通过脚本定制和优化整个流程,即通过命令行的方式来操作现有的网页生成音频并播放,通过代码控制流程相比操作网页,可以操作的空间就有很多:将想看到的结果打印在终端(比如生成音频文件的路径,后续可以直接清空);不再关注UI,不再需要人肉操作页面生成音频,而是直接输入文本回车生成音频;对于训练得很好的模型,可以引入缓存,输入相同的文本就无需再重新生成音频,等等。

调试websocket连接

首先运行项目,打开TTS推理WebUI,打开network,点击 "合成语音",观察页面发送的请求

发现这是一个websocket连接:ws://127.0.0.1:9872/queue/join ,一共发了两次请求:

  • 第一次发送的参数是 {"fn_index":3,"session_hash":"bikdoojwuyf"}
  • 等接受到响应 {"msg":"send_data"} 会发送第二次请求
  • 第二次发送的参数是
kotlin 复制代码
{
  data: [
    {
      data: "data:audio/wav;base64,UklGRqRjAwBXQVZFZm10IBAAAAAB...",
      name: "20240127-203452.wav_781760_892800.wav",
    },
    "其实,我并没有什么归属感",
    "中文",
    "哥哥,呆会带我打团本",
    "中文",
    "按中文句号。切",
  ],
  fn_index: 3,
  session_hash: "bikdoojwuyf",
}
  • 后续会接受到3个响应,最后的这个响应包含 {"msg":"process_completed"}说明语音生成好了,存到了硬盘里,并且连带返回了生成的这个语音文件的路径
bash 复制代码
/var/folders/f3/n23rr3s558x0_hkct1m9w9p80000gn/T/gradio/af3b1224c7ba1619d705b0890ded93887a79909a/audio.wav

简易脚本

拷贝一下network的中请求参数,尝试请求/queue/join接口,运行一下,看看能不能生成语音文件

js 复制代码
import WebSocket from "ws";
import player from "play-sound";

const sessionHash = "bikdoojwuyf";
const websocketUrl = "ws://127.0.0.1:9872/queue/join";

const socket = new WebSocket(websocketUrl);

socket.on("open", () => {
  const requestData = {
    fn_index: 3,
    session_hash: sessionHash,
  };

  socket.send(JSON.stringify(requestData));
});

socket.on("message", (message) => {
  const responseData = JSON.parse(message);

  if (responseData.msg === "send_data") {
    const requestData = {
      data: [
        {
          data: "data:audio/wav;base64,UklGRqRjAwBXQVZFZm10IBAAAAABAAEA...",
          name: "20240127-203452.wav_781760_892800.wav",
        },
        "其实,我并没有什么归属感",
        "中文",
        "哥哥,呆会带我打团本",
        "中文",
        "按中文句号。切",
      ],
      fn_index: 3,
      session_hash: sessionHash,
    };

    socket.send(JSON.stringify(requestData));
  } else if (responseData.msg === "process_completed") {
    console.log("生成音频文件路径:", responseData.output.data[0].name);
    player().play(responseData.output.data[0].name);
  }
});
bash 复制代码
node index.js
生成音频文件路径:/var/folders/f3/n23rr3s558x0_hkct1m9w9p80000gn/T/gradio/2dd86c945cee5d876d01c732c23d04ae8177e757/audio.wav

脚本完善

基于简易脚本加入路径缓存、问答式交互、打印语音生成状态、打印语音生成耗时功能

js 复制代码
import WebSocket from "ws";
import player from "play-sound";
import readline from "readline";

const sessionHash = "bikdoojwuyf";
const websocketUrl = "ws://127.0.0.1:9872/queue/join";

// 路径缓存
const audioPathCache = {};

// 问答交互
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

rl.setPrompt("请输入需要合成的文本:");
rl.prompt();

rl.on("line", (input) => {
  handleText(input.trim());
});

rl.on("close", () => {
  process.exit();
});

const handleText = async (text) => {
  if (text) {
    if (audioPathCache[text]) {
      player().play(audioPathCache[text]);
      process.stdout.write("命中缓存\n\n");
    } else {
      await sendData(text);
    }
  }
  rl.prompt();
};

const sendData = (input) => {
  return new Promise((resolve) => {
    const socket = new WebSocket(websocketUrl);
    const startTime = new Date();

    socket.on("open", () => {
      const requestData = {
        fn_index: 3,
        session_hash: sessionHash,
      };

      socket.send(JSON.stringify(requestData));
    });

    socket.addListener("close", () => {
      resolve();
    });

    socket.on("message", (message) => {
      const responseData = JSON.parse(message);

      if (responseData.msg === "send_data") {
        const requestData = {
          data: [
            {
              data: "data:audio/wav;base64,UklGRqRjAwBXQVZFZm10IBAAAAABA",
              name: "20240127-203452.wav_781760_892800.wav",
            },
            "其实,我并没有什么归属感",
            "中文",
            input,
            "中文",
            "按中文句号。切",
          ],
          fn_index: 3,
          session_hash: sessionHash,
        };

        socket.send(JSON.stringify(requestData));
      } else if (responseData.msg === "process_completed") {
        if (!responseData.success) {
          process.exit();
        }
        audioPathCache[input] = responseData.output.data[0].name;
        player().play(responseData.output.data[0].name);
        process.stdout.write(
          "音频生成耗时:" + (new Date() - startTime) / 1000 + "s\n"
        );
        process.stdout.write(
          `生成音频文件路径:${responseData.output.data[0].name}\n\n`
        );
      } else {
        process.stdout.write("音频生成状态:" + responseData.msg + "\n");
      }
    });
  });
};

最终效果

bash 复制代码
node index.js

请输入需要合成的文本:哥哥
音频生成状态:send_hash
音频生成状态:estimation
音频生成状态:process_starts
音频生成状态:process_generating
音频生成耗时:1.874s
生成音频文件路径:/var/folders/f3/n23rr3s558x0_hkct1m9w9p80000gn/T/gradio/3ed576dd979391d659a14752e3226198275a04d5/audio.wav

请输入需要合成的文本:哥哥
命中缓存

请输入需要合成的文本:哥哥
命中缓存

请输入需要合成的文本:哥哥带带
音频生成状态:send_hash
音频生成状态:estimation
音频生成状态:process_starts
音频生成状态:process_generating
音频生成耗时:6.817s
生成音频文件路径:/var/folders/f3/n23rr3s558x0_hkct1m9w9p80000gn/T/gradio/20af1251af700aef8b04957970531b59512b67a8/audio.wav

请输入需要合成的文本:她不会生气吧
音频生成状态:send_hash
音频生成状态:estimation
音频生成状态:process_starts
音频生成状态:process_generating
音频生成耗时:7.904s
生成音频文件路径:/var/folders/f3/n23rr3s558x0_hkct1m9w9p80000gn/T/gradio/f9887e58c0243914da1ac61c829d966642e4f9f1/audio.wav

使用GPT-SoVITS前

使用GPT-SoVITS后

嗯!效果非常好!大佬已经在一声声哥哥中迷失了自我,以后再也不愁没有混不过去的团本了!

相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom10 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom10 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom10 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试