diff --git a/config.json b/config.json index 9c767fc6..7b680c23 100644 --- a/config.json +++ b/config.json @@ -541,7 +541,7 @@ "request_parameters": "{{\"url\": \"https://xzjosh-nana7mi-bert-vits2.hf.space/--replicas/b9be4/\", \"fn_index\": 0, \"data_analysis\": 1, \"text_input\": \"{content}\", \"speaker_option\": \"Nana7mi\", \"sdp_ratio\": 0.5, \"noise\": 0.6, \"noise_w\": 0.9, \"length\": 1}}" }, "gpt_sovits": { - "type": "gradio", + "type": "api", "ws_ip_port": "ws://localhost:9872/queue/join", "api_ip_port": "http://127.0.0.1:9880", "ref_audio_path": "F:\\GPT-SoVITS\\raws\\ikaros\\21.wav", @@ -549,6 +549,8 @@ "prompt_language": "日文", "language": "自动识别", "cut": "凑四句一切", + "gpt_model_path": "F:\\GPT-SoVITS\\GPT_weights\\ikaros-e15.ckpt", + "sovits_model_path": "F:\\GPT-SoVITS\\SoVITS_weights\\ikaros_e8_s280.pth", "webtts": { "api_ip_port": "http://127.0.0.1:8080", "spk": "sanyueqi", diff --git a/config.json.bak b/config.json.bak index 9c767fc6..7b680c23 100644 --- a/config.json.bak +++ b/config.json.bak @@ -541,7 +541,7 @@ "request_parameters": "{{\"url\": \"https://xzjosh-nana7mi-bert-vits2.hf.space/--replicas/b9be4/\", \"fn_index\": 0, \"data_analysis\": 1, \"text_input\": \"{content}\", \"speaker_option\": \"Nana7mi\", \"sdp_ratio\": 0.5, \"noise\": 0.6, \"noise_w\": 0.9, \"length\": 1}}" }, "gpt_sovits": { - "type": "gradio", + "type": "api", "ws_ip_port": "ws://localhost:9872/queue/join", "api_ip_port": "http://127.0.0.1:9880", "ref_audio_path": "F:\\GPT-SoVITS\\raws\\ikaros\\21.wav", @@ -549,6 +549,8 @@ "prompt_language": "日文", "language": "自动识别", "cut": "凑四句一切", + "gpt_model_path": "F:\\GPT-SoVITS\\GPT_weights\\ikaros-e15.ckpt", + "sovits_model_path": "F:\\GPT-SoVITS\\SoVITS_weights\\ikaros_e8_s280.pth", "webtts": { "api_ip_port": "http://127.0.0.1:8080", "spk": "sanyueqi", diff --git "a/docs/\346\212\225\350\265\204\344\272\272/index.html" "b/docs/\346\212\225\350\265\204\344\272\272/index.html" index c64cea86..d0485240 100644 --- "a/docs/\346\212\225\350\265\204\344\272\272/index.html" +++ "b/docs/\346\212\225\350\265\204\344\272\272/index.html" @@ -71,6 +71,11 @@ avatar: "https://images.cnblogs.com/cnblogs_com/ikaros-521/2328032/o_230923093032_QQ%E5%9B%BE%E7%89%8720230923172945.jpg", amount: "¥468" }, + { + name: "/:)★XSIN", + avatar: "https://images.cnblogs.com/cnblogs_com/ikaros-521/2328032/o_240314150105_QQ%E5%9B%BE%E7%89%8720240310195619.jpg", + amount: "¥400" + }, { name: "Kingsss", avatar: "https://images.cnblogs.com/cnblogs_com/ikaros-521/2328032/o_231201155829_QQ%E5%9B%BE%E7%89%8720231201235446.jpg", @@ -86,6 +91,11 @@ avatar: "https://images.cnblogs.com/cnblogs_com/ikaros-521/2328032/o_230725123351_QQ%E5%9B%BE%E7%89%8720230725203325.jpg", amount: "¥266.6" }, + { + name: "曹某", + avatar: "https://images.cnblogs.com/cnblogs_com/ikaros-521/2328032/o_240314140043_image3.png", + amount: "¥266.33" + }, { name: "真的要多喝热水", avatar: "https://images.cnblogs.com/cnblogs_com/ikaros-521/2328032/o_231122140807_QQ%E5%9B%BE%E7%89%8720231122220722.jpg", @@ -146,6 +156,11 @@ avatar: "https://images.cnblogs.com/cnblogs_com/ikaros-521/2328032/o_230722165246_QQ%E5%9B%BE%E7%89%8720230723005230.jpg", amount: "¥146.98" }, + { + name: "灵吾玄志", + avatar: "https://images.cnblogs.com/cnblogs_com/ikaros-521/2328032/o_240303075801_imag2e.png", + amount: "¥135" + }, { name: "kunfox-", avatar: "https://images.cnblogs.com/cnblogs_com/ikaros-521/2328032/o_230902161301_otaku.jpg", @@ -166,11 +181,6 @@ avatar: "https://images.cnblogs.com/cnblogs_com/ikaros-521/2328032/o_230820095537_QQ%E5%9B%BE%E7%89%8720230820175506.jpg", amount: "¥120" }, - { - name: "灵吾玄志", - avatar: "https://images.cnblogs.com/cnblogs_com/ikaros-521/2328032/o_240303075801_imag2e.png", - amount: "¥105" - }, { name: "一如既往", avatar: "https://images.cnblogs.com/cnblogs_com/ikaros-521/2328032/o_230719123315_%E4%B8%80%E5%A6%82%E6%97%A2%E5%BE%80.jpg", diff --git "a/docs/\346\212\225\350\265\204\344\272\272/invest.png" "b/docs/\346\212\225\350\265\204\344\272\272/invest.png" index 30fefa1f..28c77e7d 100644 Binary files "a/docs/\346\212\225\350\265\204\344\272\272/invest.png" and "b/docs/\346\212\225\350\265\204\344\272\272/invest.png" differ diff --git a/tests/test_gpt_sovits/api.py b/tests/test_gpt_sovits/api.py new file mode 100644 index 00000000..8d304835 --- /dev/null +++ b/tests/test_gpt_sovits/api.py @@ -0,0 +1,198 @@ +import logging, json, aiohttp, os, traceback +import base64 +import mimetypes +import websockets +import asyncio + +async def gpt_sovits_api(data): + + + def file_to_data_url(file_path): + # 根据文件扩展名确定 MIME 类型 + mime_type, _ = mimetypes.guess_type(file_path) + + # 读取文件内容 + with open(file_path, "rb") as file: + file_content = file.read() + + # 转换为 Base64 编码 + base64_encoded_data = base64.b64encode(file_content).decode('utf-8') + + # 构造完整的 Data URL + return f"data:{mime_type};base64,{base64_encoded_data}" + + async def websocket_client(data_json): + try: + async with websockets.connect(data["ws_ip_port"]) as websocket: + # 设置最大连接时长(例如 30 秒) + return await asyncio.wait_for(websocket_client_logic(websocket, data_json), timeout=30) + except asyncio.TimeoutError: + logging.error("gpt_sovits WebSocket连接超时") + return None + + async def websocket_client_logic(websocket, data_json): + async for message in websocket: + logging.debug(f"Received message: {message}") + + # 解析收到的消息 + data = json.loads(message) + # 检查是否是预期的消息 + if "msg" in data: + if data["msg"] == "send_hash": + # 发送响应消息 + response = json.dumps({"session_hash":"3obpzfqql7f","fn_index":3}) + await websocket.send(response) + logging.debug(f"Sent message: {response}") + elif data["msg"] == "send_data": + # audio_path = "F:\\GPT-SoVITS\\raws\\ikaros\\1.wav" + audio_path = data_json["ref_audio_path"] + + # 发送响应消息 + response = json.dumps( + { + "session_hash":"3obpzfqql7f", + "fn_index":3, + "data":[ + { + "data": file_to_data_url(audio_path), + "name": os.path.basename(audio_path) + }, + data_json["prompt_text"], + data_json["prompt_language"], + data_json["content"], + data_json["language"], + data_json["cut"] + ] + } + ) + await websocket.send(response) + logging.debug(f"Sent message: {response}") + elif data["msg"] == "process_completed": + return data["output"]["data"][0]["name"] + + try: + logging.debug(f"data={data}") + + if data["type"] == "gradio": + # 调用函数并等待结果 + voice_tmp_path = await websocket_client(data) + + # if voice_tmp_path: + # new_file_path = self.common.move_file(voice_tmp_path, os.path.join(self.audio_out_path, 'gpt_sovits_' + self.common.get_bj_time(4)), 'gpt_sovits_' + self.common.get_bj_time(4)) + + new_file_path = 'gpt_sovits_.wav' + + return new_file_path + elif data["type"] == "api": + try: + data_json = { + "refer_wav_path": data["ref_audio_path"], + "prompt_text": data["prompt_text"], + "prompt_language": data["prompt_language"], + "text": data["content"], + "text_language": data["language"] + } + + async with aiohttp.ClientSession() as session: + async with session.post(data["api_ip_port"], json=data_json, timeout=30) as response: + response = await response.read() + + file_name = 'gpt_sovits_.wav' + + voice_tmp_path = file_name + + # voice_tmp_path = self.common.get_new_audio_path(self.audio_out_path, file_name) + + with open(voice_tmp_path, 'wb') as f: + f.write(response) + + return voice_tmp_path + except aiohttp.ClientError as e: + logging.error(traceback.format_exc()) + logging.error(f'gpt_sovits请求失败: {e}') + except Exception as e: + logging.error(traceback.format_exc()) + logging.error(f'gpt_sovits未知错误: {e}') + elif data["type"] == "webtts": + try: + # 使用字典推导式构建 params 字典,只包含非空字符串的值 + params = { + key: value + for key, value in data["webtts"].items() + if value != "" + if key != "api_ip_port" + } + + # params["speed"] = self.get_random_float(params["speed"]) + params["text"] = data["content"] + + async with aiohttp.ClientSession() as session: + async with session.get(data["webtts"]["api_ip_port"], params=params, timeout=30) as response: + response = await response.read() + + file_name = 'gpt_sovits_.wav' + + voice_tmp_path = file_name + + # voice_tmp_path = self.common.get_new_audio_path(self.audio_out_path, file_name) + + with open(voice_tmp_path, 'wb') as f: + f.write(response) + + return voice_tmp_path + except aiohttp.ClientError as e: + logging.error(traceback.format_exc()) + logging.error(f'gpt_sovits请求失败: {e}') + except Exception as e: + logging.error(traceback.format_exc()) + logging.error(f'gpt_sovits未知错误: {e}') + except Exception as e: + logging.error(traceback.format_exc()) + logging.error(f'gpt_sovits未知错误,请检查您的gpt_sovits推理是否启动/配置是否正确,报错内容: {e}') + + return None + + +async def gpt_sovits_set_model(data): + from urllib.parse import urljoin + + if data["type"] == "api": + try: + data_json = { + "gpt_model_path": data["gpt_model_path"], + "sovits_model_path": data["sovits_model_path"] + } + + API_URL = urljoin(data["api_ip_port"], '/set_model') + + async with aiohttp.ClientSession() as session: + async with session.post(API_URL, json=data_json, timeout=30) as response: + response = await response.read() + + print(response) + + return response + except aiohttp.ClientError as e: + logging.error(traceback.format_exc()) + logging.error(f'gpt_sovits请求失败: {e}') + except Exception as e: + logging.error(traceback.format_exc()) + logging.error(f'gpt_sovits未知错误: {e}') + + +if __name__ == '__main__': + # 配置日志输出格式 + logging.basicConfig( + level=logging.DEBUG, # 设置日志级别,可以根据需求调整 + format="%(asctime)s [%(levelname)s] %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + + data = { + "type": "api", + "api_ip_port": "http://127.0.0.1:9880", + "gpt_model_path": "F:\GPT-SoVITS\GPT_weights\ikaros-e15.ckpt", + "sovits_model_path": "F:\GPT-SoVITS\SoVITS_weights\ikaros_e8_s280.pth" + } + + asyncio.run(gpt_sovits_set_model(data)) diff --git a/utils/common.py b/utils/common.py index 67cc95ce..62be6f2e 100644 --- a/utils/common.py +++ b/utils/common.py @@ -882,7 +882,7 @@ def get_all_audio_device_info(self, type): .@/. =@@@ ,@@@@@@@. =@@@@@@` """ - def send_request(self, url, method='GET', json_data=None): + def send_request(self, url, method='GET', json_data=None, resp_data_type="json", timeout=60): """ 发送 HTTP 请求并返回结果 @@ -890,25 +890,30 @@ def send_request(self, url, method='GET', json_data=None): url (str): 请求的 URL method (str): 请求方法,'GET' 或 'POST' json_data (dict): JSON 数据,用于 POST 请求 + resp_data_type (str): 返回数据的类型(json | content) + timeout (int): 请求超时时间 Returns: - dict: 包含响应的 JSON 数据 + dict|str: 包含响应的 JSON数据 | 字符串数据 """ headers = {'Content-Type': 'application/json'} try: if method == 'GET': - response = requests.get(url, headers=headers) + response = requests.get(url, headers=headers, timeout=timeout) elif method == 'POST': - response = requests.post(url, headers=headers, data=json.dumps(json_data)) + response = requests.post(url, headers=headers, data=json.dumps(json_data), timeout=timeout) else: raise ValueError('无效 method. 支持的 methods 为 GET 和 POST.') # 检查请求是否成功 response.raise_for_status() - # 解析响应的 JSON 数据 - result = response.json() + if resp_data_type == "json": + # 解析响应的 JSON 数据 + result = response.json() + else: + result = response.content return result diff --git a/webui.py b/webui.py index 0ad68c77..970941c3 100644 --- a/webui.py +++ b/webui.py @@ -345,6 +345,33 @@ def test_openai_key(): else: ui.notify(position="top", type="negative", message=f"测试失败!") + # GPT-SoVITS加载模型 + def gpt_sovits_set_model(): + try: + from urllib.parse import urljoin + + API_URL = urljoin(input_gpt_sovits_api_ip_port.value, '/set_model') + + data_json = { + "gpt_model_path": input_gpt_sovits_gpt_model_path.value, + "sovits_model_path": input_gpt_sovits_sovits_model_path.value + } + + resp_data = common.send_request(API_URL, "POST", data_json, resp_data_type="content") + + if resp_data is None: + content = "gpt_sovits加载模型失败,请查看双方日志排查问题" + logging.error(content) + ui.notify(position="top", type="negative", message=content) + else: + content = "gpt_sovits加载模型成功" + logging.info(content) + ui.notify(position="top", type="positive", message=content) + except Exception as e: + logging.error(traceback.format_exc()) + logging.error(f'gpt_sovits未知错误: {e}') + ui.notify(position="top", type="negative", message=f'gpt_sovits未知错误: {e}') + # 页面滑到顶部 def scroll_to_top(): # 这段JavaScript代码将页面滚动到顶部 @@ -3148,18 +3175,18 @@ def clear_tts_common_audio_card(file_path): input_gpt_sovits_ws_ip_port = ui.input(label='WS地址(gradio)', value=config.get("gpt_sovits", "ws_ip_port"), placeholder='启动TTS推理后,ws的接口地址').style("width:200px;") input_gpt_sovits_api_ip_port = ui.input(label='API地址(http)', value=config.get("gpt_sovits", "api_ip_port"), placeholder='官方API程序启动后监听的地址').style("width:200px;") with ui.row(): - input_gpt_sovits_ref_audio_path = ui.input(label='参考音频路径', value=config.get("gpt_sovits", "ref_audio_path"), placeholder='参考音频路径,建议填绝对路径').style("width:200px;") + input_gpt_sovits_ref_audio_path = ui.input(label='参考音频路径', value=config.get("gpt_sovits", "ref_audio_path"), placeholder='参考音频路径,建议填绝对路径').style("width:300px;") input_gpt_sovits_prompt_text = ui.input(label='参考音频的文本', value=config.get("gpt_sovits", "prompt_text"), placeholder='参考音频的文本').style("width:200px;") select_gpt_sovits_prompt_language = ui.select( label='参考音频的语种', options={'中文':'中文', '日文':'日文', '英文':'英文'}, value=config.get("gpt_sovits", "prompt_language") - ).style("width:200px;") + ).style("width:150px;") select_gpt_sovits_language = ui.select( label='需要合成的语种', options={'自动识别':'自动识别', '中文':'中文', '日文':'日文', '英文':'英文'}, value=config.get("gpt_sovits", "language") - ).style("width:200px;") + ).style("width:150px;") select_gpt_sovits_cut = ui.select( label='语句切分', options={ @@ -3172,6 +3199,11 @@ def clear_tts_common_audio_card(file_path): }, value=config.get("gpt_sovits", "cut") ).style("width:200px;") + with ui.row(): + input_gpt_sovits_gpt_model_path = ui.input(label='GPT模型路径', value=config.get("gpt_sovits", "gpt_model_path"), placeholder='GPT模型路径,填绝对路径').style("width:300px;") + input_gpt_sovits_sovits_model_path = ui.input(label='SOVITS模型路径', value=config.get("gpt_sovits", "sovits_model_path"), placeholder='SOVITS模型路径,填绝对路径').style("width:300px;") + button_gpt_sovits_set_model = ui.button('加载模型', on_click=gpt_sovits_set_model, color=button_internal_color).style(button_internal_css) + with ui.card().style(card_css): ui.label("WebTTS相关配置") with ui.row():