TTS语音合成|f5-tts语音合成服务器部署,实现http访问

p; 上篇文章分享了如何使用GPT-SoVITS实现一个HTTP服务器,并通过该服务器提供文本到语音(TTS)服务。今天,我们将进一步探讨如何部署另一个强大的TTS模型——f5-tts。这个模型在自然语音生成方面表现出色,具有高度的可定制性和灵活性。通过这篇文章,我们将详细介绍如何搭建f5-tts模型的环境,进行模型的配置,并通过HTTP服务器提供文本到语音服务,助力用户更高效地集成到各种应用场景中。

1 部署及启动F5-TTS服务器

1.1 项目下载及根据来源

这里就不赘述咋下载F5-TTS这个项目了,如果有不知道的兄弟可以看我上一篇文章:
TTS语音合成|盘点两款主流TTS模型,F5-TTS和GPT-SoVITS
需要注意的是,这里f5-tts官方并没有给我们实现api接口的http服务器,需要基于另一个项目去实现HTTP服务器的搭建,另一个项目地址在:f5-tts-api

在这里插入图片描述
可以看到,f5-tts-api这个项目提供了三种部署方式,实际上只有两种部署方式。前两种方式都使用了整合包里面的Python环境和模型,而第三种则是使用f5-tts官网的Python环境和模型。我尝试过这三种方式,发现由于模型版本的差异,前两种方式合成的语音中会出现杂音,而使用官网提供的环境和模型合成的语音则非常清晰,没有杂音。这可能是由于整合包中的模型版本与官网最新版本存在一定的差异,影响了语音的质量,所以这里重点介绍第三种部署方式

1.2 需要文件及代码

在这里插入图片描述
这里的项目F5-TTS是官网项目,configsapi.py是f5-tts-api这个项目中的文件。

1.3 启动服务命令

# 这个环境是f5-tts官网环境
conda activate f5-tts
pip install flask waitress
pip install Flask-Cors
cd F:\TTS\F5-TTS
python api.py

启动起来大概是这样的:
在这里插入图片描述
可以看到IP是127.0.0.1,只允许本地访问,如果想要局域网内访问这个服务,或者映射出去,需要到api.py里面修改ip,如下图所示:
在这里插入图片描述
将ip改为0.0.0.0即可。

2 客户端请求部署的TTS服务器

关于请求,f5-tts-api这个项目给了两种请求方式,分别是API 使用示例接口和 兼容openai tts接口

2.1 API 使用示例

import requestsres=requests.post('http://127.0.0.1:5010/api',data={"ref_text": '这里填写 1.wav 中对应的文字内容',"gen_text": '''这里填写要生成的文本。''',"model": 'f5-tts'
},files={"audio":open('./1.wav','rb')})if res.status_code!=200:print(res.text)exit()with open("ceshi.wav",'wb') as f:f.write(res.content)

2.2 兼容openai tts接口

import requests
import json
import os
import base64
import structfrom openai import OpenAIclient = OpenAI(api_key='12314', base_url='http://127.0.0.1:5010/v1')
with  client.audio.speech.with_streaming_response.create(model='f5-tts',voice='1.wav###你说四大皆空,却为何紧闭双眼,若你睁开眼睛看看我,我不相信你,两眼空空。',input='你好啊,亲爱的朋友们',speed=1.0) as response:with open('./test.wav', 'wb') as f:for chunk in response.iter_bytes():f.write(chunk)

2.3 测试

使用API 使用示例,可以看到,不出所料的报错了😴:

在这里插入图片描述
原因很简单,你没科学上网,Hugging Face被墙了,继续往下看解决办法。

3 下载模型及修改api.py

3.1 查看需要模型

这里直接给你掠过,有兴趣的兄弟们可以自行查看api.py,直接说结论,需要两个模型,一个是TTS模型(F5-TTS_Emilia-ZH-EN
),一个是频谱合成模型(vocos-mel-24khz)。
F5-TTS_Emilia-ZH-EN下载位置在:
在这里插入图片描述
在这里插入图片描述
*vocos-mel-24khz我在modelscope没有找到,Hugging Face上倒是有:
在这里插入图片描述

3.2 api.py代码修改位置

在这里插入图片描述
上图位置1改成vocab.txt位置,位置2改成F5-TTS_Emilia-ZH-EN模型位置。

在这里插入图片描述
上图改成vocos-mel-24khz模型位置。

4 代码优化

我发现每次推理时,都需要加载一遍模型,可能是为了节省资源:
在这里插入图片描述
我这里先把模型加载进来,避免每次都加载一遍模型,并且将参考音频和参考文字都放在了服务端,并且将生成的音频上传到oss服务器,做成网络音频流。
具体代码实现:


import os,time,sys
from pathlib import Path
ROOT_DIR=Path(__file__).parent.as_posix()# ffmpeg
if sys.platform == 'win32':os.environ['PATH'] = ROOT_DIR + f';{ROOT_DIR}\\ffmpeg;' + os.environ['PATH']
else:os.environ['PATH'] = ROOT_DIR + f':{ROOT_DIR}/ffmpeg:' + os.environ['PATH']SANFANG=True
if Path(f"{ROOT_DIR}/modelscache").exists():SANFANG=Falseos.environ['HF_HOME']=Path(f"{ROOT_DIR}/modelscache").as_posix()import re
import torch
from torch.backends import cudnn
import torchaudio
import numpy as np
from flask import Flask, request, jsonify, send_file, render_template
from flask_cors import CORS
from einops import rearrange
from vocos import Vocos
from pydub import AudioSegment, silencefrom cached_path import cached_pathimport soundfile as sf
import io
import tempfile
import logging
import traceback
from waitress import serve
from importlib.resources import files
from omegaconf import OmegaConffrom f5_tts.infer.utils_infer import (infer_process,load_model,load_vocoder,preprocess_ref_audio_text,remove_silence_for_generated_wav,
)
from f5_tts.model import DiT, UNetT
from oss_uploader import upload_to_oss
import requests
TMPDIR=(Path(__file__).parent/'tmp').as_posix()
Path(TMPDIR).mkdir(exist_ok=True)# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)app = Flask(__name__, template_folder='templates')
CORS(app)# --------------------- Settings -------------------- #audio_info = {  # 字典定义26: {"audio_name": "data/cqgyq.wav", "ref_text": "111" ,"audio_speed": 1.3},25: {"audio_name": "data/shwd.wav", "ref_text": "222","audio_speed": 1.1},24: {"audio_name": "data/network8.wav", "ref_text": "333","audio_speed": 1},23: {"audio_name": "data/network4.wav", "ref_text": "444","audio_speed": 1.1},22: {"audio_name": "data/network2.wav", "ref_text": "555","audio_speed": 0.9},
}def get_audio_info(audio_id):# 判断 audio_id 是否是整数if not isinstance(audio_id, int):try:# 尝试将 audio_id 转换为整数audio_id = int(audio_id)except ValueError:# 如果转换失败,输出错误信息并返回默认值print(f"音频ID {audio_id} 不是有效的整数,无法转换。")return None, None, None# 判断 audio_id 是否在字典中if audio_id in audio_info:audio_data = audio_info[audio_id]return audio_data["audio_name"], audio_data["ref_text"], audio_data["audio_speed"]else:print(f"音频ID {audio_id} 在字典中没有找到。")return None, None, None# Add this near the top of the file, after other imports
UPLOAD_FOLDER = 'data'
if not os.path.exists(UPLOAD_FOLDER):os.makedirs(UPLOAD_FOLDER)def get_model_and_vocoder(model_dir, vocab_dir, vcoder_dir):# mel_spec_type = vocoder_namemodel_cfg =  f"{ROOT_DIR}/configs/F5TTS_Base_train.yaml"model_cfg = OmegaConf.load(model_cfg).model.archmodel = load_model(DiT, model_cfg, model_dir, mel_spec_type='vocos', vocab_file=vocab_dir)vocoder = load_vocoder(vocoder_name='vocos',is_local=True,local_path=vcoder_dir)return model, vocodermodel, vocoder = get_model_and_vocoder(f"{ROOT_DIR}/model/model_1250000.safetensors", f'{ROOT_DIR}/model/vocab.txt',f'{ROOT_DIR}/model/models--charactr--vocos-mel-24khz/snapshots/0feb3fdd929bcd6649e0e7c5a688cf7dd012ef21/')@app.route('/api', methods=['POST'])
def api():logger.info("Accessing generate_audio route")# 打印所有请求数据if request.is_json:data = request.get_json()gen_text = data['text']voice_id = data['anchor_id']ref_audio_path = data['ref_audio_path']logger.info("Received JSON data: %s", data)else:data = request.form.to_dict()  logger.info("Received form data: %s", data)gen_text = request.form.get('text')voice_id = request.form.get('anchor_id')ref_audio_path = request.form.get('ref_audio_path')remove_silence = int(request.form.get('remove_silence',0))# 自定义speed# speed = float(request.form.get('speed',1.0))print("===========voice_id: ", voice_id, type(voice_id))audio_name, ref_text, audio_speed = get_audio_info(voice_id)print("================get audio_name: ",audio_name)print("================get ref_text :",ref_text)print("================get audio_speed :",audio_speed)print("================get ref_audio_path :",ref_audio_path)if not all([audio_name, ref_text, gen_text]):  # Include audio_filename in the checkreturn jsonify({"error": "Missing required parameters"}), 400speed = audio_speed# 调用接口查询是否存在记录check_url = "xxx/existVoice"try:response = requests.post(check_url, data={"voice_text": gen_text, 'anchor_id': voice_id})if response.status_code == 200:result = response.json()if result.get("code") == 200 and result.get("data"):# 如果接口返回 code 为 200 且 data 有值,表示有记录return {"code": 200, "oss_url": result.get("data")}except Exception as e:return {"code": 400, "message": "Request error", "Exception": str(e)}print("=======================================Check Success!!!")try:main_voice = {"ref_audio": audio_name, "ref_text": ref_text}voices = {"main": main_voice}for voice in voices:voices[voice]["ref_audio"], voices[voice]["ref_text"] = preprocess_ref_audio_text(voices[voice]["ref_audio"], voices[voice]["ref_text"])print("Voice:", voice)print("Ref_audio:", voices[voice]["ref_audio"])print("Ref_text:", voices[voice]["ref_text"])generated_audio_segments = []reg1 = r"(?=\[\w+\])"chunks = re.split(reg1, gen_text)reg2 = r"\[(\w+)\]"for text in chunks:if not text.strip():continuematch = re.match(reg2, text)if match:voice = match[1]else:print("No voice tag found, using main.")voice = "main"if voice not in voices:print(f"Voice {voice} not found, using main.")voice = "main"text = re.sub(reg2, "", text)gen_text = text.strip()ref_audio = voices[voice]["ref_audio"]ref_text = voices[voice]["ref_text"]print(f"Voice: {voice}")# 语音生成audio, final_sample_rate, spectragram = infer_process(ref_audio, ref_text, gen_text, model, vocoder, mel_spec_type='vocos', speed=speed)generated_audio_segments.append(audio)# if generated_audio_segments:final_wave = np.concatenate(generated_audio_segments)# 使用BytesIO在内存中处理音频文件with io.BytesIO() as audio_buffer:# 将音频写入内存缓冲区sf.write(audio_buffer, final_wave, final_sample_rate, format='wav')if remove_silence == 1:# 如果需要去除静音,需要先将数据加载到pydub中处理audio_buffer.seek(0)  # 回到缓冲区开头sound = AudioSegment.from_file(audio_buffer, format="wav")# 去除静音sound = remove_silence_from_audio(sound)  # 假设有这个函数# 将处理后的音频重新写入缓冲区audio_buffer.seek(0)audio_buffer.truncate()sound.export(audio_buffer, format="wav")# 上传到OSSaudio_buffer.seek(0)  # 回到缓冲区开头以便读取oss_url = upload_to_oss(audio_buffer.read(), "wav")  # 上传到 OSSprint(f"Uploaded to OSS: {oss_url}")# 将返回的 oss_url 插入数据库insert_url = "xxx/insertVoice"try:insert_response = requests.post(insert_url, data={"voice_text": gen_text,"voice_type": 1,"oss_url": oss_url,"anchor_id": voice_id,"type": 1})if insert_response.status_code == 200:# 插入成功,返回生成的 oss_urlreturn {"code": 200, "oss_url": oss_url}else:# 如果插入失败,返回错误信息return {"code": 400, "message": "Failed to insert oss_url into database"}except Exception as e:return {"code": 500, "message": "Error during TTS processing", "Exception": str(e)}# 返回 OSS URL# return {"code": 200, "oss_url": oss_url}# print("==========audio_file.filename: ",audio_file.filename)# return send_file(wave_path, mimetype="audio/wav", as_attachment=True, download_name="aaa.wav")except Exception as e:logger.error(f"Error generating audio: {str(e)}", exc_info=True)return jsonify({"error": str(e)}), 500        if __name__ == '__main__':try:# host="127.0.0.1"host="0.0.0.0"port=5010print(f"api接口地址  http://{host}:{port}")serve(app,host=host, port=port)except Exception as e:logger.error(f"An error occurred: {str(e)}")logger.error(traceback.format_exc())

总结

  本篇文章主要分享了如何使用F5-TTS实现一个HTTP服务器,并通过该服务器提供文本到语音(TTS)服务。通过搭建这个服务器,用户可以方便地通过API接口进行文本转语音的请求。文章详细介绍了如何配置和运行该服务器,如何处理API请求,并展示了如何利用该服务将文本转换为自然流畅的语音输出。此外,文中还探讨了如何优化服务器的性能,确保高效的文本转语音处理,同时提供了相关的错误处理机制,确保用户体验的稳定性与可靠性。通过实现这个TTS服务,用户能够轻松将文本信息转化为语音形式,广泛应用于语音助手、自动化客户服务以及各类语音交互系统中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/91333.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/91333.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Golang】Go语言指针

Go语言指针 文章目录Go语言指针一、指针1.1、Go语言中的指针1.1.1、指针地址和指针类型1.1.2、指针取值1.1.3、空指针1.1.4、new和make1.1.5、new1.1.6、make1.1.7、new与make的区别一、指针 区别于C/C中的指针,Go语言中的指针不能进行偏移和运算,是安全…

EMC的一些简单常识

ESD测试比对 & 需要做到动作 试验: -780系统,板子直流地 和 PE连接(主板PE & DC-分开,但是前端板PE & DC-连接),只能承受1K接触放电。 -780系统,板子直流地 和 PE分开(主…

Day4||vue-计算属性

一.定义概念:基于现有的数据,计算出来的新属性。依赖的数据变化,自动重新计算。语法:声明需要放在computed的配置项中,一个计算属性对于一个函数。使用起来和普通属性一样使用{{计算属性名}}计算属性->可以将一段求…

编程算法在金融、医疗、教育、制造业等领域的落地案例

随着人工智能与大数据技术的迅猛发展,编程算法已从理论研究走向实际应用,在金融、医疗、教育、制造业等多个关键领域实现了深度落地。这些算法不仅提升了业务效率,还推动了行业智能化转型。本文将系统性地分析编程算法在四大核心领域的典型应…

vue npm install卡住没反应

install某个插件的时候,发现卡住不动,可以尝试以下解决方法 1.使用–verbose查看安装详情 npm install consola --verbose2.更换淘宝镜像(一般都是淘宝镜像正式过期问题) 查看当前镜像 npm config get registry更换淘宝镜像 第一种…

【Unity3D实例-功能-移动】角色移动-通过WSAD(CharacterController方式)

今天,我们就来聊聊如何在Unity中使用CharacterController组件,让角色能够灵活移动,在游戏世界里像风一样自由奔跑吧。废话不多说,走,让我们马上来一探究竟!目录:1.准备模型2.动画的处理3.为角色…

AI Agent开发学习系列 - langchain之LCEL(4):Memory

Memory的添加方式 from operator import itemgetterfrom langchain.memory import ConversationBufferMemory from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.runnables import RunnableLambda, RunnablePassthrough from l…

向Coze学习,我们把这些工作流也开源了

要说现在最火的工作流是什么,那非视频工作流莫属了。我每天也都能接到不少咨询短视频工作流的需求。 这大半年来我们团队也制作和整理了超过 100 工作流,其中很大一部分都是现在最火的视频工作流。 为了向Coze开源致敬,这次我们斑码团队也决…

Git 从零开始:完整项目上传指南

一、准备工作 1. 安装 Git Windows:下载安装包macOS:在终端运行 brew install gitLinux: # Ubuntu/Debian sudo apt update && sudo apt install git# CentOS/Fedora sudo yum install git2. 配置用户信息(首次使用&am…

【AI】入门级提示词模板:适用于ChatGPT、文心一言等主流模型

入门级提示词模板:适用于 ChatGPT、文心一言等主流模型**在当下这个人工智能飞速发展的时代,ChatGPT、文心一言等主流大模型已经广泛地融入到我们的工作与生活当中。不管是创作文章、生成代码,还是进行数据分析、获取创意灵感,这些…

如何解决 undetected_chromedriver 启动慢问题

要解决 undetected_chromedriver 启动慢的问题,可以从以下几个方面优化配置和代码: 1. 指定本地 Chrome 二进制路径 避免自动搜索 Chrome 路径,直接指定位置: driver uc.Chrome(browser_executable_pathrC:\Program Files\Google…

Python 程序设计讲义(42):组合数据类型——元组类型:创建元组

Python 程序设计讲义(42):组合数据类型——元组类型:创建元组 目录Python 程序设计讲义(42):组合数据类型——元组类型:创建元组一、元组的特征二、创建元组1、使用圆括号&#xff0…

windows 设置 vscode 免密远程

我们可以使用 vscode ssh 进行远程编辑文件项目。也可以使用 ssh 密匙 来实现免密登录。 本人在本地windows系统有多个密匙,使用 D:\SPB_Data\.ssh\id_rsa_local 进行本地设备进行登录。 在 vscode ssh 配置文件中添加 IdentityFile 配置 Host 本地设备ipHostName …

ubuntu自动搭建Android平台NDK编译环境

sh setup_ndk_env.sh自动下载NDK并解压 提取Android平台工具链 验证Android工具链 设置工具链变量 export CROSS_TRIPLE=aarch64_linux_android export CROSS_ROOT=/home/ubuntu/${CROSS_TRIPLE} export ANDROID_NDK=${CROSS_ROOT} export AS=${CROSS_ROOT}/bin/llvm-as exp…

添加捕捉吸附标识(使用QT+OpenGL开发三维CAD)

捕捉吸附标识怎么画出来?在点吸附的时候能够展示吸附标识可以让用户更直观的看到当前捕捉点,从而更准确的进行设计和绘制。 效果视频见原文:添加捕捉吸附标识(使用QTOpenGL开发三维CAD) 16.Add snap label 鼠标捕捉吸…

元宇宙中的“虫洞“:技术实现、应用场景与未来挑战

一、技术定义与核心架构1.1 虫洞的元宇宙隐喻概念来源:虫洞在物理学中是连接不同时空的通道,而在元宇宙中,这一概念被引申为连接不同虚拟世界的跨平台协议。英伟达Omniverse平台通过USD(通用场景描述)实现了这一隐喻&a…

使用GIS中基于森林的分类与回归模型来估算房屋价值

“基于森林的分类与回归”,它可以帮助分析师有效地设计、测试和部署预测模型。 基于森林的分类与回归应用了 Leo Breiman 的随机森林算法,这是一种用于分类和预测的流行监督机器学习方法。该工具允许分析师轻松整合表格属性、基于距离的要素和解释栅格来…

《 java 随想录》| LeetCode链表高频考题

前言:这是专门针对java语言讲解的算法解析(题目顺序大致参考《代码随想录》)思维导图操作链表删除节点删除链表中 D 节点时,只需将其前驱节点 C 的 next 指针指向 D 的下一个节点 E。添加节点​先让 新节点 F 的 next 指针 指向 C…

学习嵌入式的第三十一天-数据结构-(2025.7.23)网络协议封装

今天的内容主要是网络协议以及常用工具的介绍。协议头与数据封包/拆包数据封包示例:MAC|IP|TCP|hello| ———————————— IP数据报IP头信息默认20字节常用网络测试工具telnetnetstatpingarpwiresharktcpdumpssh2secure crt工具安装命令sudo ufw disable sud…

STL学习(十、常用排序、拷贝、替换算法)

目录 一、常用排序算法 1.sort (1) 内置数据类型 (2)自定义数据类型 2. random_shuffle(iterator beg, iterator end) 3.merge 4.reverse 二、常用的拷贝和替换算法 1.copy(起始不如直接赋值) 2.replace 3.replace_if 4.swap 一、常用排序算法 1.sort 函数原型 s…