学会用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后

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

相关推荐
醉の虾7 分钟前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧15 分钟前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm25 分钟前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
asleep70137 分钟前
第8章利用CSS制作导航菜单
前端·css
hummhumm41 分钟前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
幼儿园的小霸王1 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue
疯狂的沙粒1 小时前
对 TypeScript 中高级类型的理解?应该在哪些方面可以更好的使用!
前端·javascript·typescript
gqkmiss2 小时前
Chrome 浏览器 131 版本开发者工具(DevTools)更新内容
前端·chrome·浏览器·chrome devtools
Summer不秃2 小时前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰2 小时前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter