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.
- livekit_plugins_flashtts-1.1.0/.gitignore +183 -0
- livekit_plugins_flashtts-1.1.0/PKG-INFO +64 -0
- livekit_plugins_flashtts-1.1.0/README.md +43 -0
- livekit_plugins_flashtts-1.1.0/livekit/plugins/flashtts/__init__.py +25 -0
- livekit_plugins_flashtts-1.1.0/livekit/plugins/flashtts/log.py +3 -0
- livekit_plugins_flashtts-1.1.0/livekit/plugins/flashtts/py.typed +0 -0
- livekit_plugins_flashtts-1.1.0/livekit/plugins/flashtts/tts.py +246 -0
- livekit_plugins_flashtts-1.1.0/livekit/plugins/flashtts/version.py +1 -0
- livekit_plugins_flashtts-1.1.0/pyproject.toml +42 -0
|
@@ -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
|
|
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,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"]
|