xiaogpt 3.2__tar.gz → 3.4__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 (32) hide show
  1. {xiaogpt-3.2 → xiaogpt-3.4}/PKG-INFO +16 -13
  2. {xiaogpt-3.2 → xiaogpt-3.4}/pyproject.toml +16 -13
  3. xiaogpt-3.4/xiaogpt/tts/__init__.py +6 -0
  4. xiaogpt-3.4/xiaogpt/tts/base.py +51 -0
  5. xiaogpt-3.2/xiaogpt/tts/base.py → xiaogpt-3.4/xiaogpt/tts/file.py +20 -57
  6. xiaogpt-3.4/xiaogpt/tts/live.py +98 -0
  7. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/xiaogpt.py +6 -3
  8. xiaogpt-3.2/xiaogpt/tts/__init__.py +0 -5
  9. xiaogpt-3.2/xiaogpt/tts/tetos.py +0 -31
  10. {xiaogpt-3.2 → xiaogpt-3.4}/LICENSE +0 -0
  11. {xiaogpt-3.2 → xiaogpt-3.4}/README.md +0 -0
  12. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/__init__.py +0 -0
  13. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/__main__.py +0 -0
  14. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/bot/__init__.py +0 -0
  15. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/bot/base_bot.py +0 -0
  16. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/bot/chatgptapi_bot.py +0 -0
  17. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/bot/doubao_bot.py +0 -0
  18. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/bot/gemini_bot.py +0 -0
  19. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/bot/glm_bot.py +0 -0
  20. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/bot/langchain_bot.py +0 -0
  21. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/bot/llama_bot.py +0 -0
  22. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/bot/moonshot_bot.py +0 -0
  23. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/bot/qwen_bot.py +0 -0
  24. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/bot/yi_bot.py +0 -0
  25. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/cli.py +0 -0
  26. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/config.py +0 -0
  27. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/langchain/callbacks.py +0 -0
  28. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/langchain/chain.py +0 -0
  29. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/langchain/examples/email/mail_box.py +0 -0
  30. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/langchain/examples/email/mail_summary_tools.py +0 -0
  31. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/tts/mi.py +0 -0
  32. {xiaogpt-3.2 → xiaogpt-3.4}/xiaogpt/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xiaogpt
3
- Version: 3.2
3
+ Version: 3.4
4
4
  Summary: Play ChatGPT or other LLM with xiaomi AI speaker
5
5
  Author-Email: yihong0618 <zouzou0208@gmail.com>
6
6
  License: MIT
@@ -28,7 +28,7 @@ Requires-Dist: langchain-community>=0.0.38
28
28
  Requires-Dist: lingua-language-detector>=2.0.2; python_version < "3.13"
29
29
  Provides-Extra: locked
30
30
  Requires-Dist: aiohappyeyeballs==2.4.0; extra == "locked"
31
- Requires-Dist: aiohttp==3.10.5; extra == "locked"
31
+ Requires-Dist: aiohttp==3.10.10; extra == "locked"
32
32
  Requires-Dist: aiosignal==1.3.1; extra == "locked"
33
33
  Requires-Dist: annotated-types==0.6.0; extra == "locked"
34
34
  Requires-Dist: anyio==4.3.0; extra == "locked"
@@ -47,14 +47,14 @@ Requires-Dist: distro==1.9.0; extra == "locked"
47
47
  Requires-Dist: edge-tts==6.1.10; extra == "locked"
48
48
  Requires-Dist: exceptiongroup==1.2.0; python_version < "3.11" and extra == "locked"
49
49
  Requires-Dist: frozenlist==1.4.1; extra == "locked"
50
- Requires-Dist: google-ai-generativelanguage==0.6.9; extra == "locked"
50
+ Requires-Dist: google-ai-generativelanguage==0.6.10; extra == "locked"
51
51
  Requires-Dist: google-api-core==2.15.0; extra == "locked"
52
52
  Requires-Dist: google-api-core[grpc]==2.15.0; extra == "locked"
53
53
  Requires-Dist: google-api-python-client==2.125.0; extra == "locked"
54
54
  Requires-Dist: google-auth==2.26.1; extra == "locked"
55
55
  Requires-Dist: google-auth-httplib2==0.2.0; extra == "locked"
56
56
  Requires-Dist: google-cloud-texttospeech==2.16.3; extra == "locked"
57
- Requires-Dist: google-generativeai==0.8.1; extra == "locked"
57
+ Requires-Dist: google-generativeai==0.8.3; extra == "locked"
58
58
  Requires-Dist: google-search-results==2.4.2; extra == "locked"
59
59
  Requires-Dist: googleapis-common-protos==1.62.0; extra == "locked"
60
60
  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"
@@ -65,16 +65,17 @@ Requires-Dist: h11==0.14.0; extra == "locked"
65
65
  Requires-Dist: httpcore==1.0.5; extra == "locked"
66
66
  Requires-Dist: httplib2==0.22.0; extra == "locked"
67
67
  Requires-Dist: httpx==0.27.2; extra == "locked"
68
+ Requires-Dist: httpx-ws==0.6.2; extra == "locked"
68
69
  Requires-Dist: httpx[socks]==0.27.2; extra == "locked"
69
70
  Requires-Dist: idna==3.7; extra == "locked"
70
71
  Requires-Dist: jiter==0.5.0; extra == "locked"
71
72
  Requires-Dist: jsonpatch==1.33; extra == "locked"
72
73
  Requires-Dist: jsonpointer==2.4; extra == "locked"
73
- Requires-Dist: langchain==0.3.0; extra == "locked"
74
- Requires-Dist: langchain-community==0.3.0; extra == "locked"
75
- Requires-Dist: langchain-core==0.3.0; extra == "locked"
74
+ Requires-Dist: langchain==0.3.3; extra == "locked"
75
+ Requires-Dist: langchain-community==0.3.2; extra == "locked"
76
+ Requires-Dist: langchain-core==0.3.10; extra == "locked"
76
77
  Requires-Dist: langchain-text-splitters==0.3.0; extra == "locked"
77
- Requires-Dist: langsmith==0.1.120; extra == "locked"
78
+ Requires-Dist: langsmith==0.1.133; extra == "locked"
78
79
  Requires-Dist: lingua-language-detector==2.0.2; python_version < "3.13" and extra == "locked"
79
80
  Requires-Dist: markdown-it-py==3.0.0; extra == "locked"
80
81
  Requires-Dist: marshmallow==3.20.1; extra == "locked"
@@ -85,10 +86,11 @@ Requires-Dist: mutagen==1.47.0; extra == "locked"
85
86
  Requires-Dist: mypy-extensions==1.0.0; extra == "locked"
86
87
  Requires-Dist: numexpr==2.10.1; extra == "locked"
87
88
  Requires-Dist: numpy==1.26.3; extra == "locked"
88
- Requires-Dist: openai==1.45.0; extra == "locked"
89
+ Requires-Dist: openai==1.51.2; extra == "locked"
89
90
  Requires-Dist: orjson==3.10.0; extra == "locked"
90
91
  Requires-Dist: ormsgpack==1.5.0; extra == "locked"
91
92
  Requires-Dist: packaging==23.2; extra == "locked"
93
+ Requires-Dist: propcache==0.2.0; extra == "locked"
92
94
  Requires-Dist: proto-plus==1.23.0; extra == "locked"
93
95
  Requires-Dist: protobuf==4.25.1; extra == "locked"
94
96
  Requires-Dist: pyasn1==0.5.1; extra == "locked"
@@ -102,22 +104,23 @@ Requires-Dist: pyparsing==3.1.2; python_version > "3.0" and extra == "locked"
102
104
  Requires-Dist: python-dotenv==1.0.1; extra == "locked"
103
105
  Requires-Dist: pyyaml==6.0.2; extra == "locked"
104
106
  Requires-Dist: requests==2.31.0; extra == "locked"
105
- Requires-Dist: rich==13.8.1; extra == "locked"
107
+ Requires-Dist: requests-toolbelt==1.0.0; extra == "locked"
108
+ Requires-Dist: rich==13.9.2; extra == "locked"
106
109
  Requires-Dist: rsa==4.9; extra == "locked"
107
110
  Requires-Dist: sniffio==1.3.0; extra == "locked"
108
111
  Requires-Dist: socksio==1.0.0; extra == "locked"
109
112
  Requires-Dist: soupsieve==2.5; extra == "locked"
110
113
  Requires-Dist: sqlalchemy==2.0.25; extra == "locked"
111
114
  Requires-Dist: tenacity==8.2.3; extra == "locked"
112
- Requires-Dist: tetos==0.3.1; extra == "locked"
115
+ Requires-Dist: tetos==0.4.1; extra == "locked"
113
116
  Requires-Dist: tqdm==4.66.1; extra == "locked"
114
117
  Requires-Dist: typing-extensions==4.12.2; extra == "locked"
115
118
  Requires-Dist: typing-inspect==0.9.0; extra == "locked"
116
119
  Requires-Dist: uritemplate==4.1.1; extra == "locked"
117
120
  Requires-Dist: urllib3==2.1.0; extra == "locked"
118
121
  Requires-Dist: websocket-client==1.8.0; extra == "locked"
119
- Requires-Dist: websockets==12.0; extra == "locked"
120
- Requires-Dist: yarl==1.9.4; extra == "locked"
122
+ Requires-Dist: wsproto==1.2.0; extra == "locked"
123
+ Requires-Dist: yarl==1.14.0; extra == "locked"
121
124
  Requires-Dist: zhipuai==2.1.5.20230904; extra == "locked"
122
125
  Description-Content-Type: text/markdown
123
126
 
@@ -31,7 +31,7 @@ dependencies = [
31
31
  "lingua-language-detector>=2.0.2; python_version < \"3.13\"",
32
32
  ]
33
33
  dynamic = []
34
- version = "3.2"
34
+ version = "3.4"
35
35
 
36
36
  [project.license]
37
37
  text = "MIT"
@@ -45,7 +45,7 @@ xiaogpt = "xiaogpt.cli:main"
45
45
  [project.optional-dependencies]
46
46
  locked = [
47
47
  "aiohappyeyeballs==2.4.0",
48
- "aiohttp==3.10.5",
48
+ "aiohttp==3.10.10",
49
49
  "aiosignal==1.3.1",
50
50
  "annotated-types==0.6.0",
51
51
  "anyio==4.3.0",
@@ -64,14 +64,14 @@ locked = [
64
64
  "edge-tts==6.1.10",
65
65
  "exceptiongroup==1.2.0 ; python_version < \"3.11\"",
66
66
  "frozenlist==1.4.1",
67
- "google-ai-generativelanguage==0.6.9",
67
+ "google-ai-generativelanguage==0.6.10",
68
68
  "google-api-core==2.15.0",
69
69
  "google-api-core[grpc]==2.15.0",
70
70
  "google-api-python-client==2.125.0",
71
71
  "google-auth==2.26.1",
72
72
  "google-auth-httplib2==0.2.0",
73
73
  "google-cloud-texttospeech==2.16.3",
74
- "google-generativeai==0.8.1",
74
+ "google-generativeai==0.8.3",
75
75
  "google-search-results==2.4.2",
76
76
  "googleapis-common-protos==1.62.0",
77
77
  "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\"",
@@ -82,16 +82,17 @@ locked = [
82
82
  "httpcore==1.0.5",
83
83
  "httplib2==0.22.0",
84
84
  "httpx==0.27.2",
85
+ "httpx-ws==0.6.2",
85
86
  "httpx[socks]==0.27.2",
86
87
  "idna==3.7",
87
88
  "jiter==0.5.0",
88
89
  "jsonpatch==1.33",
89
90
  "jsonpointer==2.4",
90
- "langchain==0.3.0",
91
- "langchain-community==0.3.0",
92
- "langchain-core==0.3.0",
91
+ "langchain==0.3.3",
92
+ "langchain-community==0.3.2",
93
+ "langchain-core==0.3.10",
93
94
  "langchain-text-splitters==0.3.0",
94
- "langsmith==0.1.120",
95
+ "langsmith==0.1.133",
95
96
  "lingua-language-detector==2.0.2 ; python_version < \"3.13\"",
96
97
  "markdown-it-py==3.0.0",
97
98
  "marshmallow==3.20.1",
@@ -102,10 +103,11 @@ locked = [
102
103
  "mypy-extensions==1.0.0",
103
104
  "numexpr==2.10.1",
104
105
  "numpy==1.26.3",
105
- "openai==1.45.0",
106
+ "openai==1.51.2",
106
107
  "orjson==3.10.0",
107
108
  "ormsgpack==1.5.0",
108
109
  "packaging==23.2",
110
+ "propcache==0.2.0",
109
111
  "proto-plus==1.23.0",
110
112
  "protobuf==4.25.1",
111
113
  "pyasn1==0.5.1",
@@ -119,22 +121,23 @@ locked = [
119
121
  "python-dotenv==1.0.1",
120
122
  "pyyaml==6.0.2",
121
123
  "requests==2.31.0",
122
- "rich==13.8.1",
124
+ "requests-toolbelt==1.0.0",
125
+ "rich==13.9.2",
123
126
  "rsa==4.9",
124
127
  "sniffio==1.3.0",
125
128
  "socksio==1.0.0",
126
129
  "soupsieve==2.5",
127
130
  "sqlalchemy==2.0.25",
128
131
  "tenacity==8.2.3",
129
- "tetos==0.3.1",
132
+ "tetos==0.4.1",
130
133
  "tqdm==4.66.1",
131
134
  "typing-extensions==4.12.2",
132
135
  "typing-inspect==0.9.0",
133
136
  "uritemplate==4.1.1",
134
137
  "urllib3==2.1.0",
135
138
  "websocket-client==1.8.0",
136
- "websockets==12.0",
137
- "yarl==1.9.4",
139
+ "wsproto==1.2.0",
140
+ "yarl==1.14.0",
138
141
  "zhipuai==2.1.5.20230904",
139
142
  ]
140
143
 
@@ -0,0 +1,6 @@
1
+ from xiaogpt.tts.base import TTS
2
+ from xiaogpt.tts.file import TetosFileTTS
3
+ from xiaogpt.tts.live import TetosLiveTTS
4
+ from xiaogpt.tts.mi import MiTTS
5
+
6
+ __all__ = ["TTS", "TetosFileTTS", "MiTTS", "TetosLiveTTS"]
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ import asyncio
5
+ import json
6
+ import logging
7
+ from typing import TYPE_CHECKING, AsyncIterator
8
+
9
+ if TYPE_CHECKING:
10
+ from typing import TypeVar
11
+
12
+ from miservice import MiNAService
13
+
14
+ from xiaogpt.config import Config
15
+
16
+ T = TypeVar("T", bound="TTS")
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class TTS(abc.ABC):
22
+ """An abstract base class for Text-to-Speech models."""
23
+
24
+ def __init__(
25
+ self, mina_service: MiNAService, device_id: str, config: Config
26
+ ) -> None:
27
+ self.mina_service = mina_service
28
+ self.device_id = device_id
29
+ self.config = config
30
+
31
+ async def wait_for_duration(self, duration: float) -> None:
32
+ """Wait for the specified duration."""
33
+ await asyncio.sleep(duration)
34
+ while True:
35
+ if not await self.get_if_xiaoai_is_playing():
36
+ break
37
+ await asyncio.sleep(1)
38
+
39
+ async def get_if_xiaoai_is_playing(self) -> bool:
40
+ playing_info = await self.mina_service.player_get_status(self.device_id)
41
+ # WTF xiaomi api
42
+ is_playing = (
43
+ json.loads(playing_info.get("data", {}).get("info", "{}")).get("status", -1)
44
+ == 1
45
+ )
46
+ return is_playing
47
+
48
+ @abc.abstractmethod
49
+ async def synthesize(self, lang: str, text_stream: AsyncIterator[str]) -> None:
50
+ """Synthesize speech from a stream of text."""
51
+ raise NotImplementedError
@@ -1,10 +1,5 @@
1
- from __future__ import annotations
2
-
3
- import abc
4
1
  import asyncio
5
2
  import functools
6
- import json
7
- import logging
8
3
  import os
9
4
  import random
10
5
  import socket
@@ -12,53 +7,13 @@ import tempfile
12
7
  import threading
13
8
  from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
14
9
  from pathlib import Path
15
- from typing import TYPE_CHECKING, AsyncIterator
16
-
17
- from xiaogpt.utils import get_hostname
18
-
19
- if TYPE_CHECKING:
20
- from typing import TypeVar
21
-
22
- from miservice import MiNAService
23
-
24
- from xiaogpt.config import Config
25
-
26
- T = TypeVar("T", bound="TTS")
27
-
28
- logger = logging.getLogger(__name__)
29
-
30
-
31
- class TTS(abc.ABC):
32
- """An abstract base class for Text-to-Speech models."""
33
-
34
- def __init__(
35
- self, mina_service: MiNAService, device_id: str, config: Config
36
- ) -> None:
37
- self.mina_service = mina_service
38
- self.device_id = device_id
39
- self.config = config
10
+ from typing import AsyncIterator
40
11
 
41
- async def wait_for_duration(self, duration: float) -> None:
42
- """Wait for the specified duration."""
43
- await asyncio.sleep(duration)
44
- while True:
45
- if not await self.get_if_xiaoai_is_playing():
46
- break
47
- await asyncio.sleep(1)
48
-
49
- async def get_if_xiaoai_is_playing(self):
50
- playing_info = await self.mina_service.player_get_status(self.device_id)
51
- # WTF xiaomi api
52
- is_playing = (
53
- json.loads(playing_info.get("data", {}).get("info", "{}")).get("status", -1)
54
- == 1
55
- )
56
- return is_playing
12
+ from miservice import MiNAService
57
13
 
58
- @abc.abstractmethod
59
- async def synthesize(self, lang: str, text_stream: AsyncIterator[str]) -> None:
60
- """Synthesize speech from a stream of text."""
61
- raise NotImplementedError
14
+ from xiaogpt.config import Config
15
+ from xiaogpt.tts.base import TTS, logger
16
+ from xiaogpt.utils import get_hostname
62
17
 
63
18
 
64
19
  class HTTPRequestHandler(SimpleHTTPRequestHandler):
@@ -76,23 +31,31 @@ class HTTPRequestHandler(SimpleHTTPRequestHandler):
76
31
  pass
77
32
 
78
33
 
79
- class AudioFileTTS(TTS):
34
+ class TetosFileTTS(TTS):
80
35
  """A TTS model that generates audio files locally and plays them via URL."""
81
36
 
82
37
  def __init__(
83
38
  self, mina_service: MiNAService, device_id: str, config: Config
84
39
  ) -> None:
40
+ from tetos import get_speaker
41
+
85
42
  super().__init__(mina_service, device_id, config)
86
43
  self.dirname = tempfile.TemporaryDirectory(prefix="xiaogpt-tts-")
87
44
  self._start_http_server()
88
45
 
89
- @abc.abstractmethod
46
+ assert config.tts and config.tts != "mi"
47
+ speaker_cls = get_speaker(config.tts)
48
+ try:
49
+ self.speaker = speaker_cls(**config.tts_options)
50
+ except TypeError as e:
51
+ raise ValueError(f"{e}. Please add them via `tts_options` config") from e
52
+
90
53
  async def make_audio_file(self, lang: str, text: str) -> tuple[Path, float]:
91
- """Synthesize speech from text and save it to a file.
92
- Return the file path and the duration of the audio in seconds.
93
- The file path must be relative to the self.dirname.
94
- """
95
- raise NotImplementedError
54
+ output_file = tempfile.NamedTemporaryFile(
55
+ suffix=".mp3", mode="wb", delete=False, dir=self.dirname.name
56
+ )
57
+ duration = await self.speaker.synthesize(text, output_file.name, lang=lang)
58
+ return Path(output_file.name), duration
96
59
 
97
60
  async def synthesize(self, lang: str, text_stream: AsyncIterator[str]) -> None:
98
61
  queue: asyncio.Queue[tuple[str, float]] = asyncio.Queue()
@@ -0,0 +1,98 @@
1
+ import asyncio
2
+ import os
3
+ import queue
4
+ import random
5
+ import threading
6
+ import uuid
7
+ from functools import lru_cache
8
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
9
+ from typing import AsyncIterator
10
+
11
+ from miservice import MiNAService
12
+
13
+ from xiaogpt.config import Config
14
+ from xiaogpt.tts.base import TTS, logger
15
+ from xiaogpt.utils import get_hostname
16
+
17
+
18
+ @lru_cache(maxsize=64)
19
+ def get_queue(key: str) -> queue.Queue[bytes]:
20
+ return queue.Queue()
21
+
22
+
23
+ class HTTPRequestHandler(BaseHTTPRequestHandler):
24
+ def do_GET(self):
25
+ self.send_response(200)
26
+ self.send_header("Content-type", "audio/mpeg")
27
+ self.end_headers()
28
+ key = self.path.split("/")[-1]
29
+ queue = get_queue(key)
30
+ while True:
31
+ chunk = queue.get()
32
+ if chunk == b"":
33
+ break
34
+ self.wfile.write(chunk)
35
+
36
+ def log_message(self, format, *args):
37
+ logger.debug(f"{self.address_string()} - {format}", *args)
38
+
39
+ def log_error(self, format, *args):
40
+ logger.error(f"{self.address_string()} - {format}", *args)
41
+
42
+
43
+ class TetosLiveTTS(TTS):
44
+ """A TTS model that generates audio in real-time."""
45
+
46
+ def __init__(
47
+ self, mina_service: MiNAService, device_id: str, config: Config
48
+ ) -> None:
49
+ from tetos import get_speaker
50
+
51
+ super().__init__(mina_service, device_id, config)
52
+ self._start_http_server()
53
+
54
+ assert config.tts and config.tts != "mi"
55
+ speaker_cls = get_speaker(config.tts)
56
+ try:
57
+ self.speaker = speaker_cls(**config.tts_options)
58
+ except TypeError as e:
59
+ raise ValueError(f"{e}. Please add them via `tts_options` config") from e
60
+ if not hasattr(self.speaker, "live"):
61
+ raise ValueError(f"{config.tts} Speaker does not support live synthesis")
62
+
63
+ async def synthesize(self, lang: str, text_stream: AsyncIterator[str]) -> None:
64
+ key = str(uuid.uuid4())
65
+ queue = get_queue(key)
66
+
67
+ async def worker():
68
+ async for chunk in self.speaker.live(text_stream, lang):
69
+ queue.put(chunk)
70
+ queue.put(b"")
71
+
72
+ task = asyncio.create_task(worker())
73
+ await self.mina_service.play_by_url(
74
+ self.device_id, f"http://{self.hostname}:{self.port}/{key}", _type=1
75
+ )
76
+
77
+ while True:
78
+ if await self.get_if_xiaoai_is_playing():
79
+ await asyncio.sleep(1)
80
+ else:
81
+ break
82
+ await task
83
+
84
+ def _start_http_server(self):
85
+ # set the port range
86
+ port_range = range(8050, 8090)
87
+ # get a random port from the range
88
+ self.port = int(os.getenv("XIAOGPT_PORT", random.choice(port_range)))
89
+ # create the server
90
+ handler = HTTPRequestHandler
91
+ httpd = ThreadingHTTPServer(("", self.port), handler)
92
+ # start the server in a new thread
93
+ server_thread = threading.Thread(target=httpd.serve_forever)
94
+ server_thread.daemon = True
95
+ server_thread.start()
96
+
97
+ self.hostname = get_hostname()
98
+ logger.info(f"Serving on {self.hostname}:{self.port}")
@@ -23,7 +23,8 @@ from xiaogpt.config import (
23
23
  WAKEUP_KEYWORD,
24
24
  Config,
25
25
  )
26
- from xiaogpt.tts import TTS, MiTTS, TetosTTS
26
+ from xiaogpt.tts import TTS, MiTTS, TetosFileTTS
27
+ from xiaogpt.tts.live import TetosLiveTTS
27
28
  from xiaogpt.utils import detect_language, parse_cookie_string
28
29
 
29
30
  EOF = object()
@@ -260,8 +261,10 @@ class MiGPT:
260
261
  def tts(self) -> TTS:
261
262
  if self.config.tts == "mi":
262
263
  return MiTTS(self.mina_service, self.device_id, self.config)
264
+ elif self.config.tts == "fish":
265
+ return TetosLiveTTS(self.mina_service, self.device_id, self.config)
263
266
  else:
264
- return TetosTTS(self.mina_service, self.device_id, self.config)
267
+ return TetosFileTTS(self.mina_service, self.device_id, self.config)
265
268
 
266
269
  async def wait_for_tts_finish(self):
267
270
  while True:
@@ -274,7 +277,7 @@ class MiGPT:
274
277
  message = message.strip().replace(" ", "--")
275
278
  message = message.replace("\n", ",")
276
279
  message = message.replace('"', ",")
277
- message = message.replace('*', "")
280
+ message = message.replace("*", "")
278
281
  return message
279
282
 
280
283
  async def ask_gpt(self, query: str) -> AsyncIterator[str]:
@@ -1,5 +0,0 @@
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"]
@@ -1,31 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import tempfile
4
- from pathlib import Path
5
-
6
- from miservice import MiNAService
7
-
8
- from xiaogpt.config import Config
9
- from xiaogpt.tts.base import AudioFileTTS
10
-
11
-
12
- class TetosTTS(AudioFileTTS):
13
- def __init__(
14
- self, mina_service: MiNAService, device_id: str, config: Config
15
- ) -> None:
16
- from tetos import get_speaker
17
-
18
- super().__init__(mina_service, device_id, config)
19
- assert config.tts and config.tts != "mi"
20
- speaker_cls = get_speaker(config.tts)
21
- try:
22
- self.speaker = speaker_cls(**config.tts_options)
23
- except TypeError as e:
24
- raise ValueError(f"{e}. Please add them via `tts_options` config") from e
25
-
26
- async def make_audio_file(self, lang: str, text: str) -> tuple[Path, float]:
27
- output_file = tempfile.NamedTemporaryFile(
28
- suffix=".mp3", mode="wb", delete=False, dir=self.dirname.name
29
- )
30
- duration = await self.speaker.synthesize(text, output_file.name, lang=lang)
31
- return Path(output_file.name), duration
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
File without changes
File without changes