pipecat-lokutor 0.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.
- pipecat_lokutor-0.1.0/.gitignore +8 -0
- pipecat_lokutor-0.1.0/LICENSE +25 -0
- pipecat_lokutor-0.1.0/PKG-INFO +97 -0
- pipecat_lokutor-0.1.0/README.md +83 -0
- pipecat_lokutor-0.1.0/pipecat_lokutor/__init__.py +6 -0
- pipecat_lokutor-0.1.0/pipecat_lokutor/tts.py +270 -0
- pipecat_lokutor-0.1.0/pyproject.toml +28 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026, Lokutor AI
|
|
4
|
+
Copyright (c) 2024-2026, Daily
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
22
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
23
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
24
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pipecat-lokutor
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lokutor TTS integration for Pipecat
|
|
5
|
+
Project-URL: Homepage, https://github.com/lokutor-ai/pipecat-lokutor
|
|
6
|
+
Project-URL: Source, https://github.com/lokutor-ai/pipecat-lokutor
|
|
7
|
+
Author-email: Lokutor AI <your-email@lokutor.com>
|
|
8
|
+
License: BSD-2-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: pipecat>=0.0.86
|
|
12
|
+
Requires-Dist: websockets>=12.0
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Pipecat Lokutor TTS
|
|
16
|
+
|
|
17
|
+
Lokutor text-to-speech integration for [Pipecat](https://github.com/pipecat-ai/pipecat).
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install pipecat-lokutor
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or with uv:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
uv add pipecat-lokutor
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from pipecat_lokutor import LokutorTTSService
|
|
35
|
+
|
|
36
|
+
tts = LokutorTTSService(
|
|
37
|
+
api_key="your_api_key",
|
|
38
|
+
voice_id="F1",
|
|
39
|
+
params=LokutorTTSService.InputParams(
|
|
40
|
+
language="en",
|
|
41
|
+
speed=1.0,
|
|
42
|
+
steps=5,
|
|
43
|
+
visemes=False,
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Pipecat Pipeline
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from pipecat.pipeline.pipeline import Pipeline
|
|
52
|
+
|
|
53
|
+
pipeline = Pipeline([
|
|
54
|
+
transport.input(),
|
|
55
|
+
stt,
|
|
56
|
+
llm,
|
|
57
|
+
tts,
|
|
58
|
+
transport.output(),
|
|
59
|
+
])
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Voices
|
|
63
|
+
|
|
64
|
+
| Voice | Description |
|
|
65
|
+
|-------|-------------|
|
|
66
|
+
| M1-M5 | Male voices |
|
|
67
|
+
| F1-F5 | Female voices |
|
|
68
|
+
|
|
69
|
+
## Supported Languages
|
|
70
|
+
|
|
71
|
+
EN, ES, FR, PT, KO
|
|
72
|
+
|
|
73
|
+
## Example
|
|
74
|
+
|
|
75
|
+
See `examples/groq-stt-groq-llm-lokutor-tts.py` for a full example using Groq STT + Groq LLM + Lokutor TTS.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Install dependencies
|
|
79
|
+
uv sync
|
|
80
|
+
|
|
81
|
+
# Set environment variables
|
|
82
|
+
export GROQ_API_KEY="your_groq_api_key"
|
|
83
|
+
export LOKUTOR_API_KEY="your_lokutor_api_key"
|
|
84
|
+
|
|
85
|
+
# Run the example
|
|
86
|
+
python examples/groq-stt-groq-llm-lokutor-tts.py -t webrtc
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Then open `http://localhost:7860/client/`.
|
|
90
|
+
|
|
91
|
+
## Compatibility
|
|
92
|
+
|
|
93
|
+
Tested with Pipecat v0.0.86+.
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
BSD 2-Clause License. See `LICENSE` for details.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Pipecat Lokutor TTS
|
|
2
|
+
|
|
3
|
+
Lokutor text-to-speech integration for [Pipecat](https://github.com/pipecat-ai/pipecat).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install pipecat-lokutor
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with uv:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv add pipecat-lokutor
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from pipecat_lokutor import LokutorTTSService
|
|
21
|
+
|
|
22
|
+
tts = LokutorTTSService(
|
|
23
|
+
api_key="your_api_key",
|
|
24
|
+
voice_id="F1",
|
|
25
|
+
params=LokutorTTSService.InputParams(
|
|
26
|
+
language="en",
|
|
27
|
+
speed=1.0,
|
|
28
|
+
steps=5,
|
|
29
|
+
visemes=False,
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Pipecat Pipeline
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from pipecat.pipeline.pipeline import Pipeline
|
|
38
|
+
|
|
39
|
+
pipeline = Pipeline([
|
|
40
|
+
transport.input(),
|
|
41
|
+
stt,
|
|
42
|
+
llm,
|
|
43
|
+
tts,
|
|
44
|
+
transport.output(),
|
|
45
|
+
])
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Voices
|
|
49
|
+
|
|
50
|
+
| Voice | Description |
|
|
51
|
+
|-------|-------------|
|
|
52
|
+
| M1-M5 | Male voices |
|
|
53
|
+
| F1-F5 | Female voices |
|
|
54
|
+
|
|
55
|
+
## Supported Languages
|
|
56
|
+
|
|
57
|
+
EN, ES, FR, PT, KO
|
|
58
|
+
|
|
59
|
+
## Example
|
|
60
|
+
|
|
61
|
+
See `examples/groq-stt-groq-llm-lokutor-tts.py` for a full example using Groq STT + Groq LLM + Lokutor TTS.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Install dependencies
|
|
65
|
+
uv sync
|
|
66
|
+
|
|
67
|
+
# Set environment variables
|
|
68
|
+
export GROQ_API_KEY="your_groq_api_key"
|
|
69
|
+
export LOKUTOR_API_KEY="your_lokutor_api_key"
|
|
70
|
+
|
|
71
|
+
# Run the example
|
|
72
|
+
python examples/groq-stt-groq-llm-lokutor-tts.py -t webrtc
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then open `http://localhost:7860/client/`.
|
|
76
|
+
|
|
77
|
+
## Compatibility
|
|
78
|
+
|
|
79
|
+
Tested with Pipecat v0.0.86+.
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
BSD 2-Clause License. See `LICENSE` for details.
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2024-2026, Daily
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: BSD 2-Clause License
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import AsyncGenerator, Optional
|
|
11
|
+
|
|
12
|
+
from loguru import logger
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
from websockets.asyncio.client import connect as websocket_connect
|
|
15
|
+
from websockets.protocol import State
|
|
16
|
+
|
|
17
|
+
from pipecat.frames.frames import (
|
|
18
|
+
ErrorFrame,
|
|
19
|
+
Frame,
|
|
20
|
+
TTSAudioRawFrame,
|
|
21
|
+
TTSStartedFrame,
|
|
22
|
+
TTSStoppedFrame,
|
|
23
|
+
)
|
|
24
|
+
from pipecat.services.settings import TTSSettings
|
|
25
|
+
from pipecat.services.tts_service import WebsocketTTSService
|
|
26
|
+
from pipecat.transcriptions.language import Language
|
|
27
|
+
from pipecat.utils.tracing.service_decorators import traced_tts
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class LokutorTTSSettings(TTSSettings):
|
|
32
|
+
"""Settings for Lokutor TTS service."""
|
|
33
|
+
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class LokutorTTSService(WebsocketTTSService):
|
|
38
|
+
"""Lokutor TTS service implementation."""
|
|
39
|
+
|
|
40
|
+
Settings = LokutorTTSSettings
|
|
41
|
+
_settings: LokutorTTSSettings
|
|
42
|
+
|
|
43
|
+
class InputParams(BaseModel):
|
|
44
|
+
"""Input parameters for Lokutor TTS such as speed and language."""
|
|
45
|
+
|
|
46
|
+
language: Optional[Language] = None
|
|
47
|
+
speed: Optional[float] = 1.0
|
|
48
|
+
steps: Optional[int] = 5
|
|
49
|
+
visemes: Optional[bool] = False
|
|
50
|
+
|
|
51
|
+
SUPPORTED_VOICES = {"M1", "M2", "M3", "M4", "M5", "F1", "F2", "F3", "F4", "F5"}
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
*,
|
|
56
|
+
api_key: str,
|
|
57
|
+
voice_id: str = "F1",
|
|
58
|
+
sample_rate: int = 44100,
|
|
59
|
+
params: Optional[InputParams] = None,
|
|
60
|
+
settings: Optional[LokutorTTSSettings] = None,
|
|
61
|
+
base_url: str = "wss://api.lokutor.com/ws",
|
|
62
|
+
**kwargs,
|
|
63
|
+
):
|
|
64
|
+
if voice_id not in self.SUPPORTED_VOICES:
|
|
65
|
+
raise ValueError(f"Invalid voice_id '{voice_id}'")
|
|
66
|
+
|
|
67
|
+
self._api_key = api_key
|
|
68
|
+
self._voice_id = voice_id
|
|
69
|
+
self._params = params or self.InputParams()
|
|
70
|
+
|
|
71
|
+
default_settings = self.Settings(
|
|
72
|
+
model=None,
|
|
73
|
+
voice=self._voice_id,
|
|
74
|
+
language=None,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if params is not None and settings is None:
|
|
78
|
+
default_settings.language = params.language
|
|
79
|
+
|
|
80
|
+
if settings is not None:
|
|
81
|
+
default_settings.apply_update(settings)
|
|
82
|
+
|
|
83
|
+
super().__init__(
|
|
84
|
+
push_start_frame=True,
|
|
85
|
+
push_stop_frames=True,
|
|
86
|
+
pause_frame_processing=True,
|
|
87
|
+
sample_rate=sample_rate,
|
|
88
|
+
settings=default_settings,
|
|
89
|
+
**kwargs,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
self._sample_rate = sample_rate
|
|
93
|
+
self._base_url = base_url
|
|
94
|
+
self._websocket = None
|
|
95
|
+
self._receive_task = None
|
|
96
|
+
|
|
97
|
+
async def _connect(self):
|
|
98
|
+
await super()._connect()
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
await self._connect_websocket()
|
|
102
|
+
except Exception as e:
|
|
103
|
+
raise ConnectionError(f"Failed to connect to Lokutor: {e}") from e
|
|
104
|
+
|
|
105
|
+
if self._websocket and not self._receive_task:
|
|
106
|
+
self._receive_task = self.create_task(self._receive_task_handler(self._report_error))
|
|
107
|
+
|
|
108
|
+
async def _disconnect(self):
|
|
109
|
+
await super()._disconnect()
|
|
110
|
+
|
|
111
|
+
if self._receive_task:
|
|
112
|
+
await self.cancel_task(self._receive_task)
|
|
113
|
+
self._receive_task = None
|
|
114
|
+
|
|
115
|
+
await self._disconnect_websocket()
|
|
116
|
+
|
|
117
|
+
async def _connect_websocket(self):
|
|
118
|
+
if self._websocket and self._websocket.state is State.OPEN:
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
logger.debug("Connecting to Lokutor")
|
|
122
|
+
url = f"{self._base_url}?api_key={self._api_key}"
|
|
123
|
+
self._websocket = await websocket_connect(url)
|
|
124
|
+
|
|
125
|
+
await self._call_event_handler("on_connected")
|
|
126
|
+
|
|
127
|
+
async def _disconnect_websocket(self):
|
|
128
|
+
try:
|
|
129
|
+
await self.stop_all_metrics()
|
|
130
|
+
if self._websocket:
|
|
131
|
+
logger.debug("Disconnecting from Lokutor")
|
|
132
|
+
await self._websocket.close()
|
|
133
|
+
except Exception as exc:
|
|
134
|
+
await self.push_error(error_msg=f"Unknown error occurred: {exc}", exception=exc)
|
|
135
|
+
finally:
|
|
136
|
+
self._websocket = None
|
|
137
|
+
await self._call_event_handler("on_disconnected")
|
|
138
|
+
|
|
139
|
+
async def _receive_messages(self):
|
|
140
|
+
"""Keep the websocket connection alive.
|
|
141
|
+
|
|
142
|
+
Lokutor uses request-response (send request, receive audio), not streaming.
|
|
143
|
+
All message handling happens in run_tts(). This method just keeps the
|
|
144
|
+
background receive task alive to maintain the persistent connection.
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
while True:
|
|
148
|
+
await asyncio.sleep(1)
|
|
149
|
+
except asyncio.CancelledError:
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
def _get_websocket(self):
|
|
153
|
+
if self._websocket is None:
|
|
154
|
+
raise ConnectionError("Lokutor websocket not connected")
|
|
155
|
+
return self._websocket
|
|
156
|
+
|
|
157
|
+
def can_generate_metrics(self) -> bool:
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
@traced_tts
|
|
161
|
+
async def run_tts(self, text: str, context_id: str) -> AsyncGenerator[Frame, None]:
|
|
162
|
+
logger.debug(f"{self}: Generating TTS [{text}]")
|
|
163
|
+
|
|
164
|
+
await self.start_tts_usage_metrics(text)
|
|
165
|
+
yield TTSStartedFrame(context_id=context_id)
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
if not self._websocket or self._websocket.state is State.CLOSED:
|
|
169
|
+
await self._connect()
|
|
170
|
+
|
|
171
|
+
request = {
|
|
172
|
+
"text": text,
|
|
173
|
+
"voice": self._voice_id,
|
|
174
|
+
"speed": self._params.speed,
|
|
175
|
+
"steps": self._params.steps,
|
|
176
|
+
"visemes": self._params.visemes,
|
|
177
|
+
}
|
|
178
|
+
if self._params.language:
|
|
179
|
+
lokutor_lang = language_to_lokutor_language(self._params.language)
|
|
180
|
+
if lokutor_lang:
|
|
181
|
+
request["lang"] = lokutor_lang
|
|
182
|
+
|
|
183
|
+
request_json = json.dumps(request)
|
|
184
|
+
logger.debug(f"Sending request to Lokutor: {request_json}")
|
|
185
|
+
|
|
186
|
+
await self.start_ttfb_metrics()
|
|
187
|
+
await self._get_websocket().send(request_json)
|
|
188
|
+
logger.debug("Request sent to Lokutor, waiting for first response...")
|
|
189
|
+
|
|
190
|
+
first_audio_received = False
|
|
191
|
+
while True:
|
|
192
|
+
try:
|
|
193
|
+
logger.debug("Waiting for message from Lokutor...")
|
|
194
|
+
message = await asyncio.wait_for(self._get_websocket().recv(), timeout=10.0)
|
|
195
|
+
logger.debug(
|
|
196
|
+
f"Received message from Lokutor: {type(message)} {len(message) if isinstance(message, bytes) else message[:100]}"
|
|
197
|
+
)
|
|
198
|
+
if isinstance(message, str):
|
|
199
|
+
try:
|
|
200
|
+
data = json.loads(message)
|
|
201
|
+
if isinstance(data, dict):
|
|
202
|
+
msg_type = data.get("type")
|
|
203
|
+
if msg_type == "eos":
|
|
204
|
+
logger.debug("Received EOS from Lokutor")
|
|
205
|
+
break
|
|
206
|
+
elif msg_type == "error":
|
|
207
|
+
error_msg = data.get("message", "Unknown error")
|
|
208
|
+
logger.error(f"Lokutor error: {error_msg}")
|
|
209
|
+
yield ErrorFrame(error=f"Lokutor error: {error_msg}")
|
|
210
|
+
break
|
|
211
|
+
elif isinstance(data, list):
|
|
212
|
+
logger.debug(f"Received viseme data: {len(data)} visemes")
|
|
213
|
+
except json.JSONDecodeError:
|
|
214
|
+
logger.warning(f"Received unknown text message: {message}")
|
|
215
|
+
else:
|
|
216
|
+
logger.debug(f"Received audio data: {len(message)} bytes")
|
|
217
|
+
if not first_audio_received:
|
|
218
|
+
logger.debug("First audio chunk received - stopping TTFB metrics")
|
|
219
|
+
await self.stop_ttfb_metrics()
|
|
220
|
+
first_audio_received = True
|
|
221
|
+
yield TTSAudioRawFrame(message, self.sample_rate, 1)
|
|
222
|
+
except asyncio.TimeoutError:
|
|
223
|
+
logger.error("Timeout waiting for Lokutor response")
|
|
224
|
+
yield ErrorFrame(error="Timeout waiting for Lokutor response")
|
|
225
|
+
break
|
|
226
|
+
except Exception as e:
|
|
227
|
+
logger.error(f"Error receiving from Lokutor: {e}")
|
|
228
|
+
yield ErrorFrame(error=f"Error receiving from Lokutor: {e}")
|
|
229
|
+
break
|
|
230
|
+
|
|
231
|
+
except ConnectionError as e:
|
|
232
|
+
logger.error(f"{self} exception: {e}")
|
|
233
|
+
yield ErrorFrame(error=f"Unknown error occurred: {e}")
|
|
234
|
+
except Exception as e:
|
|
235
|
+
logger.error(f"{self} exception: {e}")
|
|
236
|
+
yield ErrorFrame(error=f"Unknown error occurred: {e}")
|
|
237
|
+
finally:
|
|
238
|
+
logger.debug(f"{self}: Finished TTS [{text}]")
|
|
239
|
+
if not first_audio_received:
|
|
240
|
+
await self.stop_ttfb_metrics()
|
|
241
|
+
yield TTSStoppedFrame(context_id=context_id)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def language_to_lokutor_language(language):
|
|
245
|
+
if isinstance(language, Language):
|
|
246
|
+
mapping = {
|
|
247
|
+
Language.EN: "en",
|
|
248
|
+
Language.ES: "es",
|
|
249
|
+
Language.FR: "fr",
|
|
250
|
+
Language.PT: "pt",
|
|
251
|
+
Language.KO: "ko",
|
|
252
|
+
}
|
|
253
|
+
return mapping.get(language)
|
|
254
|
+
if hasattr(language, "value"):
|
|
255
|
+
return language.value
|
|
256
|
+
return str(language)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def lokutor_language_to_language(language):
|
|
260
|
+
if not isinstance(language, str):
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
mapping = {
|
|
264
|
+
"en": Language.EN,
|
|
265
|
+
"es": Language.ES,
|
|
266
|
+
"fr": Language.FR,
|
|
267
|
+
"pt": Language.PT,
|
|
268
|
+
"ko": Language.KO,
|
|
269
|
+
}
|
|
270
|
+
return mapping.get(language)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pipecat-lokutor"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Lokutor TTS integration for Pipecat"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "BSD-2-Clause" }
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Lokutor AI", email = "your-email@lokutor.com" },
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pipecat>=0.0.86",
|
|
13
|
+
"websockets>=12.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["hatchling"]
|
|
18
|
+
build-backend = "hatchling.build"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.build.targets.wheel]
|
|
21
|
+
packages = ["src/pipecat_lokutor"]
|
|
22
|
+
|
|
23
|
+
[tool.hatch.build.targets.sdist]
|
|
24
|
+
packages = ["src/pipecat_lokutor"]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/lokutor-ai/pipecat-lokutor"
|
|
28
|
+
Source = "https://github.com/lokutor-ai/pipecat-lokutor"
|