Skip to content

Commit ea8fd15

Browse files
committed
🚀 feat(tts): 支持离线 API 和更多配置项
- 新增离线 API 配置项和相关功能 - 重构在线 API 调用逻辑,支持参考音频和自定义模型 - 优化配置文件结构,增加更多可配置选项 - 重构代码,提高可维护性和扩展性 - 更新 README 文档,增加新的配置说明和使用方法
1 parent 711d04e commit ea8fd15

File tree

10 files changed

+339
-133
lines changed

10 files changed

+339
-133
lines changed

README.md

+25-8
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,36 @@ git clone https://github.com/Cvandia/nonebot-plugin-fishspeech-tts
6969

7070
## ⚙️ 配置
7171

72-
**在env.中添加以下配置**
72+
**在.env中添加以下配置**
7373

74-
| 配置 | 类型 |必填项| 默认值 | 说明 |
74+
| 基础配置 | 类型 |必填项| 默认值 | 说明 |
7575
|:-----:|:----:|:----:|:---:|:----:|
7676
|tts_is_online|bool||True|是否使用云端api|
77-
|tts_is_to_me|bool||True|是否需要at触发(防止误触)|
78-
|online_authorization|str|依据第一个配置项|"xxxx"|fish-audio 后端api鉴权,详见[链接](https://fish.audio/zh-CN/go-api/api-keys/)||
79-
|tts_api_url|str|依据第一个配置项|"http://127.0.0.1:8080"|离线或自定义api地址
80-
|tts_audio_path|str|依据第一个配置项|"./data/参考音频"|合成角色语音路劲|
8177
|tts_chunk_length|literal||"normal"|请求时音频分片长度,默认为normal,可选:short, normal, long|
78+
|tts_is_to_me|bool||True|是否仅当被@时回复|
79+
|tts_audio_path|str||"./data/参考音频"|语音素材路径,默认为"./data/参考音频"|
8280

83-
**注:参考音频的文件名格式为:[角色名]音频对应的文字标签.wav**
84-
**支持同一角色的不同语音**
81+
**注:参考音频的文件名格式为:[角色名]音频对应的文字标签.[音频后缀名]**
82+
83+
**! 支持同一角色的不同语音 !**
84+
85+
**音频后缀目前支持有详见[files.py](./nonebot_plugin_fishspeech_tts/files.py)中的`AUDIO_FILE_SUFFIX`**
86+
___
87+
88+
如果你想使用官方的api,请将配置项`tts_is_online`设置为`True`并配置以下
89+
90+
| 配置项 | 类型 | 必填项 | 默认值 | 说明 |
91+
|:-----:|:----:|:----:|:---:|:----:|
92+
|online_authorization|str||"xxxxx"|官网api鉴权秘钥,详见[链接](https://fish.audio/zh-CN/go-api/api-keys/)|
93+
|online_model_first|bool||True|如果你想调用官方模型,通过自己的参考音频,定制角色音色,将此项设为`False`。当然,如果你没有准备参考音频,也是会调用官网已经有的音色,具体详见[链接](https://fish.audio/zh-CN/)|
94+
95+
---
96+
97+
如果你想使用[自搭](#离线搭建fish-speech)或者其他的[fish-speech](https://github.com/fishaudio/fish-speech)项目的api,请将配置项`tts_is_online`设置为`Fasle`并配置以下
98+
99+
| 配置项 | 类型 | 必填项 | 默认值 | 说明 |
100+
|:----:|:----:|:----:|:---:|:----:|
101+
|offline_api_url|str||"http://127.0.0.1:8080"|你的`fish-speech`api地址|
85102

86103
## ⭐ 使用
87104

nonebot_plugin_fishspeech_tts/__init__.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
require("nonebot_plugin_alconna")
99

1010
from nonebot_plugin_alconna import UniMessage, Reply, UniMsg, Text
11-
from .fish_audio_api import fish_audio_api, ChunkLength
11+
from .fish_audio_api import fish_audio_api
1212
from .fish_speech_api import fish_speech_api
1313
from .exception import APIException
14+
from .request_params import ChunkLength
1415
from .config import config, Config
1516
import contextlib
1617

@@ -76,7 +77,10 @@ async def tts_handle(message: UniMsg, match: tuple = RegexGroup()):
7677
try:
7778
if is_online:
7879
await tts_handler.send("正在通过在线api合成语音, 请稍等")
79-
audio = await fish_audio_api.generate_tts(text, speaker, chunk_length)
80+
request = await fish_audio_api.generate_servettsrequest(
81+
text, speaker, chunk_length
82+
)
83+
audio = await fish_audio_api.generate_tts(request)
8084
await UniMessage.voice(raw=audio).finish()
8185
else:
8286
await tts_handler.send("正在通过本地api合成语音, 请稍等")
@@ -93,11 +97,11 @@ async def tts_handle(message: UniMsg, match: tuple = RegexGroup()):
9397
async def speaker_list_handle(event: Event):
9498
try:
9599
if is_online:
96-
await speaker_list.send("具体见官网:https://fish.audio/zh-CN/")
100+
_list = fish_audio_api.get_speaker_list()
101+
await speaker_list.finish("语音角色列表: " + ", ".join(_list))
97102
else:
98-
await speaker_list.send("正在获取本地语音角色列表, 请稍等")
99-
speakers = fish_speech_api.get_speaker_list()
100-
await speaker_list.send("语音角色列表: " + ", ".join(speakers))
103+
_list = fish_speech_api.get_speaker_list()
104+
await speaker_list.finish("语音角色列表: " + ", ".join(_list))
101105
except APIException as e:
102106
await speaker_list.finish(str(e))
103107

nonebot_plugin_fishspeech_tts/config.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@
55

66
class Config(BaseModel):
77

8-
online_authorization: Optional[str] = "xxxxx"
9-
tts_api_url: str = "http://127.0.0.1:8080"
8+
# 基础配置
109
tts_is_online: bool = True
1110
tts_chunk_length: Literal["normal", "short", "long"] = "normal"
1211
tts_is_to_me: bool = True
1312
tts_audio_path: str = "./data/参考音频"
1413

14+
# 区分配置
15+
online_authorization: Optional[str] = "xxxxx"
16+
online_model_first: bool = True
17+
18+
offline_api_url: str = "http://127.0.0.1:8080"
19+
1520

1621
config = get_plugin_config(Config)

nonebot_plugin_fishspeech_tts/exception.py

+23
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
from httpx import (
2+
ReadTimeout,
3+
ConnectTimeout,
4+
ConnectError,
5+
RequestError,
6+
HTTPStatusError,
7+
)
8+
9+
110
class APIException(Exception):
211
"""API异常类"""
312

@@ -8,3 +17,17 @@ class AuthorizationException(APIException):
817
"""授权异常类"""
918

1019
pass
20+
21+
22+
class FileHandleException(Exception):
23+
"""文件处理异常类"""
24+
25+
pass
26+
27+
28+
class HTTPException(
29+
ReadTimeout, ConnectTimeout, ConnectError, RequestError, HTTPStatusError
30+
):
31+
"""HTTP异常类"""
32+
33+
pass
+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from pathlib import Path
2+
from nonebot.log import logger
3+
from .exception import FileHandleException
4+
import re
5+
6+
7+
# 音频文件后缀
8+
AUDIO_FILE_SUFFIX = [
9+
".mp3",
10+
".wav",
11+
".flac",
12+
".ogg",
13+
".m4a",
14+
".wma",
15+
".aac",
16+
".aiff",
17+
".aif",
18+
".aifc",
19+
]
20+
21+
22+
def extract_text_by_filename(file_name: str) -> str:
23+
"""
24+
从文件名中提取文本标签
25+
26+
Args:
27+
file_name: 文件名
28+
Returns:
29+
ref_text: 提取的文本标签
30+
31+
exception:
32+
FileHandleException: 未能从文件名中提取文本标签
33+
"""
34+
35+
ref_text = re.sub(r"\[.*\]", "", file_name)
36+
ref_text = Path(ref_text).stem
37+
if not ref_text:
38+
raise FileHandleException(f"未能从文件名{file_name}中提取文本标签")
39+
return ref_text
40+
41+
42+
def get_speaker_audio_path(path_audio: Path, speaker_name: str) -> list[Path]:
43+
"""
44+
获取指定说话人的音频文件路径
45+
46+
Args:
47+
speaker_name: 说话人姓名
48+
path_audio: 音频文件路径
49+
Returns:
50+
speaker_audio_path: 说话人音频文件路径列表
51+
52+
exception:
53+
FileHandleException: 未找到说话人音频文件
54+
"""
55+
56+
speaker_audio_path = []
57+
for audio in path_audio.iterdir():
58+
if speaker_name in audio.name and audio.suffix in AUDIO_FILE_SUFFIX:
59+
speaker_audio_path.append(audio)
60+
logger.debug(f"获取到角色的语音路劲: {speaker_audio_path}")
61+
if not speaker_audio_path:
62+
raise FileHandleException(f"未找到角色:{speaker_name}的音频文件")
63+
return speaker_audio_path
64+
65+
66+
def get_path_speaker_list(path_audio: Path) -> list[str]:
67+
"""
68+
获取说话人列表
69+
70+
Returns:
71+
list[str]: 说话人列表
72+
73+
exception:
74+
FileHandleException: 未找到说话人
75+
"""
76+
speaker_list = []
77+
for audio in path_audio.iterdir():
78+
if audio.suffix in AUDIO_FILE_SUFFIX:
79+
speaker_name = re.search(r"\[(.*)\]", audio.stem)
80+
if speaker_name:
81+
speaker_list.append(speaker_name.group(1))
82+
# 去重
83+
speaker_list = list(set(speaker_list))
84+
if not speaker_list:
85+
raise FileHandleException("未找到说话人音频文件")
86+
return speaker_list

0 commit comments

Comments
 (0)