livekit-plugins-azure 0.2.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_azure-0.2.0/PKG-INFO +38 -0
- livekit_plugins_azure-0.2.0/README.md +13 -0
- livekit_plugins_azure-0.2.0/livekit/plugins/azure/__init__.py +35 -0
- livekit_plugins_azure-0.2.0/livekit/plugins/azure/log.py +3 -0
- livekit_plugins_azure-0.2.0/livekit/plugins/azure/stt.py +224 -0
- livekit_plugins_azure-0.2.0/livekit/plugins/azure/tts.py +168 -0
- livekit_plugins_azure-0.2.0/livekit/plugins/azure/version.py +13 -0
- livekit_plugins_azure-0.2.0/livekit_plugins_azure.egg-info/PKG-INFO +38 -0
- livekit_plugins_azure-0.2.0/livekit_plugins_azure.egg-info/SOURCES.txt +13 -0
- livekit_plugins_azure-0.2.0/livekit_plugins_azure.egg-info/dependency_links.txt +1 -0
- livekit_plugins_azure-0.2.0/livekit_plugins_azure.egg-info/requires.txt +3 -0
- livekit_plugins_azure-0.2.0/livekit_plugins_azure.egg-info/top_level.txt +1 -0
- livekit_plugins_azure-0.2.0/pyproject.toml +3 -0
- livekit_plugins_azure-0.2.0/setup.cfg +4 -0
- livekit_plugins_azure-0.2.0/setup.py +59 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: livekit-plugins-azure
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Agent Framework plugin for services from Azure
|
|
5
|
+
Home-page: https://github.com/livekit/agents
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Documentation, https://docs.livekit.io
|
|
8
|
+
Project-URL: Website, https://livekit.io/
|
|
9
|
+
Project-URL: Source, https://github.com/livekit/agents
|
|
10
|
+
Keywords: webrtc,realtime,audio,video,livekit
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
14
|
+
Classifier: Topic :: Multimedia :: Video
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Requires-Python: >=3.9.0
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: livekit>=0.9.0
|
|
23
|
+
Requires-Dist: livekit-agents>=0.3.0
|
|
24
|
+
Requires-Dist: azure-cognitiveservices-speech>=1.35.0
|
|
25
|
+
|
|
26
|
+
# LiveKit Plugins Azure
|
|
27
|
+
|
|
28
|
+
Agent Framework plugin for services from Azure Cognitive Services. Currently supports STT and TTS.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install livekit-plugins-azure
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Pre-requisites
|
|
37
|
+
|
|
38
|
+
You'll need to specify an Azure Speech Key and a Deployment Region. They can be set as environment variables: `AZURE_SPEECH_KEY` and `AZURE_SPEECH_REGION`, respectively.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# LiveKit Plugins Azure
|
|
2
|
+
|
|
3
|
+
Agent Framework plugin for services from Azure Cognitive Services. Currently supports STT and TTS.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install livekit-plugins-azure
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Pre-requisites
|
|
12
|
+
|
|
13
|
+
You'll need to specify an Azure Speech Key and a Deployment Region. They can be set as environment variables: `AZURE_SPEECH_KEY` and `AZURE_SPEECH_REGION`, respectively.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
from .stt import STT, SpeechStream
|
|
14
|
+
from .tts import TTS
|
|
15
|
+
from .version import __version__
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"STT",
|
|
19
|
+
"SpeechStream",
|
|
20
|
+
"TTS",
|
|
21
|
+
"__version__",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
from livekit.agents import Plugin
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AzurePlugin(Plugin):
|
|
28
|
+
def __init__(self):
|
|
29
|
+
super().__init__(__name__, __version__, __package__)
|
|
30
|
+
|
|
31
|
+
def download_files(self):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
Plugin.register_plugin(AzurePlugin())
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import os
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
from livekit import rtc
|
|
21
|
+
from livekit.agents import stt
|
|
22
|
+
from livekit.agents.utils import AudioBuffer
|
|
23
|
+
|
|
24
|
+
import azure.cognitiveservices.speech as speechsdk
|
|
25
|
+
|
|
26
|
+
from .log import logger
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class STTOptions:
|
|
31
|
+
speech_key: str
|
|
32
|
+
speech_region: str
|
|
33
|
+
sample_rate: int
|
|
34
|
+
num_channels: int
|
|
35
|
+
languages: list[
|
|
36
|
+
str
|
|
37
|
+
] # see https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=stt
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class STT(stt.STT):
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
*,
|
|
44
|
+
speech_key: str | None = None,
|
|
45
|
+
speech_region: str | None = None,
|
|
46
|
+
sample_rate: int = 48000,
|
|
47
|
+
num_channels: int = 1,
|
|
48
|
+
languages: list[str] = [], # when empty, auto-detect the language
|
|
49
|
+
):
|
|
50
|
+
super().__init__(streaming_supported=True)
|
|
51
|
+
|
|
52
|
+
speech_key = speech_key or os.environ.get("AZURE_SPEECH_KEY")
|
|
53
|
+
if not speech_key:
|
|
54
|
+
raise ValueError("AZURE_SPEECH_KEY must be set")
|
|
55
|
+
|
|
56
|
+
speech_region = speech_region or os.environ.get("AZURE_SPEECH_REGION")
|
|
57
|
+
if not speech_region:
|
|
58
|
+
raise ValueError("AZURE_SPEECH_REGION must be set")
|
|
59
|
+
|
|
60
|
+
self._config = STTOptions(
|
|
61
|
+
speech_key=speech_key,
|
|
62
|
+
speech_region=speech_region,
|
|
63
|
+
languages=languages,
|
|
64
|
+
sample_rate=sample_rate,
|
|
65
|
+
num_channels=num_channels,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
async def recognize(
|
|
69
|
+
self,
|
|
70
|
+
*,
|
|
71
|
+
buffer: AudioBuffer,
|
|
72
|
+
language: str | None = None,
|
|
73
|
+
) -> stt.SpeechEvent:
|
|
74
|
+
raise NotImplementedError("Azure STT does not support single frame recognition")
|
|
75
|
+
|
|
76
|
+
def stream(
|
|
77
|
+
self,
|
|
78
|
+
*,
|
|
79
|
+
language: str | None = None,
|
|
80
|
+
) -> "SpeechStream":
|
|
81
|
+
return SpeechStream(self._config)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class SpeechStream(stt.SpeechStream):
|
|
85
|
+
def __init__(self, opts: STTOptions) -> None:
|
|
86
|
+
super().__init__()
|
|
87
|
+
self._opts = opts
|
|
88
|
+
self._event_queue = asyncio.Queue[Optional[stt.SpeechEvent]]()
|
|
89
|
+
self._closed = False
|
|
90
|
+
self._speaking = False
|
|
91
|
+
|
|
92
|
+
self._stream = speechsdk.audio.PushAudioInputStream(
|
|
93
|
+
stream_format=speechsdk.audio.AudioStreamFormat(
|
|
94
|
+
samples_per_second=self._opts.sample_rate,
|
|
95
|
+
bits_per_sample=16,
|
|
96
|
+
channels=self._opts.num_channels,
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
self._recognizer = _create_speech_recognizer(
|
|
100
|
+
config=self._opts, stream=self._stream
|
|
101
|
+
)
|
|
102
|
+
self._recognizer.recognizing.connect(self._on_recognizing)
|
|
103
|
+
self._recognizer.recognized.connect(self._on_recognized)
|
|
104
|
+
self._recognizer.speech_start_detected.connect(self._on_speech_start)
|
|
105
|
+
self._recognizer.speech_end_detected.connect(self._on_speech_end)
|
|
106
|
+
self._recognizer.session_stopped.connect(self._on_session_stopped)
|
|
107
|
+
self._recognizer.start_continuous_recognition()
|
|
108
|
+
self._done_event = asyncio.Event()
|
|
109
|
+
self._loop = asyncio.get_running_loop()
|
|
110
|
+
|
|
111
|
+
def push_frame(self, frame: rtc.AudioFrame) -> None:
|
|
112
|
+
if self._closed:
|
|
113
|
+
raise ValueError("cannot push frame to closed stream")
|
|
114
|
+
|
|
115
|
+
self._stream.write(frame.data.tobytes())
|
|
116
|
+
|
|
117
|
+
async def aclose(self, *, wait: bool = True) -> None:
|
|
118
|
+
if self._closed:
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
self._closed = True
|
|
122
|
+
self._stream.close()
|
|
123
|
+
|
|
124
|
+
await self._done_event.wait()
|
|
125
|
+
|
|
126
|
+
def _cleanup():
|
|
127
|
+
self._recognizer.stop_continuous_recognition()
|
|
128
|
+
del self._recognizer
|
|
129
|
+
|
|
130
|
+
await asyncio.to_thread(_cleanup)
|
|
131
|
+
|
|
132
|
+
def _on_recognized(self, evt: speechsdk.SpeechRecognitionEventArgs):
|
|
133
|
+
detected_lg = speechsdk.AutoDetectSourceLanguageResult(evt.result).language
|
|
134
|
+
text = evt.result.text.strip()
|
|
135
|
+
if not text:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
final_data = stt.SpeechData(
|
|
139
|
+
language=detected_lg,
|
|
140
|
+
confidence=1.0,
|
|
141
|
+
text=evt.result.text,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
self._threadsafe_put(
|
|
145
|
+
stt.SpeechEvent(
|
|
146
|
+
type=stt.SpeechEventType.FINAL_TRANSCRIPT,
|
|
147
|
+
alternatives=[final_data],
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def _on_recognizing(self, evt: speechsdk.SpeechRecognitionEventArgs):
|
|
152
|
+
detected_lg = speechsdk.AutoDetectSourceLanguageResult(evt.result).language
|
|
153
|
+
text = evt.result.text.strip()
|
|
154
|
+
if not text:
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
interim_data = stt.SpeechData(
|
|
158
|
+
language=detected_lg,
|
|
159
|
+
confidence=0.0,
|
|
160
|
+
text=evt.result.text,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
self._threadsafe_put(
|
|
164
|
+
stt.SpeechEvent(
|
|
165
|
+
type=stt.SpeechEventType.INTERIM_TRANSCRIPT,
|
|
166
|
+
alternatives=[interim_data],
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def _on_speech_start(self, evt: speechsdk.SpeechRecognitionEventArgs):
|
|
171
|
+
if self._speaking:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
self._speaking = True
|
|
175
|
+
self._threadsafe_put(stt.SpeechEvent(type=stt.SpeechEventType.START_OF_SPEECH))
|
|
176
|
+
|
|
177
|
+
def _on_speech_end(self, evt: speechsdk.SpeechRecognitionEventArgs):
|
|
178
|
+
if not self._speaking:
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
self._speaking = False
|
|
182
|
+
self._threadsafe_put(stt.SpeechEvent(type=stt.SpeechEventType.END_OF_SPEECH))
|
|
183
|
+
|
|
184
|
+
def _on_session_stopped(self, evt: speechsdk.SpeechRecognitionEventArgs):
|
|
185
|
+
if not self._closed:
|
|
186
|
+
logger.error("session stopped unexpectedly")
|
|
187
|
+
|
|
188
|
+
self._loop.call_soon_threadsafe(self._done_event.set)
|
|
189
|
+
self._threadsafe_put(None)
|
|
190
|
+
|
|
191
|
+
def _threadsafe_put(self, evt: stt.SpeechEvent | None):
|
|
192
|
+
self._loop.call_soon_threadsafe(self._event_queue.put_nowait, evt)
|
|
193
|
+
|
|
194
|
+
async def __anext__(self) -> stt.SpeechEvent:
|
|
195
|
+
evt = await self._event_queue.get()
|
|
196
|
+
if evt is None:
|
|
197
|
+
raise StopAsyncIteration
|
|
198
|
+
|
|
199
|
+
return evt
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _create_speech_recognizer(
|
|
203
|
+
*, config: STTOptions, stream: speechsdk.audio.AudioInputStream
|
|
204
|
+
) -> speechsdk.SpeechRecognizer:
|
|
205
|
+
speech_config = speechsdk.SpeechConfig(
|
|
206
|
+
subscription=config.speech_key, region=config.speech_region
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
auto_detect_source_language_config = None
|
|
210
|
+
if config.languages:
|
|
211
|
+
auto_detect_source_language_config = (
|
|
212
|
+
speechsdk.languageconfig.AutoDetectSourceLanguageConfig(
|
|
213
|
+
languages=config.languages
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
audio_config = speechsdk.audio.AudioConfig(stream=stream)
|
|
218
|
+
speech_recognizer = speechsdk.SpeechRecognizer(
|
|
219
|
+
speech_config=speech_config,
|
|
220
|
+
audio_config=audio_config,
|
|
221
|
+
auto_detect_source_language_config=auto_detect_source_language_config, # type: ignore
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
return speech_recognizer
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import contextlib
|
|
17
|
+
import os
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
from livekit import rtc
|
|
22
|
+
from livekit.agents import tts
|
|
23
|
+
|
|
24
|
+
import azure.cognitiveservices.speech as speechsdk
|
|
25
|
+
|
|
26
|
+
from .log import logger
|
|
27
|
+
|
|
28
|
+
AZURE_SAMPLE_RATE: int = 16000
|
|
29
|
+
AZURE_BITS_PER_SAMPLE: int = 16
|
|
30
|
+
AZURE_NUM_CHANNELS: int = 1
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class _TTSOptions:
|
|
35
|
+
speech_key: str | None = None
|
|
36
|
+
speech_region: str | None = None
|
|
37
|
+
# see https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=tts
|
|
38
|
+
voice: str | None = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TTS(tts.TTS):
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
*,
|
|
45
|
+
speech_key: str | None = None,
|
|
46
|
+
speech_region: str | None = None,
|
|
47
|
+
voice: str | None = None,
|
|
48
|
+
) -> None:
|
|
49
|
+
super().__init__(
|
|
50
|
+
streaming_supported=False,
|
|
51
|
+
sample_rate=AZURE_SAMPLE_RATE,
|
|
52
|
+
num_channels=AZURE_NUM_CHANNELS,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
speech_key = speech_key or os.environ.get("AZURE_SPEECH_KEY")
|
|
56
|
+
if not speech_key:
|
|
57
|
+
raise ValueError("AZURE_SPEECH_KEY must be set")
|
|
58
|
+
|
|
59
|
+
speech_region = speech_region or os.environ.get("AZURE_SPEECH_REGION")
|
|
60
|
+
if not speech_region:
|
|
61
|
+
raise ValueError("AZURE_SPEECH_REGION must be set")
|
|
62
|
+
|
|
63
|
+
self._opts = _TTSOptions(
|
|
64
|
+
speech_key=speech_key,
|
|
65
|
+
speech_region=speech_region,
|
|
66
|
+
voice=voice,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def synthesize(
|
|
70
|
+
self,
|
|
71
|
+
text: str,
|
|
72
|
+
) -> "ChunkedStream":
|
|
73
|
+
return ChunkedStream(text, self._opts)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ChunkedStream(tts.ChunkedStream):
|
|
77
|
+
def __init__(self, text: str, opts: _TTSOptions) -> None:
|
|
78
|
+
self._opts = opts
|
|
79
|
+
self._text = text
|
|
80
|
+
self._main_task: asyncio.Task | None = None
|
|
81
|
+
self._queue = asyncio.Queue[Optional[tts.SynthesizedAudio]]()
|
|
82
|
+
|
|
83
|
+
async def _run(self):
|
|
84
|
+
try:
|
|
85
|
+
stream_callback = _PushAudioOutputStreamCallback(
|
|
86
|
+
asyncio.get_running_loop(), self._queue
|
|
87
|
+
)
|
|
88
|
+
push_stream = speechsdk.audio.PushAudioOutputStream(stream_callback)
|
|
89
|
+
synthesizer = _create_speech_synthesizer(
|
|
90
|
+
config=self._opts, stream=push_stream
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def _synthesize() -> speechsdk.SpeechSynthesisResult:
|
|
94
|
+
return synthesizer.speak_text_async(self._text).get() # type: ignore
|
|
95
|
+
|
|
96
|
+
result = await asyncio.to_thread(_synthesize)
|
|
97
|
+
if result.reason != speechsdk.ResultReason.SynthesizingAudioCompleted:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"failed to synthesize audio: {result.reason} {result.cancellation_details}"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def _cleanup() -> None:
|
|
103
|
+
nonlocal synthesizer, result
|
|
104
|
+
del synthesizer
|
|
105
|
+
del result
|
|
106
|
+
|
|
107
|
+
await asyncio.to_thread(_cleanup)
|
|
108
|
+
|
|
109
|
+
except Exception:
|
|
110
|
+
logger.exception("failed to synthesize")
|
|
111
|
+
finally:
|
|
112
|
+
self._queue.put_nowait(None)
|
|
113
|
+
|
|
114
|
+
async def __anext__(self) -> tts.SynthesizedAudio:
|
|
115
|
+
if not self._main_task:
|
|
116
|
+
self._main_task = asyncio.create_task(self._run())
|
|
117
|
+
|
|
118
|
+
frame = await self._queue.get()
|
|
119
|
+
if frame is None:
|
|
120
|
+
raise StopAsyncIteration
|
|
121
|
+
|
|
122
|
+
return frame
|
|
123
|
+
|
|
124
|
+
async def aclose(self) -> None:
|
|
125
|
+
if not self._main_task:
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
self._main_task.cancel()
|
|
129
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
130
|
+
await self._main_task
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _create_speech_synthesizer(
|
|
134
|
+
*, config: _TTSOptions, stream: speechsdk.audio.AudioOutputStream
|
|
135
|
+
) -> speechsdk.SpeechSynthesizer:
|
|
136
|
+
speech_config = speechsdk.SpeechConfig(
|
|
137
|
+
subscription=config.speech_key, region=config.speech_region
|
|
138
|
+
)
|
|
139
|
+
stream_config = speechsdk.audio.AudioOutputConfig(stream=stream)
|
|
140
|
+
if config.voice is not None:
|
|
141
|
+
speech_config.speech_synthesis_voice_name = config.voice
|
|
142
|
+
|
|
143
|
+
return speechsdk.SpeechSynthesizer(
|
|
144
|
+
speech_config=speech_config, audio_config=stream_config
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class _PushAudioOutputStreamCallback(speechsdk.audio.PushAudioOutputStreamCallback):
|
|
149
|
+
def __init__(
|
|
150
|
+
self,
|
|
151
|
+
loop: asyncio.AbstractEventLoop,
|
|
152
|
+
event_queue: asyncio.Queue[tts.SynthesizedAudio | None],
|
|
153
|
+
):
|
|
154
|
+
super().__init__()
|
|
155
|
+
self._event_queue = event_queue
|
|
156
|
+
self._loop = loop
|
|
157
|
+
|
|
158
|
+
def write(self, audio_buffer: memoryview) -> int:
|
|
159
|
+
audio_frame = rtc.AudioFrame(
|
|
160
|
+
data=audio_buffer,
|
|
161
|
+
sample_rate=AZURE_SAMPLE_RATE,
|
|
162
|
+
num_channels=AZURE_NUM_CHANNELS,
|
|
163
|
+
samples_per_channel=audio_buffer.nbytes // 2,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
audio = tts.SynthesizedAudio(text="", data=audio_frame)
|
|
167
|
+
self._loop.call_soon_threadsafe(self._event_queue.put_nowait, audio)
|
|
168
|
+
return audio_buffer.nbytes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: livekit-plugins-azure
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Agent Framework plugin for services from Azure
|
|
5
|
+
Home-page: https://github.com/livekit/agents
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Documentation, https://docs.livekit.io
|
|
8
|
+
Project-URL: Website, https://livekit.io/
|
|
9
|
+
Project-URL: Source, https://github.com/livekit/agents
|
|
10
|
+
Keywords: webrtc,realtime,audio,video,livekit
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
14
|
+
Classifier: Topic :: Multimedia :: Video
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Requires-Python: >=3.9.0
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: livekit>=0.9.0
|
|
23
|
+
Requires-Dist: livekit-agents>=0.3.0
|
|
24
|
+
Requires-Dist: azure-cognitiveservices-speech>=1.35.0
|
|
25
|
+
|
|
26
|
+
# LiveKit Plugins Azure
|
|
27
|
+
|
|
28
|
+
Agent Framework plugin for services from Azure Cognitive Services. Currently supports STT and TTS.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install livekit-plugins-azure
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Pre-requisites
|
|
37
|
+
|
|
38
|
+
You'll need to specify an Azure Speech Key and a Deployment Region. They can be set as environment variables: `AZURE_SPEECH_KEY` and `AZURE_SPEECH_REGION`, respectively.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
livekit/plugins/azure/__init__.py
|
|
5
|
+
livekit/plugins/azure/log.py
|
|
6
|
+
livekit/plugins/azure/stt.py
|
|
7
|
+
livekit/plugins/azure/tts.py
|
|
8
|
+
livekit/plugins/azure/version.py
|
|
9
|
+
livekit_plugins_azure.egg-info/PKG-INFO
|
|
10
|
+
livekit_plugins_azure.egg-info/SOURCES.txt
|
|
11
|
+
livekit_plugins_azure.egg-info/dependency_links.txt
|
|
12
|
+
livekit_plugins_azure.egg-info/requires.txt
|
|
13
|
+
livekit_plugins_azure.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
livekit
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import pathlib
|
|
15
|
+
|
|
16
|
+
import setuptools
|
|
17
|
+
import setuptools.command.build_py
|
|
18
|
+
|
|
19
|
+
here = pathlib.Path(__file__).parent.resolve()
|
|
20
|
+
about = {}
|
|
21
|
+
with open(os.path.join(here, "livekit", "plugins", "azure", "version.py"), "r") as f:
|
|
22
|
+
exec(f.read(), about)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
setuptools.setup(
|
|
26
|
+
name="livekit-plugins-azure",
|
|
27
|
+
version=about["__version__"],
|
|
28
|
+
description="Agent Framework plugin for services from Azure",
|
|
29
|
+
long_description=(here / "README.md").read_text(encoding="utf-8"),
|
|
30
|
+
long_description_content_type="text/markdown",
|
|
31
|
+
url="https://github.com/livekit/agents",
|
|
32
|
+
cmdclass={},
|
|
33
|
+
classifiers=[
|
|
34
|
+
"Intended Audience :: Developers",
|
|
35
|
+
"License :: OSI Approved :: Apache Software License",
|
|
36
|
+
"Topic :: Multimedia :: Sound/Audio",
|
|
37
|
+
"Topic :: Multimedia :: Video",
|
|
38
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
39
|
+
"Programming Language :: Python :: 3",
|
|
40
|
+
"Programming Language :: Python :: 3.9",
|
|
41
|
+
"Programming Language :: Python :: 3.10",
|
|
42
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
43
|
+
],
|
|
44
|
+
keywords=["webrtc", "realtime", "audio", "video", "livekit"],
|
|
45
|
+
license="Apache-2.0",
|
|
46
|
+
packages=setuptools.find_namespace_packages(include=["livekit.*"]),
|
|
47
|
+
python_requires=">=3.9.0",
|
|
48
|
+
install_requires=[
|
|
49
|
+
"livekit >= 0.9.0",
|
|
50
|
+
"livekit-agents >= 0.3.0",
|
|
51
|
+
"azure-cognitiveservices-speech >= 1.35.0",
|
|
52
|
+
],
|
|
53
|
+
package_data={},
|
|
54
|
+
project_urls={
|
|
55
|
+
"Documentation": "https://docs.livekit.io",
|
|
56
|
+
"Website": "https://livekit.io/",
|
|
57
|
+
"Source": "https://github.com/livekit/agents",
|
|
58
|
+
},
|
|
59
|
+
)
|