livekit-plugins-flashtts 1.1.0__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.
@@ -0,0 +1,183 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # Ruff stuff:
171
+ .ruff_cache/
172
+
173
+ # PyPI configuration file
174
+ .pypirc
175
+
176
+ # tests
177
+ test.py
178
+
179
+ .python-version
180
+
181
+ *.ipynb
182
+ config.yaml
183
+ uv.lock
@@ -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,43 @@
1
+ # 简介
2
+
3
+ [FlashTTS](https://github.com/HuiResearch/FlashTTS)是一个开源的TTS推理框架,你可以使用它部署Spark-TTS,MegaTTS等开源模型。
4
+
5
+ ## 安装
6
+ ```python
7
+ pip install livekit-plugins-flashtts
8
+ ```
9
+
10
+ ## 环境变量
11
+
12
+ - `FLASHTTS_API_URL`,FlashTTS的API地址,默认值为`http://localhost:8000`。
13
+ - `FLASHTTS_API_KEY`,FlashTTS的API密钥,默认值为空。
14
+
15
+ ## 使用
16
+
17
+
18
+ 以下是一个使用FlashTTS插件的示例:
19
+
20
+ ```python
21
+ from livekit.agents import Agent, AgentSession, JobContext, cli, WorkerOptions
22
+ from livekit.plugins import flashtts
23
+ from dotenv import load_dotenv
24
+
25
+
26
+ async def entry_point(ctx: JobContext):
27
+
28
+ await ctx.connect()
29
+
30
+ agent = Agent(instructions="You are a helpful assistant.")
31
+
32
+ session = AgentSession(
33
+ tts=flashtts.TTS(voice="female"),
34
+ )
35
+
36
+ await session.start(agent=agent, room=ctx.room)
37
+
38
+
39
+ if __name__ == "__main__":
40
+ load_dotenv()
41
+ cli.run_app(WorkerOptions(entrypoint_fnc=entry_point))
42
+ ```
43
+
@@ -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")
@@ -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,42 @@
1
+ [project]
2
+ name = "livekit-plugins-flashtts"
3
+ dynamic = ["version"]
4
+ description = "LiveKit Agent Plugins for FlashTTS"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "wangmengdi", email = "790990241@qq.com" }
8
+ ]
9
+ keywords = ["webrtc", "realtime", "audio", "video", "livekit"]
10
+ requires-python = ">=3.9"
11
+ dependencies = [
12
+ "livekit-agents~=1.0.23",
13
+ "osc-data>=0.1.7.post0",
14
+ "pydantic",
15
+ ]
16
+
17
+ classifiers = [
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: Apache Software License",
20
+ "Topic :: Multimedia :: Sound/Audio",
21
+ "Topic :: Multimedia :: Video",
22
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3 :: Only",
27
+ ]
28
+
29
+
30
+
31
+ [build-system]
32
+ requires = ["hatchling"]
33
+ build-backend = "hatchling.build"
34
+
35
+ [tool.hatch.version]
36
+ path = "livekit/plugins/flashtts/version.py"
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["livekit"]
40
+
41
+ [tool.hatch.build.targets.sdist]
42
+ include = ["/livekit"]