livekit-plugins-flashtts 1.1.0__py3-none-any.whl

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.
@@ -0,0 +1,25 @@
1
+ from .tts import TTS
2
+ from .version import __version__
3
+
4
+ __all__ = ["TTS", "__version__"]
5
+
6
+ from livekit.agents import Plugin
7
+
8
+ from .log import logger
9
+
10
+
11
+ class FlashTTSPlugin(Plugin):
12
+ def __init__(self):
13
+ super().__init__(__name__, __version__, __package__, logger)
14
+
15
+
16
+ Plugin.register_plugin(FlashTTSPlugin())
17
+
18
+ # Cleanup docs of unexported modules
19
+ _module = dir()
20
+ NOT_IN_ALL = [m for m in _module if m not in __all__]
21
+
22
+ __pdoc__ = {}
23
+
24
+ for n in NOT_IN_ALL:
25
+ __pdoc__[n] = False
@@ -0,0 +1,3 @@
1
+ from logging import getLogger
2
+
3
+ logger = getLogger("livekit.plugins.flashtts")
File without changes
@@ -0,0 +1,246 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from typing import Dict, Literal, Optional
5
+ import os
6
+
7
+ import aiohttp
8
+ from pydantic import BaseModel, Field
9
+ from osc_data.text_stream import TextStreamSentencizer
10
+
11
+ from livekit.agents import (
12
+ APIConnectOptions,
13
+ tts,
14
+ utils,
15
+ )
16
+ from livekit.agents.types import DEFAULT_API_CONNECT_OPTIONS
17
+
18
+ from .log import logger
19
+
20
+
21
+ class TTSOptions(BaseModel):
22
+ base_url: str | None = None
23
+ api_key: str | None = None
24
+ sample_rate: int = 16000
25
+ name: Optional[str] = Field(
26
+ default=None,
27
+ description="The name of the voice character to be used for speech synthesis.",
28
+ )
29
+ pitch: Optional[Literal["very_low", "low", "moderate", "high", "very_high"]] = (
30
+ Field(
31
+ default=None,
32
+ description="Specifies the pitch level for the generated audio. Valid options: 'very_low', 'low', 'moderate', 'high', 'very_high'.",
33
+ )
34
+ )
35
+ speed: Optional[Literal["very_low", "low", "moderate", "high", "very_high"]] = (
36
+ Field(
37
+ default=None,
38
+ description="Specifies the speed level of the audio output. Valid options: 'very_low', 'low', 'moderate', 'high', 'very_high'.",
39
+ )
40
+ )
41
+ temperature: float = Field(
42
+ default=0.9,
43
+ description="Controls the randomness of the speech synthesis. A higher temperature produces more diverse outputs.",
44
+ )
45
+ top_k: int = Field(
46
+ default=50,
47
+ description="Limits the sampling to the top 'k' most probable tokens during generation.",
48
+ )
49
+ top_p: float = Field(
50
+ default=0.95,
51
+ description="Nucleus sampling threshold: only tokens with a cumulative probability up to 'top_p' are considered.",
52
+ )
53
+ repetition_penalty: float = Field(
54
+ default=1.0,
55
+ description="Controls the repetition penalty applied to the generated text. "
56
+ "Higher values penalize repeated words and phrases.",
57
+ )
58
+ max_tokens: int = Field(
59
+ default=32768,
60
+ description="Specifies the maximum number of tokens to generate in the output.",
61
+ )
62
+ length_threshold: int = Field(
63
+ default=1000000,
64
+ description="If the input text exceeds this token length threshold, it will be split into multiple segments for synthesis.",
65
+ )
66
+ window_size: int = Field(
67
+ default=100000,
68
+ description="Determines the window size for each text segment when performing segmentation on longer texts.",
69
+ )
70
+ stream: bool = Field(
71
+ default=True,
72
+ description="Indicates whether the audio output should be streamed in real-time (True) or returned only after complete synthesis (False).",
73
+ )
74
+ response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] = Field(
75
+ default="pcm",
76
+ description=(
77
+ "The format in which to return audio. Supported formats: mp3, opus, aac, flac, wav, pcm. "
78
+ "Note: PCM returns raw 16-bit samples without headers and AAC is not currently supported."
79
+ ),
80
+ )
81
+
82
+ def get_http_url(self) -> str:
83
+ return f"{self.base_url}/speak"
84
+
85
+ def get_http_headers(self) -> Dict:
86
+ return {
87
+ "Content-Type": "application/json",
88
+ "Authorization": f"Bearer {self.api_key}",
89
+ }
90
+
91
+ def get_query_params(self, text: str) -> Dict:
92
+ if self.api_key is None:
93
+ self.api_key = os.environ.get("FLASHTTS_API_KEY", None)
94
+ if self.base_url is None:
95
+ self.base_url = os.environ.get("FLASHTTS_BASE_URL", "http://localhost:8000")
96
+ params = self.model_dump()
97
+ params["text"] = text
98
+ return params
99
+
100
+
101
+ class TTS(tts.TTS):
102
+ def __init__(
103
+ self,
104
+ base_url: str | None = None,
105
+ api_key: str | None = None,
106
+ sample_rate: int = 16000,
107
+ voice: Optional[str] = "female",
108
+ pitch: Optional[
109
+ Literal["very_low", "low", "moderate", "high", "very_high"]
110
+ ] = None,
111
+ speed: Optional[
112
+ Literal["very_low", "low", "moderate", "high", "very_high"]
113
+ ] = None,
114
+ temperature: float = 0.9,
115
+ top_k: int = 50,
116
+ top_p: float = 0.95,
117
+ repetition_penalty: float = 1.0,
118
+ max_tokens: int = 32768,
119
+ http_session: aiohttp.ClientSession | None = None,
120
+ max_session_duration: float = 600,
121
+ ):
122
+ """flashtts
123
+
124
+ Args:
125
+ base_url (str | None, optional): Base URL. Defaults to None.
126
+ api_key (str | None, optional): API key. Defaults to None.
127
+ sample_rate (int, optional): Sample rate. Defaults to 16000.
128
+ voice (Optional[str], optional): voice name. Defaults to "female".
129
+ pitch (Optional[Literal[ "very_low", "low", "moderate", "high", "very_high" ]], optional): Pitch. Defaults to None.
130
+ speed (Optional[Literal[ "very_low", "low", "moderate", "high", "very_high" ]], optional): Speed. Defaults to None.
131
+ temperature (float, optional): Temperature. Defaults to 0.9.
132
+ top_k (int, optional): Top k. Defaults to 50.
133
+ top_p (float, optional): Top p. Defaults to 0.95.
134
+ repetition_penalty (float, optional): Repetition penalty. Defaults to 1.0.
135
+ max_tokens (int, optional): Max tokens. Defaults to 4096.
136
+ stream (bool, optional): Stream. Defaults to False.
137
+ http_session (aiohttp.ClientSession | None, optional): HTTP session. Defaults to None.
138
+ max_session_duration (float, optional): Max session duration. Defaults to 600.
139
+ """
140
+ super().__init__(
141
+ capabilities=tts.TTSCapabilities(streaming=True),
142
+ sample_rate=sample_rate,
143
+ num_channels=1,
144
+ )
145
+ self._opts = TTSOptions(
146
+ base_url=base_url,
147
+ api_key=api_key,
148
+ sample_rate=sample_rate,
149
+ name=voice,
150
+ pitch=pitch,
151
+ speed=speed,
152
+ temperature=temperature,
153
+ top_k=top_k,
154
+ top_p=top_p,
155
+ repetition_penalty=repetition_penalty,
156
+ max_tokens=max_tokens,
157
+ )
158
+ self._session = http_session
159
+
160
+ def _ensure_session(self) -> aiohttp.ClientSession:
161
+ if self._session is None:
162
+ self._session = utils.http_context.http_session()
163
+
164
+ return self._session
165
+
166
+ def synthesize(
167
+ self, text, *, conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS
168
+ ):
169
+ raise NotImplementedError("Minimax TTS does not support synthesize method")
170
+
171
+ def stream(self, *, conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS):
172
+ return SynthesizeStream(
173
+ tts=self,
174
+ conn_options=conn_options,
175
+ opts=self._opts,
176
+ session=self._ensure_session(),
177
+ )
178
+
179
+
180
+ class SynthesizeStream(tts.SynthesizeStream):
181
+ def __init__(
182
+ self,
183
+ *,
184
+ tts: TTS,
185
+ opts: TTSOptions,
186
+ session: aiohttp.ClientSession,
187
+ conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS,
188
+ ):
189
+ super().__init__(tts=tts, conn_options=conn_options)
190
+ self._opts, self._session = opts, session
191
+
192
+ async def _run(self) -> None:
193
+ request_id = utils.shortuuid()
194
+ audio_bstream = utils.audio.AudioByteStream(
195
+ sample_rate=self._opts.sample_rate,
196
+ num_channels=1,
197
+ )
198
+ emitter = tts.SynthesizedAudioEmitter(
199
+ event_ch=self._event_ch,
200
+ request_id=request_id,
201
+ )
202
+ splitter = TextStreamSentencizer()
203
+ first_sentence_spend = None
204
+ start_time = time.perf_counter()
205
+ async for token in self._input_ch:
206
+ if isinstance(token, self._FlushSentinel):
207
+ sentences = splitter.flush()
208
+
209
+ else:
210
+ sentences = splitter.push(text=token)
211
+ for sentence in sentences:
212
+ if first_sentence_spend is None:
213
+ first_sentence_spend = time.perf_counter() - start_time
214
+ logger.info(
215
+ "llm first sentence",
216
+ extra={"spent": str(first_sentence_spend)},
217
+ )
218
+ if len(sentence.strip()) > 0:
219
+ first_response_spend = None
220
+ logger.info("tts start", extra={"sentence": sentence})
221
+ data = self._opts.get_query_params(text=sentence)
222
+ if first_response_spend is None:
223
+ start_time = time.perf_counter()
224
+ async with self._session.post(
225
+ self._opts.get_http_url(),
226
+ json=data,
227
+ timeout=aiohttp.ClientTimeout(
228
+ total=30,
229
+ sock_connect=self._conn_options.timeout,
230
+ ),
231
+ headers=self._opts.get_http_headers(),
232
+ ) as resp:
233
+ resp.raise_for_status()
234
+ async for data in resp.content:
235
+ if first_response_spend is None:
236
+ first_response_spend = time.perf_counter() - start_time
237
+ logger.info(
238
+ "tts first response",
239
+ extra={"spent": str(first_response_spend)},
240
+ )
241
+ for frame in audio_bstream.write(data=data):
242
+ emitter.push(frame)
243
+ for frame in audio_bstream.flush():
244
+ emitter.push(frame)
245
+ emitter.flush()
246
+ logger.info("tts end")
@@ -0,0 +1 @@
1
+ __version__ = "1.1.0"
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: livekit-plugins-flashtts
3
+ Version: 1.1.0
4
+ Summary: LiveKit Agent Plugins for FlashTTS
5
+ Author-email: wangmengdi <790990241@qq.com>
6
+ Keywords: audio,livekit,realtime,video,webrtc
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: License :: OSI Approved :: Apache Software License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Topic :: Multimedia :: Sound/Audio
14
+ Classifier: Topic :: Multimedia :: Video
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Requires-Python: >=3.9
17
+ Requires-Dist: livekit-agents~=1.0.23
18
+ Requires-Dist: osc-data>=0.1.7.post0
19
+ Requires-Dist: pydantic
20
+ Description-Content-Type: text/markdown
21
+
22
+ # 简介
23
+
24
+ [FlashTTS](https://github.com/HuiResearch/FlashTTS)是一个开源的TTS推理框架,你可以使用它部署Spark-TTS,MegaTTS等开源模型。
25
+
26
+ ## 安装
27
+ ```python
28
+ pip install livekit-plugins-flashtts
29
+ ```
30
+
31
+ ## 环境变量
32
+
33
+ - `FLASHTTS_API_URL`,FlashTTS的API地址,默认值为`http://localhost:8000`。
34
+ - `FLASHTTS_API_KEY`,FlashTTS的API密钥,默认值为空。
35
+
36
+ ## 使用
37
+
38
+
39
+ 以下是一个使用FlashTTS插件的示例:
40
+
41
+ ```python
42
+ from livekit.agents import Agent, AgentSession, JobContext, cli, WorkerOptions
43
+ from livekit.plugins import flashtts
44
+ from dotenv import load_dotenv
45
+
46
+
47
+ async def entry_point(ctx: JobContext):
48
+
49
+ await ctx.connect()
50
+
51
+ agent = Agent(instructions="You are a helpful assistant.")
52
+
53
+ session = AgentSession(
54
+ tts=flashtts.TTS(voice="female"),
55
+ )
56
+
57
+ await session.start(agent=agent, room=ctx.room)
58
+
59
+
60
+ if __name__ == "__main__":
61
+ load_dotenv()
62
+ cli.run_app(WorkerOptions(entrypoint_fnc=entry_point))
63
+ ```
64
+
@@ -0,0 +1,8 @@
1
+ livekit/plugins/flashtts/__init__.py,sha256=E6bq3pwf5Gon4jhZy1xw2OhYe9WOBzA-ZDdjwtaUfkk,485
2
+ livekit/plugins/flashtts/log.py,sha256=-C-i1PZKWxlwxL5voj6sPz9qLVuWOh0V8CKEdKLGTZc,78
3
+ livekit/plugins/flashtts/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ livekit/plugins/flashtts/tts.py,sha256=LocuED2GlK55TV9JNI1hhVlvEerwIcvl_gBfgCRgzyA,9686
5
+ livekit/plugins/flashtts/version.py,sha256=LGVQyDsWifdACo7qztwb8RWWHds1E7uQ-ZqD8SAjyw4,22
6
+ livekit_plugins_flashtts-1.1.0.dist-info/METADATA,sha256=iTmVORK7NZ8G2hWxSf6R-c9BOGV7601VIiFhg7ZMZrc,1828
7
+ livekit_plugins_flashtts-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ livekit_plugins_flashtts-1.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any