xiaogpt 2.42__tar.gz → 2.60__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {xiaogpt-2.42 → xiaogpt-2.60}/PKG-INFO +52 -67
- {xiaogpt-2.42 → xiaogpt-2.60}/README.md +40 -57
- {xiaogpt-2.42 → xiaogpt-2.60}/pyproject.toml +12 -10
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/cli.py +10 -22
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/config.py +2 -15
- xiaogpt-2.60/xiaogpt/tts/__init__.py +5 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/tts/base.py +4 -4
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/tts/mi.py +1 -1
- xiaogpt-2.60/xiaogpt/tts/tetos.py +56 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/xiaogpt.py +34 -19
- xiaogpt-2.42/xiaogpt/tts/__init__.py +0 -6
- xiaogpt-2.42/xiaogpt/tts/azure.py +0 -98
- xiaogpt-2.42/xiaogpt/tts/edge.py +0 -32
- xiaogpt-2.42/xiaogpt/tts/openai.py +0 -46
- {xiaogpt-2.42 → xiaogpt-2.60}/LICENSE +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/__init__.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/__main__.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/__init__.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/base_bot.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/chatgptapi_bot.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/gemini_bot.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/glm_bot.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/langchain_bot.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/newbing_bot.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/qwen_bot.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/langchain/callbacks.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/langchain/chain.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/langchain/examples/email/mail_box.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/langchain/examples/email/mail_summary_tools.py +0 -0
- {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: xiaogpt
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.60
|
4
4
|
Summary: Play ChatGPT or other LLM with xiaomi AI speaker
|
5
5
|
Author-Email: yihong0618 <zouzou0208@gmail.com>
|
6
6
|
License: MIT
|
@@ -15,7 +15,6 @@ Requires-Dist: aiohttp
|
|
15
15
|
Requires-Dist: rich
|
16
16
|
Requires-Dist: zhipuai>=2.0.1
|
17
17
|
Requires-Dist: httpx[socks]
|
18
|
-
Requires-Dist: edge-tts>=6.1.3
|
19
18
|
Requires-Dist: EdgeGPT==0.1.26
|
20
19
|
Requires-Dist: langchain>=0.0.343
|
21
20
|
Requires-Dist: beautifulsoup4>=4.12.0
|
@@ -23,11 +22,11 @@ Requires-Dist: google-search-results>=2.4.2
|
|
23
22
|
Requires-Dist: google-generativeai
|
24
23
|
Requires-Dist: numexpr>=2.8.6
|
25
24
|
Requires-Dist: dashscope>=1.10.0
|
26
|
-
Requires-Dist:
|
27
|
-
Requires-Dist: aiohttp==3.9.
|
25
|
+
Requires-Dist: tetos>=0.1.0
|
26
|
+
Requires-Dist: aiohttp==3.9.5; extra == "locked"
|
28
27
|
Requires-Dist: aiosignal==1.3.1; extra == "locked"
|
29
28
|
Requires-Dist: annotated-types==0.6.0; extra == "locked"
|
30
|
-
Requires-Dist: anyio==3.
|
29
|
+
Requires-Dist: anyio==4.3.0; extra == "locked"
|
31
30
|
Requires-Dist: async-timeout==4.0.3; python_version < "3.11" and extra == "locked"
|
32
31
|
Requires-Dist: attrs==23.2.0; extra == "locked"
|
33
32
|
Requires-Dist: azure-cognitiveservices-speech==1.37.0; extra == "locked"
|
@@ -36,21 +35,23 @@ Requires-Dist: bingimagecreator==0.5.0; extra == "locked"
|
|
36
35
|
Requires-Dist: cachetools==5.3.2; extra == "locked"
|
37
36
|
Requires-Dist: certifi==2024.2.2; extra == "locked"
|
38
37
|
Requires-Dist: charset-normalizer==3.3.2; extra == "locked"
|
38
|
+
Requires-Dist: click==8.1.7; extra == "locked"
|
39
39
|
Requires-Dist: colorama==0.4.6; platform_system == "Windows" and extra == "locked"
|
40
|
-
Requires-Dist: dashscope==1.
|
40
|
+
Requires-Dist: dashscope==1.17.0; extra == "locked"
|
41
41
|
Requires-Dist: dataclasses-json==0.6.3; extra == "locked"
|
42
42
|
Requires-Dist: distro==1.9.0; extra == "locked"
|
43
43
|
Requires-Dist: edge-tts==6.1.10; extra == "locked"
|
44
44
|
Requires-Dist: edgegpt==0.1.26; extra == "locked"
|
45
45
|
Requires-Dist: exceptiongroup==1.2.0; python_version < "3.11" and extra == "locked"
|
46
46
|
Requires-Dist: frozenlist==1.4.1; extra == "locked"
|
47
|
-
Requires-Dist: google-ai-generativelanguage==0.6.
|
47
|
+
Requires-Dist: google-ai-generativelanguage==0.6.2; extra == "locked"
|
48
48
|
Requires-Dist: google-api-core==2.15.0; extra == "locked"
|
49
49
|
Requires-Dist: google-api-core[grpc]==2.15.0; extra == "locked"
|
50
50
|
Requires-Dist: google-api-python-client==2.125.0; extra == "locked"
|
51
51
|
Requires-Dist: google-auth==2.26.1; extra == "locked"
|
52
52
|
Requires-Dist: google-auth-httplib2==0.2.0; extra == "locked"
|
53
|
-
Requires-Dist: google-
|
53
|
+
Requires-Dist: google-cloud-texttospeech==2.16.3; extra == "locked"
|
54
|
+
Requires-Dist: google-generativeai==0.5.1; extra == "locked"
|
54
55
|
Requires-Dist: google-search-results==2.4.2; extra == "locked"
|
55
56
|
Requires-Dist: googleapis-common-protos==1.62.0; extra == "locked"
|
56
57
|
Requires-Dist: greenlet==3.0.3; (platform_machine == "win32" or platform_machine == "WIN32" or platform_machine == "AMD64" or platform_machine == "amd64" or platform_machine == "x86_64" or platform_machine == "ppc64le" or platform_machine == "aarch64") and extra == "locked"
|
@@ -73,12 +74,12 @@ Requires-Dist: markdown-it-py==3.0.0; extra == "locked"
|
|
73
74
|
Requires-Dist: marshmallow==3.20.1; extra == "locked"
|
74
75
|
Requires-Dist: mdurl==0.1.2; extra == "locked"
|
75
76
|
Requires-Dist: miservice-fork==2.4.3; extra == "locked"
|
76
|
-
Requires-Dist: multidict==6.0.
|
77
|
+
Requires-Dist: multidict==6.0.5; extra == "locked"
|
77
78
|
Requires-Dist: mutagen==1.47.0; extra == "locked"
|
78
79
|
Requires-Dist: mypy-extensions==1.0.0; extra == "locked"
|
79
80
|
Requires-Dist: numexpr==2.10.0; extra == "locked"
|
80
81
|
Requires-Dist: numpy==1.26.3; extra == "locked"
|
81
|
-
Requires-Dist: openai==1.
|
82
|
+
Requires-Dist: openai==1.21.2; extra == "locked"
|
82
83
|
Requires-Dist: orjson==3.10.0; extra == "locked"
|
83
84
|
Requires-Dist: packaging==23.2; extra == "locked"
|
84
85
|
Requires-Dist: prompt-toolkit==3.0.43; extra == "locked"
|
@@ -101,6 +102,7 @@ Requires-Dist: socksio==1.0.0; extra == "locked"
|
|
101
102
|
Requires-Dist: soupsieve==2.5; extra == "locked"
|
102
103
|
Requires-Dist: sqlalchemy==2.0.25; extra == "locked"
|
103
104
|
Requires-Dist: tenacity==8.2.3; extra == "locked"
|
105
|
+
Requires-Dist: tetos==0.1.0; extra == "locked"
|
104
106
|
Requires-Dist: tqdm==4.66.1; extra == "locked"
|
105
107
|
Requires-Dist: typing-extensions==4.9.0; extra == "locked"
|
106
108
|
Requires-Dist: typing-inspect==0.9.0; extra == "locked"
|
@@ -134,33 +136,16 @@ Play ChatGPT and other LLM with Xiaomi AI Speaker
|
|
134
136
|
- [通义千问](https://help.aliyun.com/zh/dashscope/developer-reference/api-details)
|
135
137
|
|
136
138
|
## 获取小米音响DID
|
137
|
-
|
138
|
-
|
139
|
-
pip install miservice_fork
|
140
|
-
set MI_USER=
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
139
|
+
| 系统和Shell | Linux *sh | Windows CMD用户 | Windows PowerShell用户 |
|
140
|
+
| ------------- | ---------------------------------------------- | -------------------------------------- | ---------------------------------------------- |
|
141
|
+
| 1、安装包 | `pip install miservice_fork` | `pip install miservice_fork` | `pip install miservice_fork` |
|
142
|
+
| 2、设置变量 | `export MI_USER=xxx` <br> `export MI_PASS=xxx` | `set MI_USER=xxx`<br>`set MI_PASS=xxx` | `$env:MI_USER="xxx"` <br> `$env:MI_PASS="xxx"` |
|
143
|
+
| 3、取得MI_DID | `micli list` | `micli list` | `micli list` |
|
144
|
+
| 4、设置MI_DID | `export MI_DID=xxx` | `set MI_DID=xxx` | `$env:MI_DID="xxx"` |
|
145
|
+
|
146
|
+
- 注意不同shell 对环境变量的处理是不同的,尤其是powershell赋值时,可能需要双引号来包括值。
|
146
147
|
- 如果获取did报错时,请更换一下无线网络,有很大概率解决问题。
|
147
148
|
|
148
|
-
### Linux(使用 export 设置环境变量)
|
149
|
-
```sh
|
150
|
-
# 1、安装模块
|
151
|
-
pip install miservice_fork
|
152
|
-
|
153
|
-
# 2、设置环境用户参数
|
154
|
-
export MI_USER=xxxx
|
155
|
-
export MI_PASS=xxx
|
156
|
-
|
157
|
-
# 3、使用micli list 得到did
|
158
|
-
micli list
|
159
|
-
|
160
|
-
# 4、根据did设置环境DID参数
|
161
|
-
export MI_DID=xxxx
|
162
|
-
```
|
163
|
-
|
164
149
|
## 一点原理
|
165
150
|
|
166
151
|
[不用 root 使用小爱同学和 ChatGPT 交互折腾记](https://github.com/yihong0618/gitblog/issues/258)
|
@@ -278,38 +263,37 @@ ChatGLM [文档](http://open.bigmodel.cn/doc/api#chatglm_130b)
|
|
278
263
|
|
279
264
|
## 配置项说明
|
280
265
|
|
281
|
-
| 参数
|
282
|
-
|
|
283
|
-
| hardware
|
284
|
-
| account
|
285
|
-
| password
|
286
|
-
| openai_key
|
287
|
-
| serpapi_api_key
|
288
|
-
| glm_key
|
289
|
-
| gemini_key
|
290
|
-
| qwen_key
|
291
|
-
| cookie
|
292
|
-
| mi_did
|
293
|
-
| use_command
|
294
|
-
| mute_xiaoai
|
295
|
-
| verbose
|
296
|
-
| bot
|
297
|
-
| tts
|
298
|
-
|
|
299
|
-
| prompt
|
300
|
-
| keyword
|
301
|
-
| change_prompt_keyword
|
302
|
-
| start_conversation
|
303
|
-
| end_conversation
|
304
|
-
| stream
|
305
|
-
| proxy
|
306
|
-
| gpt_options
|
307
|
-
| bing_cookie_path
|
308
|
-
| bing_cookies
|
309
|
-
| deployment_id
|
310
|
-
| api_base
|
311
|
-
|
312
|
-
| azure_tts_service_region | Azure TTS 服务地区 | `eastasia` | [Regions - Speech service - Azure AI services](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/regions) |
|
266
|
+
| 参数 | 说明 | 默认值 | 可选值 |
|
267
|
+
| --------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
|
268
|
+
| hardware | 设备型号 | | |
|
269
|
+
| account | 小爱账户 | | |
|
270
|
+
| password | 小爱账户密码 | | |
|
271
|
+
| openai_key | openai的apikey | | |
|
272
|
+
| serpapi_api_key | serpapi的key 参考 [SerpAPI](https://serpapi.com/) | | |
|
273
|
+
| glm_key | chatglm 的 apikey | | |
|
274
|
+
| gemini_key | gemini 的 apikey [参考](https://makersuite.google.com/app/apikey) | | |
|
275
|
+
| qwen_key | qwen 的 apikey [参考](https://help.aliyun.com/zh/dashscope/developer-reference/api-details) | | |
|
276
|
+
| cookie | 小爱账户cookie (如果用上面密码登录可以不填) | | |
|
277
|
+
| mi_did | 设备did | | |
|
278
|
+
| use_command | 使用 MI command 与小爱交互 | `false` | |
|
279
|
+
| mute_xiaoai | 快速停掉小爱自己的回答 | `true` | |
|
280
|
+
| verbose | 是否打印详细日志 | `false` | |
|
281
|
+
| bot | 使用的 bot 类型,目前支持 chatgptapi,newbing, qwen, gemini | `chatgptapi` | |
|
282
|
+
| tts | 使用的 TTS 类型 | `mi` | `edge`、 `openai`、`azure`、`volc`、`baidu`、`google` |
|
283
|
+
| tts_options | TTS 参数字典,参考 [tetos](https://github.com/frostming/tetos) 获取可用参数 | | |
|
284
|
+
| prompt | 自定义prompt | `请用100字以内回答` | |
|
285
|
+
| keyword | 自定义请求词列表 | `["请"]` | |
|
286
|
+
| change_prompt_keyword | 更改提示词触发列表 | `["更改提示词"]` | |
|
287
|
+
| start_conversation | 开始持续对话关键词 | `开始持续对话` | |
|
288
|
+
| end_conversation | 结束持续对话关键词 | `结束持续对话` | |
|
289
|
+
| stream | 使用流式响应,获得更快的响应 | `false` | |
|
290
|
+
| proxy | 支持 HTTP 代理,传入 http proxy URL | "" | |
|
291
|
+
| gpt_options | OpenAI API 的参数字典 | `{}` | |
|
292
|
+
| bing_cookie_path | NewBing使用的cookie路径,参考[这里]获取 | 也可通过环境变量 `COOKIE_FILE` 设置 | |
|
293
|
+
| bing_cookies | NewBing使用的cookie字典,参考[这里]获取 | | |
|
294
|
+
| deployment_id | Azure OpenAI 服务的 deployment ID | 参考这个[如何找到deployment_id](https://github.com/yihong0618/xiaogpt/issues/347#issuecomment-1784410784) | |
|
295
|
+
| api_base | 如果需要替换默认的api,或者使用Azure OpenAI 服务 | 例如:`https://abc-def.openai.azure.com/` | |
|
296
|
+
|
313
297
|
|
314
298
|
[这里]: https://github.com/acheong08/EdgeGPT#getting-authentication-required
|
315
299
|
|
@@ -427,6 +411,7 @@ docker run -v <your-config-dir>:/config -p 9527:9527 -e XIAOGPT_HOSTNAME=<your i
|
|
427
411
|
|
428
412
|
- [xiaomi](https://www.mi.com/)
|
429
413
|
- [PDM](https://pdm.fming.dev/latest/)
|
414
|
+
- [Tetos](https://github.com/frostming/tetos) TTS 云服务支持
|
430
415
|
- @[Yonsm](https://github.com/Yonsm) 的 [MiService](https://github.com/Yonsm/MiService)
|
431
416
|
- @[pjq](https://github.com/pjq) 给了这个项目非常多的帮助
|
432
417
|
- @[frostming](https://github.com/frostming) 重构了一些代码,支持了`持续会话功能`
|
@@ -19,33 +19,16 @@ Play ChatGPT and other LLM with Xiaomi AI Speaker
|
|
19
19
|
- [通义千问](https://help.aliyun.com/zh/dashscope/developer-reference/api-details)
|
20
20
|
|
21
21
|
## 获取小米音响DID
|
22
|
-
|
23
|
-
|
24
|
-
pip install miservice_fork
|
25
|
-
set MI_USER=
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
22
|
+
| 系统和Shell | Linux *sh | Windows CMD用户 | Windows PowerShell用户 |
|
23
|
+
| ------------- | ---------------------------------------------- | -------------------------------------- | ---------------------------------------------- |
|
24
|
+
| 1、安装包 | `pip install miservice_fork` | `pip install miservice_fork` | `pip install miservice_fork` |
|
25
|
+
| 2、设置变量 | `export MI_USER=xxx` <br> `export MI_PASS=xxx` | `set MI_USER=xxx`<br>`set MI_PASS=xxx` | `$env:MI_USER="xxx"` <br> `$env:MI_PASS="xxx"` |
|
26
|
+
| 3、取得MI_DID | `micli list` | `micli list` | `micli list` |
|
27
|
+
| 4、设置MI_DID | `export MI_DID=xxx` | `set MI_DID=xxx` | `$env:MI_DID="xxx"` |
|
28
|
+
|
29
|
+
- 注意不同shell 对环境变量的处理是不同的,尤其是powershell赋值时,可能需要双引号来包括值。
|
31
30
|
- 如果获取did报错时,请更换一下无线网络,有很大概率解决问题。
|
32
31
|
|
33
|
-
### Linux(使用 export 设置环境变量)
|
34
|
-
```sh
|
35
|
-
# 1、安装模块
|
36
|
-
pip install miservice_fork
|
37
|
-
|
38
|
-
# 2、设置环境用户参数
|
39
|
-
export MI_USER=xxxx
|
40
|
-
export MI_PASS=xxx
|
41
|
-
|
42
|
-
# 3、使用micli list 得到did
|
43
|
-
micli list
|
44
|
-
|
45
|
-
# 4、根据did设置环境DID参数
|
46
|
-
export MI_DID=xxxx
|
47
|
-
```
|
48
|
-
|
49
32
|
## 一点原理
|
50
33
|
|
51
34
|
[不用 root 使用小爱同学和 ChatGPT 交互折腾记](https://github.com/yihong0618/gitblog/issues/258)
|
@@ -163,38 +146,37 @@ ChatGLM [文档](http://open.bigmodel.cn/doc/api#chatglm_130b)
|
|
163
146
|
|
164
147
|
## 配置项说明
|
165
148
|
|
166
|
-
| 参数
|
167
|
-
|
|
168
|
-
| hardware
|
169
|
-
| account
|
170
|
-
| password
|
171
|
-
| openai_key
|
172
|
-
| serpapi_api_key
|
173
|
-
| glm_key
|
174
|
-
| gemini_key
|
175
|
-
| qwen_key
|
176
|
-
| cookie
|
177
|
-
| mi_did
|
178
|
-
| use_command
|
179
|
-
| mute_xiaoai
|
180
|
-
| verbose
|
181
|
-
| bot
|
182
|
-
| tts
|
183
|
-
|
|
184
|
-
| prompt
|
185
|
-
| keyword
|
186
|
-
| change_prompt_keyword
|
187
|
-
| start_conversation
|
188
|
-
| end_conversation
|
189
|
-
| stream
|
190
|
-
| proxy
|
191
|
-
| gpt_options
|
192
|
-
| bing_cookie_path
|
193
|
-
| bing_cookies
|
194
|
-
| deployment_id
|
195
|
-
| api_base
|
196
|
-
|
197
|
-
| azure_tts_service_region | Azure TTS 服务地区 | `eastasia` | [Regions - Speech service - Azure AI services](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/regions) |
|
149
|
+
| 参数 | 说明 | 默认值 | 可选值 |
|
150
|
+
| --------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
|
151
|
+
| hardware | 设备型号 | | |
|
152
|
+
| account | 小爱账户 | | |
|
153
|
+
| password | 小爱账户密码 | | |
|
154
|
+
| openai_key | openai的apikey | | |
|
155
|
+
| serpapi_api_key | serpapi的key 参考 [SerpAPI](https://serpapi.com/) | | |
|
156
|
+
| glm_key | chatglm 的 apikey | | |
|
157
|
+
| gemini_key | gemini 的 apikey [参考](https://makersuite.google.com/app/apikey) | | |
|
158
|
+
| qwen_key | qwen 的 apikey [参考](https://help.aliyun.com/zh/dashscope/developer-reference/api-details) | | |
|
159
|
+
| cookie | 小爱账户cookie (如果用上面密码登录可以不填) | | |
|
160
|
+
| mi_did | 设备did | | |
|
161
|
+
| use_command | 使用 MI command 与小爱交互 | `false` | |
|
162
|
+
| mute_xiaoai | 快速停掉小爱自己的回答 | `true` | |
|
163
|
+
| verbose | 是否打印详细日志 | `false` | |
|
164
|
+
| bot | 使用的 bot 类型,目前支持 chatgptapi,newbing, qwen, gemini | `chatgptapi` | |
|
165
|
+
| tts | 使用的 TTS 类型 | `mi` | `edge`、 `openai`、`azure`、`volc`、`baidu`、`google` |
|
166
|
+
| tts_options | TTS 参数字典,参考 [tetos](https://github.com/frostming/tetos) 获取可用参数 | | |
|
167
|
+
| prompt | 自定义prompt | `请用100字以内回答` | |
|
168
|
+
| keyword | 自定义请求词列表 | `["请"]` | |
|
169
|
+
| change_prompt_keyword | 更改提示词触发列表 | `["更改提示词"]` | |
|
170
|
+
| start_conversation | 开始持续对话关键词 | `开始持续对话` | |
|
171
|
+
| end_conversation | 结束持续对话关键词 | `结束持续对话` | |
|
172
|
+
| stream | 使用流式响应,获得更快的响应 | `false` | |
|
173
|
+
| proxy | 支持 HTTP 代理,传入 http proxy URL | "" | |
|
174
|
+
| gpt_options | OpenAI API 的参数字典 | `{}` | |
|
175
|
+
| bing_cookie_path | NewBing使用的cookie路径,参考[这里]获取 | 也可通过环境变量 `COOKIE_FILE` 设置 | |
|
176
|
+
| bing_cookies | NewBing使用的cookie字典,参考[这里]获取 | | |
|
177
|
+
| deployment_id | Azure OpenAI 服务的 deployment ID | 参考这个[如何找到deployment_id](https://github.com/yihong0618/xiaogpt/issues/347#issuecomment-1784410784) | |
|
178
|
+
| api_base | 如果需要替换默认的api,或者使用Azure OpenAI 服务 | 例如:`https://abc-def.openai.azure.com/` | |
|
179
|
+
|
198
180
|
|
199
181
|
[这里]: https://github.com/acheong08/EdgeGPT#getting-authentication-required
|
200
182
|
|
@@ -312,6 +294,7 @@ docker run -v <your-config-dir>:/config -p 9527:9527 -e XIAOGPT_HOSTNAME=<your i
|
|
312
294
|
|
313
295
|
- [xiaomi](https://www.mi.com/)
|
314
296
|
- [PDM](https://pdm.fming.dev/latest/)
|
297
|
+
- [Tetos](https://github.com/frostming/tetos) TTS 云服务支持
|
315
298
|
- @[Yonsm](https://github.com/Yonsm) 的 [MiService](https://github.com/Yonsm/MiService)
|
316
299
|
- @[pjq](https://github.com/pjq) 给了这个项目非常多的帮助
|
317
300
|
- @[frostming](https://github.com/frostming) 重构了一些代码,支持了`持续会话功能`
|
@@ -18,7 +18,6 @@ dependencies = [
|
|
18
18
|
"rich",
|
19
19
|
"zhipuai>=2.0.1",
|
20
20
|
"httpx[socks]",
|
21
|
-
"edge-tts>=6.1.3",
|
22
21
|
"EdgeGPT==0.1.26",
|
23
22
|
"langchain>=0.0.343",
|
24
23
|
"beautifulsoup4>=4.12.0",
|
@@ -26,10 +25,10 @@ dependencies = [
|
|
26
25
|
"google-generativeai",
|
27
26
|
"numexpr>=2.8.6",
|
28
27
|
"dashscope>=1.10.0",
|
29
|
-
"
|
28
|
+
"tetos>=0.1.0",
|
30
29
|
]
|
31
30
|
dynamic = []
|
32
|
-
version = "2.
|
31
|
+
version = "2.60"
|
33
32
|
|
34
33
|
[project.license]
|
35
34
|
text = "MIT"
|
@@ -42,10 +41,10 @@ xiaogpt = "xiaogpt.cli:main"
|
|
42
41
|
|
43
42
|
[project.optional-dependencies]
|
44
43
|
locked = [
|
45
|
-
"aiohttp==3.9.
|
44
|
+
"aiohttp==3.9.5",
|
46
45
|
"aiosignal==1.3.1",
|
47
46
|
"annotated-types==0.6.0",
|
48
|
-
"anyio==3.
|
47
|
+
"anyio==4.3.0",
|
49
48
|
"async-timeout==4.0.3 ; python_version < \"3.11\"",
|
50
49
|
"attrs==23.2.0",
|
51
50
|
"azure-cognitiveservices-speech==1.37.0",
|
@@ -54,21 +53,23 @@ locked = [
|
|
54
53
|
"cachetools==5.3.2",
|
55
54
|
"certifi==2024.2.2",
|
56
55
|
"charset-normalizer==3.3.2",
|
56
|
+
"click==8.1.7",
|
57
57
|
"colorama==0.4.6 ; platform_system == \"Windows\"",
|
58
|
-
"dashscope==1.
|
58
|
+
"dashscope==1.17.0",
|
59
59
|
"dataclasses-json==0.6.3",
|
60
60
|
"distro==1.9.0",
|
61
61
|
"edge-tts==6.1.10",
|
62
62
|
"edgegpt==0.1.26",
|
63
63
|
"exceptiongroup==1.2.0 ; python_version < \"3.11\"",
|
64
64
|
"frozenlist==1.4.1",
|
65
|
-
"google-ai-generativelanguage==0.6.
|
65
|
+
"google-ai-generativelanguage==0.6.2",
|
66
66
|
"google-api-core==2.15.0",
|
67
67
|
"google-api-core[grpc]==2.15.0",
|
68
68
|
"google-api-python-client==2.125.0",
|
69
69
|
"google-auth==2.26.1",
|
70
70
|
"google-auth-httplib2==0.2.0",
|
71
|
-
"google-
|
71
|
+
"google-cloud-texttospeech==2.16.3",
|
72
|
+
"google-generativeai==0.5.1",
|
72
73
|
"google-search-results==2.4.2",
|
73
74
|
"googleapis-common-protos==1.62.0",
|
74
75
|
"greenlet==3.0.3 ; platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\"",
|
@@ -91,12 +92,12 @@ locked = [
|
|
91
92
|
"marshmallow==3.20.1",
|
92
93
|
"mdurl==0.1.2",
|
93
94
|
"miservice-fork==2.4.3",
|
94
|
-
"multidict==6.0.
|
95
|
+
"multidict==6.0.5",
|
95
96
|
"mutagen==1.47.0",
|
96
97
|
"mypy-extensions==1.0.0",
|
97
98
|
"numexpr==2.10.0",
|
98
99
|
"numpy==1.26.3",
|
99
|
-
"openai==1.
|
100
|
+
"openai==1.21.2",
|
100
101
|
"orjson==3.10.0",
|
101
102
|
"packaging==23.2",
|
102
103
|
"prompt-toolkit==3.0.43",
|
@@ -119,6 +120,7 @@ locked = [
|
|
119
120
|
"soupsieve==2.5",
|
120
121
|
"sqlalchemy==2.0.25",
|
121
122
|
"tenacity==8.2.3",
|
123
|
+
"tetos==0.1.0",
|
122
124
|
"tqdm==4.66.1",
|
123
125
|
"typing-extensions==4.9.0",
|
124
126
|
"typing-inspect==0.9.0",
|
@@ -86,27 +86,9 @@ def main():
|
|
86
86
|
help="show info",
|
87
87
|
)
|
88
88
|
parser.add_argument(
|
89
|
-
"--azure_tts_speech_key",
|
90
|
-
dest="azure_tts_speech_key",
|
91
|
-
help="if use azure tts",
|
92
|
-
)
|
93
|
-
parser.add_argument(
|
94
|
-
"--azure_tts_service_region",
|
95
|
-
dest="azure_tts_service_region",
|
96
|
-
help="if use azure tts",
|
97
|
-
)
|
98
|
-
tts_group = parser.add_mutually_exclusive_group()
|
99
|
-
tts_group.add_argument(
|
100
|
-
"--enable_edge_tts",
|
101
|
-
dest="tts",
|
102
|
-
action="store_const",
|
103
|
-
const="edge",
|
104
|
-
help="if use edge tts",
|
105
|
-
)
|
106
|
-
tts_group.add_argument(
|
107
89
|
"--tts",
|
108
|
-
help="
|
109
|
-
choices=["mi", "edge", "openai", "azure"],
|
90
|
+
help="TTS provider",
|
91
|
+
choices=["mi", "edge", "openai", "azure", "google", "baidu", "volc"],
|
110
92
|
)
|
111
93
|
bot_group = parser.add_mutually_exclusive_group()
|
112
94
|
bot_group.add_argument(
|
@@ -190,9 +172,15 @@ def main():
|
|
190
172
|
options = parser.parse_args()
|
191
173
|
config = Config.from_options(options)
|
192
174
|
|
193
|
-
|
175
|
+
async def main(config: Config) -> None:
|
176
|
+
miboy = MiGPT(config)
|
177
|
+
try:
|
178
|
+
await miboy.run_forever()
|
179
|
+
finally:
|
180
|
+
await miboy.close()
|
181
|
+
|
194
182
|
loop = asyncio.get_event_loop()
|
195
|
-
loop.run_until_complete(
|
183
|
+
loop.run_until_complete(main(config))
|
196
184
|
|
197
185
|
|
198
186
|
if __name__ == "__main__":
|
@@ -33,15 +33,6 @@ HARDWARE_COMMAND_DICT = {
|
|
33
33
|
# add more here
|
34
34
|
}
|
35
35
|
|
36
|
-
EDGE_TTS_DICT = {
|
37
|
-
"用英语": "en-US-AriaNeural",
|
38
|
-
"用日语": "ja-JP-NanamiNeural",
|
39
|
-
"用法语": "fr-BE-CharlineNeural",
|
40
|
-
"用韩语": "ko-KR-SunHiNeural",
|
41
|
-
"用德语": "de-AT-JonasNeural",
|
42
|
-
# add more here
|
43
|
-
}
|
44
|
-
|
45
36
|
DEFAULT_COMMAND = ("5-1", "5-5")
|
46
37
|
|
47
38
|
KEY_WORD = ("帮我", "请")
|
@@ -80,13 +71,11 @@ class Config:
|
|
80
71
|
start_conversation: str = "开始持续对话"
|
81
72
|
end_conversation: str = "结束持续对话"
|
82
73
|
stream: bool = False
|
83
|
-
tts: Literal["mi", "edge", "azure", "openai"] = "mi"
|
84
|
-
|
74
|
+
tts: Literal["mi", "edge", "azure", "openai", "baidu", "google", "volc"] = "mi"
|
75
|
+
tts_options: dict[str, Any] = field(default_factory=dict)
|
85
76
|
gpt_options: dict[str, Any] = field(default_factory=dict)
|
86
77
|
bing_cookie_path: str = ""
|
87
78
|
bing_cookies: dict | None = None
|
88
|
-
azure_tts_speech_key: str | None = None
|
89
|
-
azure_tts_service_region: str = "eastasia"
|
90
79
|
|
91
80
|
def __post_init__(self) -> None:
|
92
81
|
if self.proxy:
|
@@ -111,8 +100,6 @@ class Config:
|
|
111
100
|
raise Exception(
|
112
101
|
"Using GPT api needs openai API key, please google how to"
|
113
102
|
)
|
114
|
-
if self.tts == "azure" and not self.azure_tts_speech_key:
|
115
|
-
raise Exception("Using Azure TTS needs azure speech key")
|
116
103
|
|
117
104
|
@property
|
118
105
|
def tts_command(self) -> str:
|
@@ -56,7 +56,7 @@ class TTS(abc.ABC):
|
|
56
56
|
return is_playing
|
57
57
|
|
58
58
|
@abc.abstractmethod
|
59
|
-
async def synthesize(self,
|
59
|
+
async def synthesize(self, lang: str, text_stream: AsyncIterator[str]) -> None:
|
60
60
|
"""Synthesize speech from a stream of text."""
|
61
61
|
raise NotImplementedError
|
62
62
|
|
@@ -87,20 +87,20 @@ class AudioFileTTS(TTS):
|
|
87
87
|
self._start_http_server()
|
88
88
|
|
89
89
|
@abc.abstractmethod
|
90
|
-
async def make_audio_file(self,
|
90
|
+
async def make_audio_file(self, lang: str, text: str) -> tuple[Path, float]:
|
91
91
|
"""Synthesize speech from text and save it to a file.
|
92
92
|
Return the file path and the duration of the audio in seconds.
|
93
93
|
The file path must be relative to the self.dirname.
|
94
94
|
"""
|
95
95
|
raise NotImplementedError
|
96
96
|
|
97
|
-
async def synthesize(self,
|
97
|
+
async def synthesize(self, lang: str, text_stream: AsyncIterator[str]) -> None:
|
98
98
|
queue: asyncio.Queue[tuple[str, float]] = asyncio.Queue()
|
99
99
|
finished = asyncio.Event()
|
100
100
|
|
101
101
|
async def worker():
|
102
102
|
async for text in text_stream:
|
103
|
-
path, duration = await self.make_audio_file(
|
103
|
+
path, duration = await self.make_audio_file(lang, text)
|
104
104
|
url = f"http://{self.hostname}:{self.port}/{path.name}"
|
105
105
|
await queue.put((url, duration))
|
106
106
|
finished.set()
|
@@ -27,7 +27,7 @@ class MiTTS(TTS):
|
|
27
27
|
f"{self.config.tts_command} {text}",
|
28
28
|
)
|
29
29
|
|
30
|
-
async def synthesize(self,
|
30
|
+
async def synthesize(self, lang: str, text_stream: AsyncIterator[str]) -> None:
|
31
31
|
async for text in text_stream:
|
32
32
|
await self.say(text)
|
33
33
|
await self.wait_for_duration(calculate_tts_elapse(text))
|
@@ -0,0 +1,56 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import tempfile
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
from miservice import MiNAService
|
7
|
+
from tetos.base import Speaker
|
8
|
+
|
9
|
+
from xiaogpt.config import Config
|
10
|
+
from xiaogpt.tts.base import AudioFileTTS
|
11
|
+
|
12
|
+
|
13
|
+
class TetosTTS(AudioFileTTS):
|
14
|
+
def __init__(
|
15
|
+
self, mina_service: MiNAService, device_id: str, config: Config
|
16
|
+
) -> None:
|
17
|
+
super().__init__(mina_service, device_id, config)
|
18
|
+
self.speaker = self._get_speaker()
|
19
|
+
|
20
|
+
def _get_speaker(self) -> Speaker:
|
21
|
+
from tetos.azure import AzureSpeaker
|
22
|
+
from tetos.baidu import BaiduSpeaker
|
23
|
+
from tetos.edge import EdgeSpeaker
|
24
|
+
from tetos.google import GoogleSpeaker
|
25
|
+
from tetos.openai import OpenAISpeaker
|
26
|
+
from tetos.volc import VolcSpeaker
|
27
|
+
|
28
|
+
options = self.config.tts_options
|
29
|
+
allowed_speakers: list[str] = []
|
30
|
+
for speaker in (
|
31
|
+
OpenAISpeaker,
|
32
|
+
EdgeSpeaker,
|
33
|
+
AzureSpeaker,
|
34
|
+
VolcSpeaker,
|
35
|
+
GoogleSpeaker,
|
36
|
+
BaiduSpeaker,
|
37
|
+
):
|
38
|
+
if (name := speaker.__name__[:-7].lower()) == self.config.tts:
|
39
|
+
try:
|
40
|
+
return speaker(**options)
|
41
|
+
except TypeError as e:
|
42
|
+
raise ValueError(
|
43
|
+
f"{e}. Please add them via `tts_options` config"
|
44
|
+
) from e
|
45
|
+
else:
|
46
|
+
allowed_speakers.append(name)
|
47
|
+
raise ValueError(
|
48
|
+
f"Unsupported TTS: {self.config.tts}, allowed: {','.join(allowed_speakers)}"
|
49
|
+
)
|
50
|
+
|
51
|
+
async def make_audio_file(self, lang: str, text: str) -> tuple[Path, float]:
|
52
|
+
output_file = tempfile.NamedTemporaryFile(
|
53
|
+
suffix=".mp3", mode="wb", delete=False, dir=self.dirname.name
|
54
|
+
)
|
55
|
+
duration = await self.speaker.synthesize(text, output_file.name, lang=lang)
|
56
|
+
return Path(output_file.name), duration
|
@@ -23,8 +23,7 @@ from xiaogpt.config import (
|
|
23
23
|
WAKEUP_KEYWORD,
|
24
24
|
Config,
|
25
25
|
)
|
26
|
-
from xiaogpt.tts import TTS,
|
27
|
-
from xiaogpt.tts.openai import OpenAITTS
|
26
|
+
from xiaogpt.tts import TTS, MiTTS, TetosTTS
|
28
27
|
from xiaogpt.utils import (
|
29
28
|
parse_cookie_string,
|
30
29
|
)
|
@@ -53,6 +52,9 @@ class MiGPT:
|
|
53
52
|
self.log.debug(config)
|
54
53
|
self.mi_session = ClientSession()
|
55
54
|
|
55
|
+
async def close(self):
|
56
|
+
await self.mi_session.close()
|
57
|
+
|
56
58
|
async def poll_latest_ask(self):
|
57
59
|
async with ClientSession() as session:
|
58
60
|
session._cookie_jar = self.cookie_jar
|
@@ -62,7 +64,9 @@ class MiGPT:
|
|
62
64
|
)
|
63
65
|
new_record = await self.get_latest_ask_from_xiaoai(session)
|
64
66
|
start = time.perf_counter()
|
65
|
-
self.log.debug(
|
67
|
+
self.log.debug(
|
68
|
+
"Polling_event, timestamp: %s %s", self.last_timestamp, new_record
|
69
|
+
)
|
66
70
|
await self.polling_event.wait()
|
67
71
|
if (
|
68
72
|
self.config.mute_xiaoai
|
@@ -76,16 +80,16 @@ class MiGPT:
|
|
76
80
|
# if you want force mute xiaoai, comment this line below.
|
77
81
|
await asyncio.sleep(1 - d)
|
78
82
|
|
79
|
-
async def init_all_data(self
|
80
|
-
await self.login_miboy(
|
83
|
+
async def init_all_data(self):
|
84
|
+
await self.login_miboy()
|
81
85
|
await self._init_data_hardware()
|
82
86
|
self.mi_session.cookie_jar.update_cookies(self.get_cookie())
|
83
87
|
self.cookie_jar = self.mi_session.cookie_jar
|
84
88
|
self.tts # init tts
|
85
89
|
|
86
|
-
async def login_miboy(self
|
90
|
+
async def login_miboy(self):
|
87
91
|
account = MiAccount(
|
88
|
-
|
92
|
+
self.mi_session,
|
89
93
|
self.config.account,
|
90
94
|
self.config.password,
|
91
95
|
str(self.mi_token_home),
|
@@ -177,7 +181,7 @@ class MiGPT:
|
|
177
181
|
return (
|
178
182
|
self.in_conversation
|
179
183
|
and not query.startswith(WAKEUP_KEYWORD)
|
180
|
-
or query.startswith(tuple(self.config.keyword))
|
184
|
+
or query.lower().startswith(tuple(w.lower() for w in self.config.keyword))
|
181
185
|
)
|
182
186
|
|
183
187
|
def need_change_prompt(self, record):
|
@@ -223,7 +227,7 @@ class MiGPT:
|
|
223
227
|
return None
|
224
228
|
|
225
229
|
async def _retry(self):
|
226
|
-
await self.init_all_data(
|
230
|
+
await self.init_all_data()
|
227
231
|
|
228
232
|
def _get_last_query(self, data: dict) -> dict | None:
|
229
233
|
if d := data.get("data"):
|
@@ -256,14 +260,10 @@ class MiGPT:
|
|
256
260
|
|
257
261
|
@functools.cached_property
|
258
262
|
def tts(self) -> TTS:
|
259
|
-
if self.config.tts == "
|
260
|
-
return EdgeTTS(self.mina_service, self.device_id, self.config)
|
261
|
-
elif self.config.tts == "azure":
|
262
|
-
return AzureTTS(self.mina_service, self.device_id, self.config)
|
263
|
-
elif self.config.tts == "openai":
|
264
|
-
return OpenAITTS(self.mina_service, self.device_id, self.config)
|
265
|
-
else:
|
263
|
+
if self.config.tts == "mi":
|
266
264
|
return MiTTS(self.mina_service, self.device_id, self.config)
|
265
|
+
else:
|
266
|
+
return TetosTTS(self.mina_service, self.device_id, self.config)
|
267
267
|
|
268
268
|
async def wait_for_tts_finish(self):
|
269
269
|
while True:
|
@@ -343,7 +343,7 @@ class MiGPT:
|
|
343
343
|
)
|
344
344
|
|
345
345
|
async def run_forever(self):
|
346
|
-
await self.init_all_data(
|
346
|
+
await self.init_all_data()
|
347
347
|
task = asyncio.create_task(self.poll_latest_ask())
|
348
348
|
assert task is not None # to keep the reference to task, do not remove this
|
349
349
|
print(
|
@@ -355,7 +355,6 @@ class MiGPT:
|
|
355
355
|
new_record = await self.last_record.get()
|
356
356
|
self.polling_event.clear() # stop polling when processing the question
|
357
357
|
query = new_record.get("query", "").strip()
|
358
|
-
|
359
358
|
if query == self.config.start_conversation:
|
360
359
|
if not self.in_conversation:
|
361
360
|
print("开始对话")
|
@@ -386,6 +385,7 @@ class MiGPT:
|
|
386
385
|
print("问题:" + query + "?")
|
387
386
|
if not self.chatbot.has_history():
|
388
387
|
query = f"{query},{self.config.prompt}"
|
388
|
+
query += ",并用本段话的language code作为开头,用|分隔,如:en-US|你好……"
|
389
389
|
if self.config.mute_xiaoai:
|
390
390
|
await self.stop_if_xiaoai_is_playing()
|
391
391
|
else:
|
@@ -401,7 +401,7 @@ class MiGPT:
|
|
401
401
|
print("小爱没回")
|
402
402
|
print(f"以下是 {self.chatbot.name} 的回答: ", end="")
|
403
403
|
try:
|
404
|
-
await self.
|
404
|
+
await self.speak(self.ask_gpt(query))
|
405
405
|
except Exception as e:
|
406
406
|
print(f"{self.chatbot.name} 回答出错 {str(e)}")
|
407
407
|
else:
|
@@ -409,3 +409,18 @@ class MiGPT:
|
|
409
409
|
if self.in_conversation:
|
410
410
|
print(f"继续对话, 或用`{self.config.end_conversation}`结束对话")
|
411
411
|
await self.wakeup_xiaoai()
|
412
|
+
|
413
|
+
async def speak(self, text_stream: AsyncIterator[str]) -> None:
|
414
|
+
text = await anext(text_stream)
|
415
|
+
# See if the first part contains language code(e.g. en-US|Hello world)
|
416
|
+
lang, _, first_chunk = text.rpartition("|")
|
417
|
+
if len(lang) > 7:
|
418
|
+
# It is not a legal language code, discard it
|
419
|
+
lang, first_chunk = "", text
|
420
|
+
|
421
|
+
async def gen(): # reconstruct the generator
|
422
|
+
yield first_chunk
|
423
|
+
async for text in text_stream:
|
424
|
+
yield text
|
425
|
+
|
426
|
+
await self.tts.synthesize(lang or "zh-CN", gen())
|
@@ -1,98 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import logging
|
4
|
-
import tempfile
|
5
|
-
from pathlib import Path
|
6
|
-
from typing import Optional
|
7
|
-
|
8
|
-
import azure.cognitiveservices.speech as speechsdk
|
9
|
-
|
10
|
-
from xiaogpt.tts.base import AudioFileTTS
|
11
|
-
from xiaogpt.utils import calculate_tts_elapse
|
12
|
-
|
13
|
-
logger = logging.getLogger(__name__)
|
14
|
-
|
15
|
-
|
16
|
-
class AzureTTS(AudioFileTTS):
|
17
|
-
voice_name = "zh-CN-XiaoxiaoMultilingualNeural"
|
18
|
-
|
19
|
-
async def make_audio_file(self, query: str, text: str) -> tuple[Path, float]:
|
20
|
-
output_file = tempfile.NamedTemporaryFile(
|
21
|
-
suffix=".mp3", mode="wb", delete=False, dir=self.dirname.name
|
22
|
-
)
|
23
|
-
|
24
|
-
speech_synthesizer = self._build_speech_synthesizer(output_file.name)
|
25
|
-
result: Optional[speechsdk.SpeechSynthesisResult] = (
|
26
|
-
speech_synthesizer.speak_text_async(text).get()
|
27
|
-
)
|
28
|
-
if result is None:
|
29
|
-
raise RuntimeError(
|
30
|
-
f"Failed to get tts from azure with voice={self.voice_name}"
|
31
|
-
)
|
32
|
-
# Check result
|
33
|
-
if result.reason == speechsdk.ResultReason.SynthesizingAudioCompleted:
|
34
|
-
logger.debug("Speech synthesized for text [{}]".format(text))
|
35
|
-
|
36
|
-
return Path(output_file.name), calculate_tts_elapse(text)
|
37
|
-
elif result.reason == speechsdk.ResultReason.Canceled:
|
38
|
-
cancellation_details = result.cancellation_details
|
39
|
-
logger.warning(f"Speech synthesis canceled: {cancellation_details.reason}")
|
40
|
-
if cancellation_details.reason == speechsdk.CancellationReason.Error:
|
41
|
-
errmsg = f"Error details: {cancellation_details.error_details}"
|
42
|
-
logger.error(errmsg)
|
43
|
-
raise RuntimeError(errmsg)
|
44
|
-
raise RuntimeError(f"Failed to get tts from azure with voice={self.voice_name}")
|
45
|
-
|
46
|
-
def _build_speech_synthesizer(self, filename: str):
|
47
|
-
speech_key = self.config.azure_tts_speech_key
|
48
|
-
service_region = self.config.azure_tts_service_region
|
49
|
-
if not speech_key:
|
50
|
-
raise Exception("Azure tts need speech key")
|
51
|
-
speech_config = speechsdk.SpeechConfig(
|
52
|
-
subscription=speech_key, region=service_region
|
53
|
-
)
|
54
|
-
speech_config.set_speech_synthesis_output_format(
|
55
|
-
speechsdk.SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3
|
56
|
-
)
|
57
|
-
if self.config.proxy:
|
58
|
-
host, port, username, password = self._parse_proxy(self.config.proxy)
|
59
|
-
|
60
|
-
if username and password:
|
61
|
-
speech_config.set_proxy(
|
62
|
-
hostname=host, port=port, username=username, password=password
|
63
|
-
)
|
64
|
-
else:
|
65
|
-
speech_config.set_proxy(hostname=host, port=port)
|
66
|
-
|
67
|
-
speech_config.speech_synthesis_voice_name = (
|
68
|
-
self.config.tts_voice or self.voice_name
|
69
|
-
)
|
70
|
-
speech_synthesizer = speechsdk.SpeechSynthesizer(
|
71
|
-
speech_config=speech_config,
|
72
|
-
audio_config=speechsdk.audio.AudioOutputConfig(filename=filename), # type: ignore
|
73
|
-
)
|
74
|
-
return speech_synthesizer
|
75
|
-
|
76
|
-
def _parse_proxy(self, proxy_str: str):
|
77
|
-
proxy_str = proxy_str
|
78
|
-
proxy_str_splited = proxy_str.split("://")
|
79
|
-
proxy_type = proxy_str_splited[0]
|
80
|
-
proxy_addr = proxy_str_splited[1]
|
81
|
-
|
82
|
-
if proxy_type == "http":
|
83
|
-
if "@" in proxy_addr:
|
84
|
-
proxy_addr_splited = proxy_addr.split("@")
|
85
|
-
proxy_auth = proxy_addr_splited[0]
|
86
|
-
proxy_addr_netloc = proxy_addr_splited[1]
|
87
|
-
proxy_auth_splited = proxy_auth.split(":")
|
88
|
-
username = proxy_auth_splited[0]
|
89
|
-
password = proxy_auth_splited[1]
|
90
|
-
else:
|
91
|
-
proxy_addr_netloc = proxy_addr
|
92
|
-
username, password = None, None
|
93
|
-
|
94
|
-
proxy_addr_netloc_splited = proxy_addr_netloc.split(":")
|
95
|
-
host = proxy_addr_netloc_splited[0]
|
96
|
-
port = int(proxy_addr_netloc_splited[1])
|
97
|
-
return host, port, username, password
|
98
|
-
raise NotImplementedError
|
xiaogpt-2.42/xiaogpt/tts/edge.py
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
import tempfile
|
2
|
-
from pathlib import Path
|
3
|
-
|
4
|
-
import edge_tts
|
5
|
-
|
6
|
-
from xiaogpt.config import EDGE_TTS_DICT
|
7
|
-
from xiaogpt.tts.base import AudioFileTTS
|
8
|
-
from xiaogpt.utils import find_key_by_partial_string
|
9
|
-
|
10
|
-
|
11
|
-
class EdgeTTS(AudioFileTTS):
|
12
|
-
default_voice = "zh-CN-XiaoxiaoNeural"
|
13
|
-
|
14
|
-
async def make_audio_file(self, query: str, text: str) -> tuple[Path, float]:
|
15
|
-
voice = (
|
16
|
-
find_key_by_partial_string(EDGE_TTS_DICT, query)
|
17
|
-
or self.config.tts_voice
|
18
|
-
or self.default_voice
|
19
|
-
)
|
20
|
-
communicate = edge_tts.Communicate(text, voice, proxy=self.config.proxy)
|
21
|
-
duration = 0
|
22
|
-
with tempfile.NamedTemporaryFile(
|
23
|
-
suffix=".mp3", mode="wb", delete=False, dir=self.dirname.name
|
24
|
-
) as f:
|
25
|
-
async for chunk in communicate.stream():
|
26
|
-
if chunk["type"] == "audio":
|
27
|
-
f.write(chunk["data"])
|
28
|
-
elif chunk["type"] == "WordBoundary":
|
29
|
-
duration = (chunk["offset"] + chunk["duration"]) / 1e7
|
30
|
-
if duration == 0:
|
31
|
-
raise RuntimeError(f"Failed to get tts from edge with voice={voice}")
|
32
|
-
return (Path(f.name), duration)
|
@@ -1,46 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import tempfile
|
4
|
-
from pathlib import Path
|
5
|
-
from typing import TYPE_CHECKING
|
6
|
-
|
7
|
-
import httpx
|
8
|
-
|
9
|
-
from xiaogpt.tts.base import AudioFileTTS
|
10
|
-
from xiaogpt.utils import calculate_tts_elapse
|
11
|
-
|
12
|
-
if TYPE_CHECKING:
|
13
|
-
import openai
|
14
|
-
|
15
|
-
|
16
|
-
class OpenAITTS(AudioFileTTS):
|
17
|
-
default_voice = "alloy"
|
18
|
-
|
19
|
-
async def make_audio_file(self, query: str, text: str) -> tuple[Path, float]:
|
20
|
-
output_file = tempfile.NamedTemporaryFile(
|
21
|
-
suffix=".mp3", mode="wb", delete=False, dir=self.dirname.name
|
22
|
-
)
|
23
|
-
httpx_kwargs = {}
|
24
|
-
if self.config.proxy:
|
25
|
-
httpx_kwargs["proxies"] = self.config.proxy
|
26
|
-
async with httpx.AsyncClient(trust_env=True, **httpx_kwargs) as sess:
|
27
|
-
client = self._make_openai_client(sess)
|
28
|
-
|
29
|
-
resp = await client.audio.speech.create(
|
30
|
-
model="tts-1",
|
31
|
-
input=text,
|
32
|
-
voice=self.config.tts_voice or self.default_voice,
|
33
|
-
)
|
34
|
-
resp.stream_to_file(output_file.name)
|
35
|
-
return Path(output_file.name), calculate_tts_elapse(text)
|
36
|
-
|
37
|
-
def _make_openai_client(self, sess: httpx.AsyncClient) -> openai.AsyncOpenAI:
|
38
|
-
import openai
|
39
|
-
|
40
|
-
api_base = self.config.api_base
|
41
|
-
if api_base and api_base.rstrip("/").endswith("openai.azure.com"):
|
42
|
-
raise NotImplementedError("TTS is not supported for Azure OpenAI")
|
43
|
-
else:
|
44
|
-
return openai.AsyncOpenAI(
|
45
|
-
api_key=self.config.openai_key, http_client=sess, base_url=api_base
|
46
|
-
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|