声音克隆与情感合成:Dify接入IndexTTS2

《声音克隆与情感合成:IndexTTS2让AI语音会"演戏"》中,已经在本地部署了模型,可以通过它提供的web页面使用语音合成。

本小节让 Dify 接入 IndexTTS2, 将 IndexTTS2 集成到Dify应用中。

1.Dify环境

本次环境使用 Dify 1.10.0, RockyLinux 操作系统,使用 Docker 安装部署。

http://http://192.168.1.20

2.IndexTTS2服务

《声音克隆与情感合成:IndexTTS2让AI语音会"演戏"》中详细介绍了如何本地部署 IndexTTS2服务,如需本地部署,请参考文章。

shell 复制代码
# 如果系统中安装了conda,需要退出conda环境
conda deactivate

# 进入 IndexTTS2源码目录
cd index-tts

# 配置环境变量,指定Haggingface镜像网站。因为启动的时候,如果需要模型权重文件,将会到 Haggingface上下载
export HF_ENDPOINT="https://hf-mirror.com"

# 启动服务, 服务会开启 7860 端口
uv run webui.py

http://http://192.168.6.133:7860/

在页面的最底部,会出现 "通过API使用" 的链接:

3.IndexTTS2 API接口

点击上面的链接后,出现页面,可以看到,它提供了4中接口嗲用方式:

  1. Python: 通过Python代码进行调用
  2. JavaScript: 通过JS代码进行接口调用
  3. cURL : 通过HTTP方式进行调用
  4. MCP: 如果IndexTTS2服务启动的时候,指定了启动参数或者添加了环境变脸,那它就可以作为一个MCP服务启动,应用中可以直接集成这个MCP服务

4.Dify中如何集成

要想在Dify中集成 IndexTTS2服务,有两种途径:

  1. 在工作流中添加 HTTP请求节点,使用HTTP方式调用 IndexTTS2服务
  2. 使用MCP。IndexTTS2提供了MCP服务,Dify中接入MCP服务即可。

4.1 使用MCP

首先安装依赖:

shell 复制代码
uv pip install gradio[mcp]

编辑 webui.py 最后一行:

python 复制代码
# 修改前
demo.launch(server_name=cmd_args.host, server_port=cmd_args.port)

# 修改后
demo.launch(server_name=cmd_args.host, server_port=cmd_args.port,mcp_server=True)

启动服务后:

4.2 使用Python发送 HTTP请求

API 文档中有说明:

latex 复制代码
Find the API endpoint below corresponding to your desired function in the app. Copy the code snippet, replacing the placeholder values with your own input data. Or use the 
API Recorder

 to automatically generate your API requests.
 
Making a prediction and getting a result requires 2 requests: a POST and a GET request. The POST request returns an EVENT_ID, which is used in the second GET request to fetch the results. In these snippets, we've used awk and read to parse the results, combining these two requests into one command for ease of use. See curl docs.

大概意思是: 首先会发送异步的请求,比如语音合成API 的URL:POSThttp://192.168.6.133:7860/gradio_api/call/gen_single ,返回$EVENT_ID,然后在使用 GET[http://192.168.6.133:7860/gradio_api/call/gen_single/ E V E N T I D ] ( h t t p : / / 192.168.6.133 : 7860 / g r a d i o a p i / c a l l / g e n s i n g l e / EVENT_ID](http://192.168.6.133:7860/gradio_api/call/gen_single/ EVENTID](http://192.168.6.133:7860/gradioapi/call/gensingle/EVENT_ID) 来获取最终生成的音频文件

因为第二个请求,也就是那个 GET请求,服务端响应的是 SSE 流,所以我们使用 sseclient这个库来解析返回内容。

shell 复制代码
pip install requests sseclient-py
python 复制代码
# 使用示例
if __name__ == "__main__":
    # 替换为你的服务器地址
    SERVER_URL = "http://192.168.6.133:7860"

    # 生成语音
    sound_url = simple_tts(
        server_url=SERVER_URL,
        text="Translate for me, what is a surprise!"
    )

    if sound_url!=None:
        print("语音合成成功!")
        print(sound_url)
    else:
        print("语音合成失败!")
python 复制代码
def simple_tts(server_url, text):
    """
    简化版TTS调用函数

    Args:
        server_url: 服务器地址,如 "http://192.168.6.133:7860"
        text: 要合成的文本
    """
    api_url = f"{server_url.rstrip('/')}/gradio_api/call/gen_single"

    # 构建请求数据
    request_data = {
        "data": [
            "与音色参考音频相同",  # 0:情感控制方式
            {  # 1: 音色参考音频
                "path": "/tmp/gradio/bbf6da2c239d53e761a6e2ab173c9db011f3e1c8e4e04566c73fc61a77e6925d/voice_01.wav",
                "meta": {"_type": "gradio.FileData"}
            },
            text,  # 2: 要转换的文本
            None, # 3:
            1,  # 4: 情感权重
            0, 0, 0, 0, 0, 0, 0, 0,  # 情感值:5:喜、6:怒、7:哀、8:惧、9:厌恶、10:低落、11:惊喜、12:平静
            "",  # 13: 情感描述文本
            False,  # 14: 情感随机采样
            120,  # 15: 分句最大Token数
            True,  # 16: do_sample
            0.8, 30, 0.8, 0, 3, 10, 1500  # 其他参数
        ]
    }

    try:
        # 第一步:发送POST请求
        print(f"正在生成语音: {text}")
        response = requests.post(api_url, json=request_data)
        response.raise_for_status()

        # 获取EVENT_ID
        result = response.json()
        event_id = result.get("event_id")
        if not event_id and "data" in result:
            event_id = result["data"][0]

        if not event_id:
            print("无法获取EVENT_ID")
            return False

        # 第二步:获取结果
        result_url = f"{api_url}/{event_id}"

        response = requests.get(result_url, stream=True)
        client = sseclient.SSEClient(response)
        sound_url = None
        try:
            for event in client.events():
                print(f"事件类型: {event.event}")
                print(f"事件数据: {event.data}")
                datas=json.loads(event.data) # 转换为JSON对象
                if len(datas)>0:
                    data=datas[0]
                    print(data['visible'])
                    if data['visible']:
                        sound_url=data['value']['url']
                        return sound_url

        except Exception as e:
            print(f"读取流时发生错误: {e}")
        finally:
            client.close()
            print("客户端已关闭")

        return sound_url

    except Exception as e:
        print(f"发生错误: {str(e)}")
        return None

注意:第一个参数是上传的音色文件,这个path ,传递的是服务器端的路径。

这个path可以通过调用 /on_example_click这个接口来获取。 相当于点击了UI页面中,最下面的Example 中的音色参考行,这样就能得到内置的参考音频路径

如果要使用自己的参考音频,就需要调用 /update_prompt_audio这个接口来获取上传到服务器上的文件路径。

4.3 Dify发送 HTTP请求

新建一个工作流,在开始节点后面添加HTTP请求节点:

4.3.1 用户输入节点

在这个节点上,添加一个变量,名称为: text,用于保存用户输入的要合成语音的文本。

4.3.2 添加HTTP请求节点

HTTP请求节点向服务器发送HTTP请求,这个请求会返回一个 event_id

请求Body:注意path 得写服务器上的文件路径

json 复制代码
{
	"data": [
    "与音色参考音频相同", 
    {  
        "path": "/tmp/gradio/bbf6da2c239d53e761a6e2ab173c9db011f3e1c8e4e04566c73fc61a77e6925d/voice_01.wav",
        "meta":{"_type":"gradio.FileData"}
    },
    "{{#1766126193865.text#}}", 
    null,
    1,
    0, 0, 0, 0, 0, 0, 0, 0,
    "",
    false,
    120,
    true,
    0.8, 30, 0.8, 0, 3, 10, 1500
]
}

如果报文不知道怎么写,可以在页面上录制操作生成 body

http://192.168.6.133:7860/ 上,打开 API Recorder 按钮,然后再页面上进行操作:

  1. 选一个音色文件
  2. 输入要转换的文本
  3. 生成语音

然后结束录制。此时就可以在页面上看到 curl代码:

4.3.3 解析返回的数据

添加一个 代码执行节点,它会执行一段代码,提取上个http请求返回的 event_id,生成GET请求的URL保存到变量中

要执行的python代码,它是一个函数,返回值必须是一个字典,key默认为 result

python 复制代码
def main(arg1: str):
    import json
    data=json.loads(arg1)
    event_id=data['event_id']
    url=f'http://192.168.6.133:7860/gradio_api/call/gen_single/{event_id}'
    return {
        "result": url,
    }

4.3.4 添加HTTP请求节点发出GET请求

这个HTTP节点发出GET请求,请求的地址是上一步构建出来的URL

4.3.5 添加代码执行节点获取合成结果

python 复制代码
import json
import re
def main(arg1: str):
     # 初始化返回结果
    result_url = None
    try:
        # 1. 获取 body 内容
        body_text = arg1
        
        # 2. 使用正则表达式查找 data: 开头的行,并提取后面的数据部分
        # 匹配 'data: ' 后面直到行尾的所有内容
        data_match = re.search(r'data:\s*(.+)', body_text, re.IGNORECASE)
        
        if data_match:
            json_str = data_match.group(1).strip()
            
            # 3. 解析 JSON 字符串
            # 注意:这里解析出来的是一个列表
            data_list = json.loads(json_str)
            
            # 4. 提取第一个元素中的 url
            # 结构: [{ "value": { "url": "实际链接" } }]
            if len(data_list) > 0:
                result_url = data_list[0].get("value", {}).get("url")
    
    except Exception as e:
        # 如果出错,返回错误信息(调试时很有用)
        return {
            "result": f"Error parsing response: {str(e)}"
        }
    
    # 5. 按照 Dify 规范返回字典
    # 这里的 "result" 键是 Dify 默认读取的输出变量名
    return {
        "result": result_url
    }

4.3.6 输出结果

添加输出节点

4.3.7 整体流程

相关推荐
会挠头但不秃2 小时前
深度学习常用工具和库介绍
人工智能·深度学习
Coder_Boy_2 小时前
【人工智能应用技术】-基础实战-小程序应用(基于springAI+百度语音技术)智能语音控制
人工智能·小程序
用泥种荷花2 小时前
智能体基础概念笔记
人工智能
雨大王5122 小时前
智能研发体是否值得投入?3大维度对比传统模式
人工智能·汽车
寰天柚子2 小时前
大模型时代的技术从业者:核心能力重构与实践路径
大数据·人工智能
智算菩萨2 小时前
AI能否可持续:从“三支柱”到“可持续AI目标体系”的理论框架与核算逻辑
人工智能·可持续
诗远Yolanda2 小时前
【EI检索会议】第二届国际人工智能创新研讨会(IS-AII 2026)
图像处理·人工智能·深度学习·机器学习·计算机视觉·机器人
IT_陈寒2 小时前
Redis实战精要:5种高频使用场景与性能优化全解析|得物技术
前端·人工智能·后端
Coder_Boy_2 小时前
【人工智能应用技术】-基础实战-小程序应用(基于springAI+百度语音技术)智能语音控制-单片机交互代码
java·人工智能·后端·嵌入式硬件