AI声音克隆GPT-SoVITS之ollama语音脚本openWebUI本地TTS输出过程记录 ...

打印 上一主题 下一主题

主题 1817|帖子 1817|积分 5451

前言:

本年春节,我本打算把没玩过的ai语音ai绘画尝试一下,见识当下的开源成果,我翻出了去年浮动在我首页被我收藏AI声音克隆——GPT-SoVITS,跟B站视频渐渐操作,不得不说出来结果很惊艳,让我这种铁耳朵人士高呼神了!
那几天我正在部署本地大预言模型玩deepseek,突发奇想,我能不能用ollama本地部署的deepseek和GPT-SoVITS结合起来,让deepseek天生文字,让GPT-SoVITS进行朗读。接下来好几天我都琢磨此事,连我玩ai绘图的操持也吹了,sd装了一年至今不会用......(´_ゝ`)然而并没有什么卵用,好几天搜刮确认了想法是可行的,但是没找到方法,网上也没有教程。
我跟风玩ai,许多专业术语和大段代码小白也不懂,最初我的想法只是GPT-SoVITS能接入openwebUI 就好了(因为我是ollama+openWebUi部署),可连捣鼓几天没有下文,我就把这个念头搁置了,再厥后就每天捡一点碎片化的知识看,在看别的教程,忽然悟到了一些灵感,于是换了一个思路打通了任督二脉!
我写这篇笔记第一部份gptsovits利用1月底写的,第二部分接ollama是二月上写完,第三部份卡了很久二月中测试乐成,现在做出一个可以分享的版本。在感谢花儿大佬的开源,和刘锐大佬修改的接口包,多亏各位大佬无私开源, 才能让我们平凡互联网用户也能吃上ai这口热饭。
特别说明,我这二和三的部分都是创建在一能乐成运行推理出音频的基础上,软件对显卡有4g以上的要求,如果本身运行 gpt-sovits 都有标题,后面都可不用看了。
第一部分:GPT-SoVITS网页端天生语音之图片版

这是我跟b站教程截图做的笔记,以免我后面再用这个软件忘记怎么操作,会的可以跳过,如果想要详细的教程可以见gptsovits的作者花儿不哭开源界面。
RVC-Boss/GPT-SoVITS: 1 min voice data can also be used to train a good TTS model! (few shot voice cloning)
https://github.com/RVC-Boss/GPT-SoVITSGPT-SoVITS是一个开源的声音克隆项目,通过少量的样本数据实现高质量的语音克隆和文本到语音转换。 该工具特别适用于须要快速天生特定人声的场景,可以资助用户在没有或只有少量目的语言人语音样本的情况下,训练出可以或许模仿该语言人声音的模型。
长处:快捷高效,作用:克隆声音,读小说、做桌宠、文本语音天生神器
1.下载花儿大佬的情况整合包,傻瓜式解压后,在文件目录下双击go-webui.bat。会弹出一个9874端口页,如果没有跳转就手动输入一下地址 localhost:9874
2.第一步是分离一个声音文件文件中的人声和bgm,如果有纯净的干音可以跳过这一步
操作:开启UVR5_web,在9873端口的页面分离须要ai解析的声音,上传音频 ,选择模型,点击转换,得到人声和bgm,会放在软件路径output\uvr5_opt


 
3.第二步把原声语音切割3-5s的音频,方便AI学习。
操作:回到9874端口页,关闭UVR5输入路径,在音频自动切分输入路径填入纯净人声的路径(如果按照默认操作步骤来,全程不用改路径),开启语音切割,这步会把大段语句切成每句话形势

 
4.第三步是语音识别文字,方便后续的矫正查对
操作:下一步开启离线批量ASR,注意这里输一次就会覆盖掉之前的文件,注意生存之前的文件

 
5.第四步开始听录批准语音文字,打标就是让ai学习字该怎么读
操作:选择开始打标弹出新的9871的窗口,一边听音频一边矫正文字 ,修改时要提交结果,再选择下一页全部听完,可以删撤除不是本身想要的声音,只留下优质的片段。删除要先点yes再删除

6.最后提交,关闭9871界面,返回9874关闭webUi
 
7.来到选项卡第二项——1-GPT-SOVITS-TTS界面,开始训练AI
填写你的模型名,查抄路径地址,开启一键三连,期待训练结果
默认的情况,路径不用改,等到右下角表现进程结束

 
8.这一步是训练的焦点,菜单标签-选择1B-微调训练
先选择sovits训练,期待训练完,再选择gpt,不要一起点。
设置batch_size,sovits训练发起batch_size设置为显存的一半以下,高了会爆显存
乐成的话能在软件目录SoVITS_weights_v2,GPT_weights_v2看到模型文件
更加详细的教程见花儿大佬的:整合包教程


 
9.到了利用的这一步,须要切换标签至1C-推理选项卡
刷新模型可以看到上一步本身训练出的模型列表,作者提到e代表训练的轮数,s代表步数。但是这也并不意味着越高结果越好,还是要用耳朵检测。
开启TTS推理,打开新的窗口9872端口页面

 
10.导入音频这一块涉及语气调解,这一步很重要!
基本上你导入的片段是什么语气,它天生音频就是什么语气,导入的声音可以从刚才切割的片段中拖入,拖入的音频不能凌驾10s。
合成之后可以听结果。训练缓存的数据在TEMP\gradio

最后视频教程作者提到,如果你不注意质量,可以直接跳转到最后一步,模型选第一个,直接进行声音克隆。
但我不太明白这两种方式质量差有多少?
总结:GPT-SoVITS说一个声音模仿的软件,如果想本身训练也行,不想训练可以直接导入别人分享的训练好的声音模型,进行文字转语音的合成。下一步的学习,是研究怎么训练出精度更高的声音模型,然后再研究情绪微调处置惩罚。
笔记的视频教程:你的声音,现在是我的了!- 手把手教你用 GPT-SoVITS 克隆声音!_哔哩哔哩_bilibili
下载地址:整合包及模型下载链接
 
第二部分:GPT-SoVITS与本地Ollama结合(python版)

这个方法是刘锐大佬视频里演示API接口调用给我的启发,他在花儿大佬修改参加了可以一键开启GPT-SoVITSapi接口的启动器,我这里用ollama接GPT-SoVITS的时候用的是他的接口,花儿大佬原版的整合包理论上也是能的,但是我第一次用原版遇到了报错,图省事我用了刘悦大佬的包调用的接口。
接口包下载地址,来自刘悦的技术博客:夸克网盘分享GPT-SoVITS-V2,参考音频利用,接口api调用,接入大模型,接入开源阅读3.0,TTS,声音克隆,文字转语音,花佬开源_哔哩哔哩_bilibili
https://www.bilibili.com/video/BV1nM4m1y7Sx/?spm_id_from=333.1387.search.video_card.click&vd_source=e0e4e4122d077cc783600ddc34cdbd00
因为我终极的目的是让GPT-SoVITS接入本地部署的ollama里AI问答中,实现文本转语音,我最开始的思路让ollama在python可以输出大模型回复→之后GPT-SoVITS在python输入可以或许进行语音朗读的转换→获取deepseek的输出回复给到GPT-SoVITS输入转换输出语音→安装python依靠播放声音的库让python获取天生的语音自动播放→同时参加正则过滤,优化语音,免得deepseek思考过程重复朗读。
操作为运行ollama,运行GPT-SoVITS-v2的接口.bat,确保两个服务正常运行,python正常创建项目即可,没有特殊的版本要求,如果报错一般是前两者服务没启动,查抄接口cmd内是否有报错内容,如果情况报错,查抄是不是提示有缺少的依靠库没装。
  1. python --version
复制代码
查抄当前运行的 Python 情况路径用到的:
  1. python -c "import sys; print(sys.executable)"
复制代码
如果窗口表现了pip 安装乐成,运行时仍旧提示找不到模块,说明现在Python运行情况和pip时安装情况不同等,查抄系统情况变量,最上面的优先级最高,我pychram利用的是我本地python的解释器,python3.12路径就和我的anaconda3情况辩论了,之前修改过anaconda3的编译器放在前面。

下一步我基础代码上,参加了循环语句让大模型这次天生之后开始下一次对话。之后又遇到了新的标题,由于输入音频我用的是固定名output.wav,导致下次音频天生后,上一次的文件还没开释,文件表现被占用无法覆盖天生语音。而我又不想给修改音频定名规则,输出许多的缓存文件,它最好是一次对话结束删除一次,以此循环。这里我扣问了AI,尝试了好多方法,定名的文件开释的标题始终未办理、不同文件名时间戳的临时文件不好管理、大概第三方库安装带了新的情况错误。多次尝试都还不可后,GPT保举我换一个声音的库,这种方式可以实现跨平台,但是须要先pip install pygame,安装pygame的库。
之后,我打算做成交互式,让脚本一开始运行时先选择和哪个模型对话,这里我调用了ollama默认的查询接口,如果你的端口不是ollama默认的是须要修改的。localhost:11434/v1/models的接口查询我用ollama部署的全部模型,通过找ID列表排序供。
最后,对原始代码做了一个优化,我用Windows 自带的模块更换了pygame,这么做的好处是无需安装其他库的方法,缺点是无法在其他平台运行,终极代码修改如下:
  1. import os
  2. import requests
  3. import re
  4. import multiprocessing
  5. import winsound  # 导入 winsound 模块
  6. # Ollama API 地址
  7. Ollama_api_url = "http://localhost:11434/api/generate"
  8. # Gptsovits API地址
  9. Gptsovits_api_url = "http://127.0.0.1:9880/tts_to_audio/"
  10. # 获取所有ollama模型
  11. def get_models():
  12.     url = "http://localhost:11434/v1/models"
  13.     try:
  14.         response = requests.get(url)
  15.         if response.status_code == 200:
  16.             data = response.json()
  17.             models = data.get("data", [])
  18.             model_ids = [model.get("id") for model in models if model.get("id")]
  19.             model_ids.sort()  # 排序模型 ID
  20.             return model_ids
  21.         else:
  22.             print(f"请求失败,状态码: {response.status_code}")
  23.             return []
  24.     except Exception as e:
  25.         print(f"请求出现错误: {e}")
  26.         return []
  27. # 播放音频文件
  28. def play_audio(file):
  29.     try:
  30.         winsound.PlaySound(file, winsound.SND_FILENAME)  # 使用 winsound 同步播放
  31.     except Exception as e:
  32.         print("播放进程播放失败:", str(e))
  33. # 选择模型并开始聊天的函数
  34. def chat_with_ai():
  35.     models = get_models()
  36.     if not models:
  37.         print("没有找到可用的模型。")
  38.         return
  39.     # 打印模型列表并让用户选择
  40.     print("※请确保GPT-SoVITS-v2的接口以及ollama服务启动※\n※以下是,可用模型:")
  41.     for i, model in enumerate(models, 1):
  42.         print(f"{i}. {model}")
  43.     try:
  44.         choice = int(input("请输入你选择的模型编号: "))
  45.         if 1 <= choice <= len(models):
  46.             selected_model = models[choice - 1]
  47.         else:
  48.             print("无效的选择。")
  49.             return
  50.     except ValueError:
  51.         print("无效的输入。")
  52.         return
  53.     print(f"你选择的模型是: {selected_model}")
  54.     while True:
  55.         # 请求参数
  56.         payload = {
  57.             "model": selected_model,  # 使用用户选择的模型
  58.             "prompt": input("聊天框:"),  # 用户输入的聊天内容
  59.             "stream": False  # 设置为 False 以获取完整的响应
  60.         }
  61.         # 发送 POST 请求
  62.         response = requests.post(Ollama_api_url, json=payload)
  63.         # 检查响应状态
  64.         if response.status_code == 200:
  65.             response_data = response.json()
  66.             generated_text = response_data.get("response", "")
  67.             # 使用正则表达式过滤<think>标签内容
  68.             generated_text = re.sub(r'<think>.*?</think>', '', generated_text, flags=re.DOTALL)
  69.             print(f"{selected_model} 回复: {generated_text.strip()}")
  70.             # 请求参数
  71.             payload = {
  72.                 "text": generated_text,  # 使用大模型生成的内容
  73.                 "text_lang": "zh",
  74.             }
  75.             # 发送POST请求
  76.             response = requests.post(Gptsovits_api_url, json=payload)
  77.             # 处理响应
  78.             if response.status_code == 200:
  79.                 # 保存音频文件
  80.                 audio_file = "output.wav"
  81.                 # 检查文件是否存在,如果存在则删除
  82.                 if os.path.exists(audio_file):
  83.                     os.remove(audio_file)  # 删除已存在的文件
  84.                 with open(audio_file, "wb") as f:
  85.                     f.write(response.content)
  86.                 # 自动播放音频 (使用 multiprocessing 和 winsound)
  87.                 try:
  88.                     p = multiprocessing.Process(target=play_audio, args=(audio_file,))
  89.                     p.start()
  90.                     p.join()  # 等待进程结束 (winsound 会阻塞直到播放完成)
  91.                     # 删除音频文件 (现在可以确保播放已经完成)
  92.                     os.remove(audio_file)
  93.                     print("【播放完成,删除音频】")
  94.                     print()
  95.                 except Exception as e:
  96.                     print("创建播放进程失败:", str(e))
  97.             else:
  98.                 print("失败:", response.text)
  99.         else:
  100.             print("请求失败,状态码:", response.status_code)
  101.             print("错误信息:", response.text)
  102. if __name__ == "__main__":
  103.     chat_with_ai()
复制代码
这个脚本还有个缺点是利用的时候无法更换声线,必须开着ollama服务和Gptsovits接口窗谈锋能正常工作,想更换声线,必须去config.py里修改,再重启接口api。AI的声音就是你选的参考音频。
SoVITS_weightsGPT_weights声音模型在GPT-SoVITS-v2\GPT_SoVITS\configs\tts_infer.yaml修改,刘锐接口包声音参考修改在config的脚本里。
新建txt生存成py在cmd窗口运行,它第一次打完标题发出后才会把大模型载入显存里,如果是用deepseek32B这种大模型,第一次的天生速度会很慢,连带着语音天生也会比力慢,慢到怀疑它到底有没有在运行,所以我还是保留了最后结束的【播放完成,删除音频】,出现这句话就表现本次对话结束了,不然就还在天生中。发起手动ollama run先把大模型跑起来。

 

第三部分:GPT-SoVITS与OpenWebUI结合(docker版)

我本身openWebui是通过docker安装的,利用时看到openwebui里内置了openApi文字转语音接口,于是我在二的基础上联想到可不可以把gptsovits本地的api做成一个适配openai接口接入openwebui,但标题是,我对怎样编写fastapi兼容openapi一无所知,这段代码我交给4o和deepseek与Gemini编写。经过两天一夜的努力十几轮的调试bug后,第一个可被post的兼容opanApi的本地gptsovitsTTS哀求乐成了。

INFO: Received valid request: {
  "text": "测试", 
  "voice": "keli",
  "speed": 1.0,
  "format": "mp3"
}
gptsovits主要吸收两个数据一个是text由大模型天生文字,另一个voice,填写你本身的参考音频名字,其他可随意填写。
  1. import os
  2. import sys
  3. from pathlib import Path
  4. from functools import lru_cache
  5. import uvicorn
  6. import soundfile as sf
  7. from io import BytesIO
  8. import numpy as np
  9. from fastapi import FastAPI, HTTPException
  10. from fastapi.responses import StreamingResponse
  11. from fastapi.middleware.cors import CORSMiddleware
  12. from pydantic import BaseModel, Field
  13. BASE_DIR = Path(__file__).parent
  14. sys.path.extend([str(BASE_DIR), str(BASE_DIR / "GPT_SoVITS")])
  15. class ServiceConfig:
  16.     REF_AUDIO_DIR = BASE_DIR / "reference_audio"  # 参考音频文件夹
  17.     CONFIG_PATH = BASE_DIR / "GPT_SoVITS" / "configs" / "tts_infer.yaml"
  18.     HOST = "0.0.0.0"
  19.     PORT = 9880
  20. from GPT_SoVITS.TTS_infer_pack.TTS import TTS, TTS_Config
  21. @lru_cache(maxsize=1)
  22. def load_tts_pipeline():
  23.     tts_config = TTS_Config(str(ServiceConfig.CONFIG_PATH))
  24.     return TTS(tts_config)
  25. tts_pipeline = load_tts_pipeline()
  26. app = FastAPI()
  27. app.add_middleware(
  28.     CORSMiddleware,
  29.     allow_origins=["*"],
  30.     allow_methods=["*"],
  31.     allow_headers=["*"],
  32. )
  33. class TTSParams(BaseModel):
  34.     text: str = Field(..., alias="input")
  35.     voice: str = Field(..., description="直接使用音频文件名,例如:GPT1.wav")
  36.     speed: float = 1.0
  37.     format: str = "mp3"
  38. class VoiceManager:
  39.     _valid_voices = set()
  40.     @classmethod
  41.     def initialize(cls):
  42.         ref_dir = ServiceConfig.REF_AUDIO_DIR
  43.         if not ref_dir.exists():
  44.             raise RuntimeError(f"参考音频目录 {ref_dir} 不存在")
  45.         cls._valid_voices = {
  46.             f.name for f in ref_dir.iterdir()
  47.             if f.is_file() and f.suffix.lower() in (".wav", ".mp3", ".ogg")
  48.         }
  49.     @classmethod
  50.     def get_voice_path(cls, voice_file: str) -> Path:
  51.         if voice_file not in cls._valid_voices:
  52.             raise HTTPException(
  53.                 status_code=404,
  54.                 detail=f"音频文件 {voice_file} 不存在,可用文件:{sorted(cls._valid_voices)}"
  55.             )
  56.         return ServiceConfig.REF_AUDIO_DIR / voice_file
  57. class AudioStreamer:
  58.     def __init__(self, tts_generator, media_type: str):
  59.         self.tts_generator = tts_generator
  60.         self.media_type = media_type
  61.     async def stream_audio(self):
  62.         sr, audio = next(self.tts_generator)
  63.         yield self._encode_audio(audio, sr)
  64.         for sr, audio in self.tts_generator:
  65.             yield self._encode_audio(audio, sr)
  66.     def _encode_audio(self, audio: np.ndarray, sr: int):
  67.         buffer = BytesIO()
  68.         sf.write(buffer, audio, sr, format=self.media_type)
  69.         return buffer.getvalue()
  70. @app.post("/v1/audio/speech")
  71. async def tts_endpoint(params: TTSParams):
  72.     try:
  73.         ref_path = VoiceManager.get_voice_path(params.voice)
  74.     except HTTPException as he:
  75.         raise he
  76.     except Exception as e:
  77.         raise HTTPException(status_code=500, detail=str(e))
  78.     tts_params = {
  79.         "text": params.text,
  80.         "text_lang": "auto",
  81.         "ref_audio_path": str(ref_path),
  82.         "speed_factor": params.speed,
  83.         "streaming_mode": True
  84.     }
  85.     streamer = AudioStreamer(
  86.         tts_generator=tts_pipeline.run(tts_params),
  87.         media_type=params.format
  88.     )
  89.     return StreamingResponse(
  90.         content=streamer.stream_audio(),
  91.         media_type=f"audio/{params.format}",
  92.         headers={"Content-Disposition": f'attachment; filename="speech.{params.format}"'}
  93.     )
  94. @app.get("/health")
  95. async def health():
  96.     return {"status": "OK", "available_voices": sorted(VoiceManager._valid_voices)}
  97. if __name__ == "__main__":
  98.     # 初始化语音管理器并扫描音频文件
  99.     try:
  100.         VoiceManager.initialize()
  101.     except RuntimeError as e:
  102.         print(f"初始化失败: {str(e)}")
  103.         sys.exit(1)
  104.     # 显示可用音频文件
  105.     print("\n可用的参考音频文件:")
  106.     for voice in sorted(VoiceManager._valid_voices):
  107.         print(f"  - {voice}")
  108.     print(f"\n服务启动在 http://{ServiceConfig.HOST}:{ServiceConfig.PORT}\n")
  109.     uvicorn.run(app, host=ServiceConfig.HOST, port=ServiceConfig.PORT)
复制代码

这版代码都是我修整完成的可以用的。原先测试乐成的只是可以被post,之后就要考虑openWebUI怎样吸收和发送哀求的标题,docker里的openwebui好像无法直接我主机地址的接口,openWebui在log里疯狂报错,gptsovits没有任何反应,我不知道标题在哪,问AI就一直让我对 OpenWebUI 的配置修改,给我干懵了,光在docker里找配置,我没整明白位置放在哪,修改配置这个方法我最后也没乐成,config的js文件我也没找到。不外,我找了其他的方法让容器访问本地。
  1. http://host.docker.internal:9880/v1
复制代码
在openWebUI中调用本地tts以下两个是必填项,下图为参考(默认运行是9880,主义下图8000是我厥后测试用的端口)

除了把api改成http://host.docker.internal:的前缀,g别的的方法是修改脚本文件本地的host为0.0.0.0,让docker可访问主机,又经过十几轮的调试。至此几十轮的调试终于结束。操作到这我已经精疲力尽了,光代码我就测试了一天一夜。
  1. http://localhost:8000/docs#/
  2. # 端口是我自定义的
复制代码

具体过程代码我就不展示了。利用方式:须要自行修改的就是参考音频的路径标题。我这里给出的全部代码都在我电脑测试乐成了,不包全部人都能乐成。管理员页面文本转语音引擎里填写本地接口,api密码任意写,声音和模型须要下面的py脚本里改。代码在花儿apiv2的基础上全部由AI转写成类兼容openai接口,我分享源代码见下方,供各位大佬拿参考。


  1. import os
  2. import sys
  3. from pathlib import Path
  4. from typing import Union  # 添加Union支持
  5. import uvicorn
  6. import soundfile as sf
  7. from io import BytesIO
  8. import numpy as np
  9. from fastapi import FastAPI
  10. from fastapi.responses import StreamingResponse
  11. from fastapi.middleware.cors import CORSMiddleware
  12. from pydantic import BaseModel, Field
  13. BASE_DIR = Path(__file__).parent
  14. sys.path.extend([str(BASE_DIR), str(BASE_DIR / "GPT_SoVITS")])
  15. class ServiceConfig:
  16.     CONFIG_PATH = BASE_DIR / "GPT_SoVITS" / "configs" / "tts_infer.yaml"
  17.     HOST = "0.0.0.0"
  18.     PORT = 9880
  19.     BASE_MODEL_DIR = BASE_DIR
  20.     SOVITS_WEIGHTS_DIR = BASE_MODEL_DIR / "SoVITS_weights_v2"
  21.     GPT_WEIGHTS_DIR = BASE_MODEL_DIR / "GPT_weights_v2"
  22.     REF_AUDIO_DIR_NAMES = ["reference_audio"]
  23.     @classmethod
  24.     def get_ref_audio_dir(cls) -> Path:
  25.         for name in cls.REF_AUDIO_DIR_NAMES:
  26.             ref_audio_dir = BASE_DIR / name
  27.             if ref_audio_dir.is_dir():
  28.                 return ref_audio_dir
  29.         default_ref_audio_dir = BASE_DIR / cls.REF_AUDIO_DIR_NAMES[0]
  30.         default_ref_audio_dir.mkdir(exist_ok=True)
  31.         return default_ref_audio_dir
  32. ServiceConfig.REF_AUDIO_DIR = ServiceConfig.get_ref_audio_dir()
  33. from GPT_SoVITS.TTS_infer_pack.TTS import TTS, TTS_Config
  34. def select_model_from_folders():
  35.     def select_model(model_type, model_dir, ext):
  36.         models = list(model_dir.glob(f"*.{ext}"))
  37.         if not models:
  38.             print(f"⚠️ {model_type} 模型文件夹 ({model_dir}) 为空或未找到 .{ext} 文件")
  39.             return None
  40.         print(f"\n----------------- {model_type} 模型选择 -----------------")
  41.         for i, path in enumerate(models):
  42.             print(f"{i + 1}. {path.name}")
  43.         while True:
  44.             try:
  45.                 choice = input(f"> 请选择 {model_type} 模型 (输入数字序号,留空使用默认): ").strip()
  46.                 if not choice:
  47.                     print(f"使用默认配置文件中的 {model_type} 模型\n")
  48.                     return None
  49.                 idx = int(choice) - 1
  50.                 if 0 <= idx < len(models):
  51.                     print(f"✅ 已选择 {model_type} 模型: {models[idx].name}\n")
  52.                     return str(models[idx])
  53.                 print("❌ 输入序号无效")
  54.             except ValueError:
  55.                 print("❌ 请输入有效数字")
  56.     gpt_path = select_model("GPT", ServiceConfig.GPT_WEIGHTS_DIR, "ckpt")
  57.     sovits_path = select_model("SoVITS", ServiceConfig.SOVITS_WEIGHTS_DIR, "pth")
  58.     return gpt_path, sovits_path
  59. def get_user_input(prompt, default=""):
  60.     value = input(prompt).strip()
  61.     return value if value else default
  62. selected_gpt_path, selected_vits_path = select_model_from_folders()
  63. reference_voice = get_user_input("> 请输入参考音频文件名(位于 reference_audio 目录下): ")
  64. use_prompt = get_user_input("> 是否设置 Prompt 文本和语言?(y/n): ").lower() == 'y'
  65. prompt_text = get_user_input("> Prompt 文本(可留空): ") if use_prompt else None
  66. prompt_lang = get_user_input("> Prompt 语言(zh/en/ja/ko/yue): ") if use_prompt else None
  67. def load_tts_pipeline(config_path, gpt_path=None, vits_path=None):
  68.     tts_config = TTS_Config(str(config_path))
  69.     if gpt_path: tts_config.t2s_weights_path = gpt_path
  70.     if vits_path: tts_config.vits_weights_path = vits_path
  71.     return TTS(tts_config)
  72. tts_pipeline = load_tts_pipeline(ServiceConfig.CONFIG_PATH, selected_gpt_path, selected_vits_path)
  73. app = FastAPI()
  74. app.add_middleware(
  75.     CORSMiddleware,
  76.     allow_origins=["*"],
  77.     allow_methods=["*"],
  78.     allow_headers=["*"],
  79. )
  80. class TTSParams(BaseModel):
  81.     text: str = Field(..., alias="input")
  82.     voice: str
  83.     speed: float = 1.0
  84.     format: str = "mp3"
  85.     gpt_weights_path: Union[str, None] = None  # 使用Union
  86.     vits_weights_path: Union[str, None] = None  # 使用Union
  87.     prompt_text: Union[str, None] = None  # 使用Union
  88.     prompt_lang: Union[str, None] = None  # 使用Union
  89. class VoiceManager:
  90.     @staticmethod
  91.     def get_voice_path(voice_id: str) -> Path:
  92.         base_path = ServiceConfig.REF_AUDIO_DIR / voice_id
  93.         if base_path.exists():
  94.             return base_path
  95.         wav_path = base_path.with_suffix('.wav')
  96.         if wav_path.exists():
  97.             return wav_path
  98.         raise ValueError(f"音频文件 {voice_id}(.wav) 不存在于 {ServiceConfig.REF_AUDIO_DIR}")
  99. @app.post("/v1/audio/speech")
  100. async def tts_endpoint(params: TTSParams):
  101.     try:
  102.         ref_path = VoiceManager.get_voice_path(params.voice)
  103.     except ValueError as e:
  104.         return {"status": "error", "message": str(e)}, 400
  105.     current_tts = load_tts_pipeline(
  106.         ServiceConfig.CONFIG_PATH,
  107.         params.gpt_weights_path or selected_gpt_path,
  108.         params.vits_weights_path or selected_vits_path
  109.     )
  110.     tts_params = {
  111.         "text": params.text,
  112.         "text_lang": "auto",
  113.         "ref_audio_path": str(ref_path),
  114.         "speed_factor": params.speed,
  115.         "streaming_mode": True,
  116.         "prompt_text": params.prompt_text or prompt_text,
  117.         "prompt_lang": params.prompt_lang or prompt_lang
  118.     }
  119.     if tts_params["prompt_text"]:
  120.         print(f"使用参考文本: {tts_params['prompt_text']}")
  121.         print(f"参考文本语言: {tts_params['prompt_lang'] or '未知'}")
  122.     else:
  123.         print("进入朴素推理模式")
  124.     def audio_generator():
  125.         buffer = BytesIO()
  126.         for sr, audio in current_tts.run(tts_params):
  127.             buffer.seek(0)
  128.             buffer.truncate()
  129.             sf.write(buffer, audio, sr, format=params.format)
  130.             yield buffer.getvalue()
  131.     return StreamingResponse(
  132.         audio_generator(),
  133.         media_type=f"audio/{params.format}",
  134.         headers={"Content-Disposition": f'attachment; filename="speech.{params.format}"'}
  135.     )
  136. @app.get("/health")
  137. async def health_check():
  138.     return {"status": "OK"}
  139. if __name__ == "__main__":
  140.     uvicorn.run(app, host=ServiceConfig.HOST, port=ServiceConfig.PORT)
复制代码
以上如果对你有资助,能帮我点点火山 api  激活码就再好不外啦~(¯▽¯~)

DeepSeek满血版免费领啦!约请挚友注册和利用,最高双方可得到145元代金券,免费抵扣3625万tokens,畅享R1与V3模型!到场入口:https://www.volcengine.com/experience/ark?utm_term=202502dsinvite&ac=DSASUQY5&rc=D61I3AL4  约请码:D61I3AL4
 
总结:

最后借这次测试,聊几句ai做生产力的看法。我以为AI已经来到了量变的时间节点。原先我以为AI将来可期,是因为它在通用范畴既代替不了高等人才,又没中间层职员好用,还没下层牛马劳动力便宜,所以绝大部分的行业无法真正地把AI当生产力来用。但是现在deepseek接入这个接入天天上消息,ai在开始铺量之后,尤其是大厂竞争的内卷,openAi官宣gpt5免费开放利用,ai不久将来会代替某些人行业某些底层的人,ai会成为中层内卷的威胁大概是所谓'提升效率的助手"
可标题也就来了,ai代替于没有履历技术的人,达到了及格的水平的,那些新人怎么办?大部分的技术岗位都是重新人一步一个脚印走的,很少人一下子就马上直接跨过低级到 了中级,但是ai在针对某些方面增强训练后就大概是会把新手淘汰掉了,将来不好说,但至少ai现在应用层还是很不美满,我利用ai做生产力感觉是我稍微掌握一些本事我就能用ai办事了,但是一旦涉及到我认知之外的事变了,我们就无法掌控的ai,被ai带着走了。而且当ai真的涉及到你专业的范畴之后,你会发现ai是会胡编乱造输出一些信息垃圾的,好比我之前问他一个软件的标题,我实在知道是怎么操作的我只是忘记那个按钮在哪里了,我用10秒就能办理的事变,ai写了1000个字没能办理我的标题,还制造了一下一堆信息垃圾浪费我时间,但是这也不是不能办理了,像我说的针对训练,,每个软件厂家都内置一个ai,大概是联网搜索,我信赖等ai铺量和专业化后,那时候真正的变革就真正滴开始了。
现在,假设问ai怎样造一辆车,这个车是你知识储备里的,你要造的车是存在的车,而且你知道组装车流程步骤,你拿着准备好的工具问ai怎样造一辆车,ai推理你手里的工具,很快地就能指导你造出一辆车。
假设你本日不是造车,想造一个火箭。而你只是平凡的司机不是火箭工,你知道火箭在地球上能被造出来,但你不知道它是具体怎么造,用什么造,而ai恰恰数据库有造火箭的方法,ai是知道怎么造一个火箭,但是造火箭须要涉及的原理操作太多了,ai会把它知道造火箭的方法用以为你应该知道的方式告诉你,然后你发现ai提供给你方法你并不能用,按照他的方法做一步卡一步,那为什么会出错呢?就像是人问不出认知以外的东西,ai同样也是,ai不知道你不知道的标题,它会默认你是一个火箭工程师或是什么具备造火箭知识的人,它提供的方法会省略“它以为你应该做到的东西”而跳过相当一部份的步骤,但是你并知道ai在这中间跳过了什么,所以你在办理一个错误时问不出真正要问ai的标题,ai也不知道你缺少那一步引起了哪些错误。就好比我只是把文件名打错了,拼错了字,而ai正在反复的修改代码这种无勤奋。
最后,你对ai说你想造一辆方舟,类似于2012里世界毁灭中国制造的方舟,这个在科幻片里的东西,在现实里没有,你问ai的问的越具体,ai会根据现实的法则推导出这个任务是当下人类可以做的,于是它按照臆想中类似造物的给你一套操作步骤,直到后面你忽然会发现这个玩意根本做不出来。
第二部分,我就是在造车的过程,因为我知道这个功能是怎样实现的,ai最后在做的实在是帮我验证它。我用了一个下战书就搞定终极的代码了,而第三部份我用了三天,本地tts到openwebUi这一部份,我遇到了许多奇形怪状的标题,做出openAi兼容的api的代码思路完全是ai写的,我并不知道这种中间具体是怎么实现的,我和ai身份交换了变成我在帮它验算。我前面也尝试过用deepseek写这部分代码,结果错误越写越多,不知道是我提问的方式不对或是触发了模型幻觉。翻来覆去地改也我几度想放弃,甚至以为我纯属没事找事。不外幸亏结果是好的。 我感觉就是越聪明的人用ai会越锋利,越锋利的人用ai会让ai反哺自身变得更强,如果以后更锋利的大模型出现,开启全民ai付费选择的期间,ai究竟是冲破信息差,还是进一步拉大人与人之间的落差存在?
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

道家人

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表