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.
Files changed (30) hide show
  1. {xiaogpt-2.42 → xiaogpt-2.60}/PKG-INFO +52 -67
  2. {xiaogpt-2.42 → xiaogpt-2.60}/README.md +40 -57
  3. {xiaogpt-2.42 → xiaogpt-2.60}/pyproject.toml +12 -10
  4. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/cli.py +10 -22
  5. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/config.py +2 -15
  6. xiaogpt-2.60/xiaogpt/tts/__init__.py +5 -0
  7. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/tts/base.py +4 -4
  8. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/tts/mi.py +1 -1
  9. xiaogpt-2.60/xiaogpt/tts/tetos.py +56 -0
  10. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/xiaogpt.py +34 -19
  11. xiaogpt-2.42/xiaogpt/tts/__init__.py +0 -6
  12. xiaogpt-2.42/xiaogpt/tts/azure.py +0 -98
  13. xiaogpt-2.42/xiaogpt/tts/edge.py +0 -32
  14. xiaogpt-2.42/xiaogpt/tts/openai.py +0 -46
  15. {xiaogpt-2.42 → xiaogpt-2.60}/LICENSE +0 -0
  16. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/__init__.py +0 -0
  17. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/__main__.py +0 -0
  18. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/__init__.py +0 -0
  19. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/base_bot.py +0 -0
  20. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/chatgptapi_bot.py +0 -0
  21. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/gemini_bot.py +0 -0
  22. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/glm_bot.py +0 -0
  23. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/langchain_bot.py +0 -0
  24. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/newbing_bot.py +0 -0
  25. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/bot/qwen_bot.py +0 -0
  26. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/langchain/callbacks.py +0 -0
  27. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/langchain/chain.py +0 -0
  28. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/langchain/examples/email/mail_box.py +0 -0
  29. {xiaogpt-2.42 → xiaogpt-2.60}/xiaogpt/langchain/examples/email/mail_summary_tools.py +0 -0
  30. {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.42
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: azure-cognitiveservices-speech>=1.37.0
27
- Requires-Dist: aiohttp==3.9.4; extra == "locked"
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.7.1; extra == "locked"
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.10.0; extra == "locked"
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.1; extra == "locked"
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-generativeai==0.5.0; extra == "locked"
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.4; extra == "locked"
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.17.1; extra == "locked"
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
- ### Windows(使用 set 设置环境变量)
138
- ```cmd
139
- pip install miservice_fork
140
- set MI_USER=xxxx
141
- set MI_PASS=xxx
142
- micli list 得到did
143
- set MI_DID=xxxx
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 | openai的apikey | | |
287
- | serpapi_api_key | serpapi的key 参考 [SerpAPI](https://serpapi.com/) | | |
288
- | glm_key | chatglm 的 apikey | | |
289
- | gemini_key | gemini 的 apikey [参考](https://makersuite.google.com/app/apikey) | | |
290
- | qwen_key | qwen 的 apikey [参考](https://help.aliyun.com/zh/dashscope/developer-reference/api-details) | | |
291
- | cookie | 小爱账户cookie (如果用上面密码登录可以不填) | | |
292
- | mi_did | 设备did | | |
293
- | use_command | 使用 MI command 与小爱交互 | `false` | |
294
- | mute_xiaoai | 快速停掉小爱自己的回答 | `true` | |
295
- | verbose | 是否打印详细日志 | `false` | |
296
- | bot | 使用的 bot 类型,目前支持 chatgptapi,newbing, qwen, gemini | `chatgptapi` | |
297
- | tts | 使用的 TTS 类型 | `mi` | `edge`、 `openai`、`azure` |
298
- | tts_voice | TTS 的嗓音 | `zh-CN-XiaoxiaoNeural`(edge), `alloy`(openai), `zh-CN-XiaoxiaoMultilingualNeural`(azure) | |
299
- | prompt | 自定义prompt | `请用100字以内回答` | |
300
- | keyword | 自定义请求词列表 | `["请"]` | |
301
- | change_prompt_keyword | 更改提示词触发列表 | `["更改提示词"]` | |
302
- | start_conversation | 开始持续对话关键词 | `开始持续对话` | |
303
- | end_conversation | 结束持续对话关键词 | `结束持续对话` | |
304
- | stream | 使用流式响应,获得更快的响应 | `false` | |
305
- | proxy | 支持 HTTP 代理,传入 http proxy URL | "" | |
306
- | gpt_options | OpenAI API 的参数字典 | `{}` | |
307
- | bing_cookie_path | NewBing使用的cookie路径,参考[这里]获取 | 也可通过环境变量 `COOKIE_FILE` 设置 | |
308
- | bing_cookies | NewBing使用的cookie字典,参考[这里]获取 | | |
309
- | deployment_id | Azure OpenAI 服务的 deployment ID | 参考这个[如何找到deployment_id](https://github.com/yihong0618/xiaogpt/issues/347#issuecomment-1784410784) | |
310
- | api_base | 如果需要替换默认的api,或者使用Azure OpenAI 服务 | 例如:`https://abc-def.openai.azure.com/` | |
311
- | azure_tts_speech_key | Azure TTS key | null | |
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
- ### Windows(使用 set 设置环境变量)
23
- ```cmd
24
- pip install miservice_fork
25
- set MI_USER=xxxx
26
- set MI_PASS=xxx
27
- micli list 得到did
28
- set MI_DID=xxxx
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 | openai的apikey | | |
172
- | serpapi_api_key | serpapi的key 参考 [SerpAPI](https://serpapi.com/) | | |
173
- | glm_key | chatglm 的 apikey | | |
174
- | gemini_key | gemini 的 apikey [参考](https://makersuite.google.com/app/apikey) | | |
175
- | qwen_key | qwen 的 apikey [参考](https://help.aliyun.com/zh/dashscope/developer-reference/api-details) | | |
176
- | cookie | 小爱账户cookie (如果用上面密码登录可以不填) | | |
177
- | mi_did | 设备did | | |
178
- | use_command | 使用 MI command 与小爱交互 | `false` | |
179
- | mute_xiaoai | 快速停掉小爱自己的回答 | `true` | |
180
- | verbose | 是否打印详细日志 | `false` | |
181
- | bot | 使用的 bot 类型,目前支持 chatgptapi,newbing, qwen, gemini | `chatgptapi` | |
182
- | tts | 使用的 TTS 类型 | `mi` | `edge`、 `openai`、`azure` |
183
- | tts_voice | TTS 的嗓音 | `zh-CN-XiaoxiaoNeural`(edge), `alloy`(openai), `zh-CN-XiaoxiaoMultilingualNeural`(azure) | |
184
- | prompt | 自定义prompt | `请用100字以内回答` | |
185
- | keyword | 自定义请求词列表 | `["请"]` | |
186
- | change_prompt_keyword | 更改提示词触发列表 | `["更改提示词"]` | |
187
- | start_conversation | 开始持续对话关键词 | `开始持续对话` | |
188
- | end_conversation | 结束持续对话关键词 | `结束持续对话` | |
189
- | stream | 使用流式响应,获得更快的响应 | `false` | |
190
- | proxy | 支持 HTTP 代理,传入 http proxy URL | "" | |
191
- | gpt_options | OpenAI API 的参数字典 | `{}` | |
192
- | bing_cookie_path | NewBing使用的cookie路径,参考[这里]获取 | 也可通过环境变量 `COOKIE_FILE` 设置 | |
193
- | bing_cookies | NewBing使用的cookie字典,参考[这里]获取 | | |
194
- | deployment_id | Azure OpenAI 服务的 deployment ID | 参考这个[如何找到deployment_id](https://github.com/yihong0618/xiaogpt/issues/347#issuecomment-1784410784) | |
195
- | api_base | 如果需要替换默认的api,或者使用Azure OpenAI 服务 | 例如:`https://abc-def.openai.azure.com/` | |
196
- | azure_tts_speech_key | Azure TTS key | null | |
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
- "azure-cognitiveservices-speech>=1.37.0",
28
+ "tetos>=0.1.0",
30
29
  ]
31
30
  dynamic = []
32
- version = "2.42"
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.4",
44
+ "aiohttp==3.9.5",
46
45
  "aiosignal==1.3.1",
47
46
  "annotated-types==0.6.0",
48
- "anyio==3.7.1",
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.10.0",
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.1",
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-generativeai==0.5.0",
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.4",
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.17.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="tts type",
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
- miboy = MiGPT(config)
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(miboy.run_forever())
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
- tts_voice: str | None = None
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:
@@ -0,0 +1,5 @@
1
+ from xiaogpt.tts.base import TTS
2
+ from xiaogpt.tts.mi import MiTTS
3
+ from xiaogpt.tts.tetos import TetosTTS
4
+
5
+ __all__ = ["TTS", "TetosTTS", "MiTTS"]
@@ -56,7 +56,7 @@ class TTS(abc.ABC):
56
56
  return is_playing
57
57
 
58
58
  @abc.abstractmethod
59
- async def synthesize(self, query: str, text_stream: AsyncIterator[str]) -> None:
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, query: str, text: str) -> tuple[Path, float]:
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, query: str, text_stream: AsyncIterator[str]) -> None:
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(query, text)
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, query: str, text_stream: AsyncIterator[str]) -> None:
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, EdgeTTS, MiTTS, AzureTTS
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("Polling_event, timestamp: %s", self.last_timestamp)
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, session):
80
- await self.login_miboy(session)
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, session):
90
+ async def login_miboy(self):
87
91
  account = MiAccount(
88
- session,
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(self.mi_session)
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 == "edge":
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(self.mi_session)
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.tts.synthesize(query, self.ask_gpt(query))
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,6 +0,0 @@
1
- from xiaogpt.tts.base import TTS as TTS
2
- from xiaogpt.tts.edge import EdgeTTS as EdgeTTS
3
- from xiaogpt.tts.mi import MiTTS as MiTTS
4
- from xiaogpt.tts.azure import AzureTTS
5
-
6
- __all__ = ["TTS", "EdgeTTS", "MiTTS", "AzureTTS"]
@@ -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
@@ -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