all frame processors are asynchrnous
In this commit we make all frame processors asynchronous, that is, they have an internal queue and they push frames using a task from that queue.
This commit is contained in:
22
CHANGELOG.md
22
CHANGELOG.md
@@ -48,15 +48,10 @@ async def on_connected(processor):
|
|||||||
frames. To achieve that, each frame processor should only output frames from a
|
frames. To achieve that, each frame processor should only output frames from a
|
||||||
single task.
|
single task.
|
||||||
|
|
||||||
In this version we introduce synchronous and asynchronous frame
|
In this version all the frame processors have their own task to push
|
||||||
processors. The synchronous processors push output frames from the same task
|
frames. That is, when `push_frame()` is called the given frame will be put
|
||||||
that they receive input frames, and therefore only pushing frames from one
|
into an internal queue (with the exception of system frames) and a frame
|
||||||
task. Asynchronous frame processors can have internal tasks to perform things
|
processor task will push it out.
|
||||||
asynchronously (e.g. receiving data from a websocket) but they also have a
|
|
||||||
single task where they push frames from.
|
|
||||||
|
|
||||||
By default, frame processors are synchronous. To change a frame processor to
|
|
||||||
asynchronous you only need to pass `sync=False` to the base class constructor.
|
|
||||||
|
|
||||||
- Added pipeline clocks. A pipeline clock is used by the output transport to
|
- Added pipeline clocks. A pipeline clock is used by the output transport to
|
||||||
know when a frame needs to be presented. For that, all frames now have an
|
know when a frame needs to be presented. For that, all frames now have an
|
||||||
@@ -68,9 +63,7 @@ async def on_connected(processor):
|
|||||||
`SystemClock`). This clock will be passed to each frame processor via the
|
`SystemClock`). This clock will be passed to each frame processor via the
|
||||||
`StartFrame`.
|
`StartFrame`.
|
||||||
|
|
||||||
- Added `CartesiaHttpTTSService`. This is a synchronous frame processor
|
- Added `CartesiaHttpTTSService`.
|
||||||
(i.e. given an input text frame it will wait for the whole output before
|
|
||||||
returning).
|
|
||||||
|
|
||||||
- `DailyTransport` now supports setting the audio bitrate to improve audio
|
- `DailyTransport` now supports setting the audio bitrate to improve audio
|
||||||
quality through the `DailyParams.audio_out_bitrate` parameter. The new
|
quality through the `DailyParams.audio_out_bitrate` parameter. The new
|
||||||
@@ -110,8 +103,9 @@ async def on_connected(processor):
|
|||||||
pipelines to be executed concurrently. The difference between a
|
pipelines to be executed concurrently. The difference between a
|
||||||
`SyncParallelPipeline` and a `ParallelPipeline` is that, given an input frame,
|
`SyncParallelPipeline` and a `ParallelPipeline` is that, given an input frame,
|
||||||
the `SyncParallelPipeline` will wait for all the internal pipelines to
|
the `SyncParallelPipeline` will wait for all the internal pipelines to
|
||||||
complete. This is achieved by ensuring all the processors in each of the
|
complete. This is achieved by making sure the last processor in each of the
|
||||||
internal pipelines are synchronous.
|
pipelines is synchronous (e.g. an HTTP-based service that waits for the
|
||||||
|
response).
|
||||||
|
|
||||||
- `StartFrame` is back a system frame so we make sure it's processed immediately
|
- `StartFrame` is back a system frame so we make sure it's processed immediately
|
||||||
by all processors. `EndFrame` stays a control frame since it needs to be
|
by all processors. `EndFrame` stays a control frame since it needs to be
|
||||||
|
|||||||
@@ -86,13 +86,13 @@ async def main():
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
|
|
||||||
tts = CartesiaHttpTTSService(
|
tts = CartesiaHttpTTSService(
|
||||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
||||||
)
|
)
|
||||||
|
|
||||||
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
|
||||||
|
|
||||||
imagegen = FalImageGenService(
|
imagegen = FalImageGenService(
|
||||||
params=FalImageGenService.InputParams(image_size="square_hd"),
|
params=FalImageGenService.InputParams(image_size="square_hd"),
|
||||||
aiohttp_session=session,
|
aiohttp_session=session,
|
||||||
@@ -107,8 +107,10 @@ async def main():
|
|||||||
# that, each pipeline runs concurrently and `SyncParallelPipeline` will
|
# that, each pipeline runs concurrently and `SyncParallelPipeline` will
|
||||||
# wait for the input frame to be processed.
|
# wait for the input frame to be processed.
|
||||||
#
|
#
|
||||||
# Note that `SyncParallelPipeline` requires all processors in it to be
|
# Note that `SyncParallelPipeline` requires the last processor in each
|
||||||
# synchronous (which is the default for most processors).
|
# of the pipelines to be synchronous. In this case, we use
|
||||||
|
# `CartesiaHttpTTSService` and `FalImageGenService` which make HTTP
|
||||||
|
# requests and wait for the response.
|
||||||
pipeline = Pipeline(
|
pipeline = Pipeline(
|
||||||
[
|
[
|
||||||
llm, # LLM
|
llm, # LLM
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ async def main():
|
|||||||
self.frame = OutputAudioRawFrame(
|
self.frame = OutputAudioRawFrame(
|
||||||
bytes(self.audio), frame.sample_rate, frame.num_channels
|
bytes(self.audio), frame.sample_rate, frame.num_channels
|
||||||
)
|
)
|
||||||
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
class ImageGrabber(FrameProcessor):
|
class ImageGrabber(FrameProcessor):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -93,6 +94,7 @@ async def main():
|
|||||||
|
|
||||||
if isinstance(frame, URLImageRawFrame):
|
if isinstance(frame, URLImageRawFrame):
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
llm = OpenAILLMService(api_key=os.getenv("OPENAI_API_KEY"), model="gpt-4o")
|
||||||
|
|
||||||
@@ -121,8 +123,10 @@ async def main():
|
|||||||
# `SyncParallelPipeline` will wait for the input frame to be
|
# `SyncParallelPipeline` will wait for the input frame to be
|
||||||
# processed.
|
# processed.
|
||||||
#
|
#
|
||||||
# Note that `SyncParallelPipeline` requires all processors in it to
|
# Note that `SyncParallelPipeline` requires the last processor in
|
||||||
# be synchronous (which is the default for most processors).
|
# each of the pipelines to be synchronous. In this case, we use
|
||||||
|
# `CartesiaHttpTTSService` and `FalImageGenService` which make HTTP
|
||||||
|
# requests and wait for the response.
|
||||||
pipeline = Pipeline(
|
pipeline = Pipeline(
|
||||||
[
|
[
|
||||||
llm, # LLM
|
llm, # LLM
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ class FrameProcessor:
|
|||||||
*,
|
*,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
metrics: FrameProcessorMetrics | None = None,
|
metrics: FrameProcessorMetrics | None = None,
|
||||||
sync: bool = True,
|
|
||||||
loop: asyncio.AbstractEventLoop | None = None,
|
loop: asyncio.AbstractEventLoop | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
@@ -47,7 +46,6 @@ class FrameProcessor:
|
|||||||
self._prev: "FrameProcessor" | None = None
|
self._prev: "FrameProcessor" | None = None
|
||||||
self._next: "FrameProcessor" | None = None
|
self._next: "FrameProcessor" | None = None
|
||||||
self._loop: asyncio.AbstractEventLoop = loop or asyncio.get_running_loop()
|
self._loop: asyncio.AbstractEventLoop = loop or asyncio.get_running_loop()
|
||||||
self._sync = sync
|
|
||||||
|
|
||||||
self._event_handlers: dict = {}
|
self._event_handlers: dict = {}
|
||||||
|
|
||||||
@@ -66,11 +64,8 @@ class FrameProcessor:
|
|||||||
|
|
||||||
# Every processor in Pipecat should only output frames from a single
|
# Every processor in Pipecat should only output frames from a single
|
||||||
# task. This avoid problems like audio overlapping. System frames are
|
# task. This avoid problems like audio overlapping. System frames are
|
||||||
# the exception to this rule.
|
# the exception to this rule. This create this task.
|
||||||
#
|
self.__create_push_task()
|
||||||
# This create this task.
|
|
||||||
if not self._sync:
|
|
||||||
self.__create_push_task()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def interruptions_allowed(self):
|
def interruptions_allowed(self):
|
||||||
@@ -167,7 +162,7 @@ class FrameProcessor:
|
|||||||
await self.push_frame(error, FrameDirection.UPSTREAM)
|
await self.push_frame(error, FrameDirection.UPSTREAM)
|
||||||
|
|
||||||
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
||||||
if self._sync or isinstance(frame, SystemFrame):
|
if isinstance(frame, SystemFrame):
|
||||||
await self.__internal_push_frame(frame, direction)
|
await self.__internal_push_frame(frame, direction)
|
||||||
else:
|
else:
|
||||||
await self.__push_queue.put((frame, direction))
|
await self.__push_queue.put((frame, direction))
|
||||||
@@ -194,13 +189,12 @@ class FrameProcessor:
|
|||||||
#
|
#
|
||||||
|
|
||||||
async def _start_interruption(self):
|
async def _start_interruption(self):
|
||||||
if not self._sync:
|
# Cancel the task. This will stop pushing frames downstream.
|
||||||
# Cancel the task. This will stop pushing frames downstream.
|
self.__push_frame_task.cancel()
|
||||||
self.__push_frame_task.cancel()
|
await self.__push_frame_task
|
||||||
await self.__push_frame_task
|
|
||||||
|
|
||||||
# Create a new queue and task.
|
# Create a new queue and task.
|
||||||
self.__create_push_task()
|
self.__create_push_task()
|
||||||
|
|
||||||
async def _stop_interruption(self):
|
async def _stop_interruption(self):
|
||||||
# Nothing to do right now.
|
# Nothing to do right now.
|
||||||
|
|||||||
@@ -516,7 +516,7 @@ class RTVIProcessor(FrameProcessor):
|
|||||||
params: RTVIProcessorParams = RTVIProcessorParams(),
|
params: RTVIProcessorParams = RTVIProcessorParams(),
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(sync=False, **kwargs)
|
super().__init__(**kwargs)
|
||||||
self._config = config
|
self._config = config
|
||||||
self._params = params
|
self._params = params
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class GStreamerPipelineSource(FrameProcessor):
|
|||||||
clock_sync: bool = True
|
clock_sync: bool = True
|
||||||
|
|
||||||
def __init__(self, *, pipeline: str, out_params: OutputParams = OutputParams(), **kwargs):
|
def __init__(self, *, pipeline: str, out_params: OutputParams = OutputParams(), **kwargs):
|
||||||
super().__init__(sync=False, **kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self._out_params = out_params
|
self._out_params = out_params
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class IdleFrameProcessor(FrameProcessor):
|
|||||||
types: List[type] = [],
|
types: List[type] = [],
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(sync=False, **kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self._callback = callback
|
self._callback = callback
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class UserIdleProcessor(FrameProcessor):
|
|||||||
timeout: float,
|
timeout: float,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(sync=False, **kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self._callback = callback
|
self._callback = callback
|
||||||
self._timeout = timeout
|
self._timeout = timeout
|
||||||
|
|||||||
@@ -144,6 +144,10 @@ class TTSService(AIService):
|
|||||||
# if True, TTSService will push TextFrames and LLMFullResponseEndFrames,
|
# if True, TTSService will push TextFrames and LLMFullResponseEndFrames,
|
||||||
# otherwise subclass must do it
|
# otherwise subclass must do it
|
||||||
push_text_frames: bool = True,
|
push_text_frames: bool = True,
|
||||||
|
# if True, TTSService will push TTSStoppedFrames, otherwise subclass must do it
|
||||||
|
push_stop_frames: bool = False,
|
||||||
|
# if push_stop_frames is True, wait for this idle period before pushing TTSStoppedFrame
|
||||||
|
stop_frame_timeout_s: float = 1.0,
|
||||||
# TTS output sample rate
|
# TTS output sample rate
|
||||||
sample_rate: int = 16000,
|
sample_rate: int = 16000,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -151,9 +155,15 @@ class TTSService(AIService):
|
|||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self._aggregate_sentences: bool = aggregate_sentences
|
self._aggregate_sentences: bool = aggregate_sentences
|
||||||
self._push_text_frames: bool = push_text_frames
|
self._push_text_frames: bool = push_text_frames
|
||||||
self._current_sentence: str = ""
|
self._push_stop_frames: bool = push_stop_frames
|
||||||
|
self._stop_frame_timeout_s: float = stop_frame_timeout_s
|
||||||
self._sample_rate: int = sample_rate
|
self._sample_rate: int = sample_rate
|
||||||
|
|
||||||
|
self._stop_frame_task: Optional[asyncio.Task] = None
|
||||||
|
self._stop_frame_queue: asyncio.Queue = asyncio.Queue()
|
||||||
|
|
||||||
|
self._current_sentence: str = ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sample_rate(self) -> int:
|
def sample_rate(self) -> int:
|
||||||
return self._sample_rate
|
return self._sample_rate
|
||||||
@@ -210,13 +220,72 @@ class TTSService(AIService):
|
|||||||
async def set_role(self, role: str):
|
async def set_role(self, role: str):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def flush_audio(self):
|
||||||
|
pass
|
||||||
|
|
||||||
# Converts the text to audio.
|
# Converts the text to audio.
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:
|
async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def start(self, frame: StartFrame):
|
||||||
|
await super().start(frame)
|
||||||
|
if self._push_stop_frames:
|
||||||
|
self._stop_frame_task = self.get_event_loop().create_task(self._stop_frame_handler())
|
||||||
|
|
||||||
|
async def stop(self, frame: EndFrame):
|
||||||
|
await super().stop(frame)
|
||||||
|
if self._stop_frame_task:
|
||||||
|
self._stop_frame_task.cancel()
|
||||||
|
await self._stop_frame_task
|
||||||
|
self._stop_frame_task = None
|
||||||
|
|
||||||
|
async def cancel(self, frame: CancelFrame):
|
||||||
|
await super().cancel(frame)
|
||||||
|
if self._stop_frame_task:
|
||||||
|
self._stop_frame_task.cancel()
|
||||||
|
await self._stop_frame_task
|
||||||
|
self._stop_frame_task = None
|
||||||
|
|
||||||
async def say(self, text: str):
|
async def say(self, text: str):
|
||||||
await self.process_frame(TextFrame(text=text), FrameDirection.DOWNSTREAM)
|
await self.process_frame(TextFrame(text=text), FrameDirection.DOWNSTREAM)
|
||||||
|
await self.flush_audio()
|
||||||
|
|
||||||
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
|
if isinstance(frame, TextFrame):
|
||||||
|
await self._process_text_frame(frame)
|
||||||
|
elif isinstance(frame, StartInterruptionFrame):
|
||||||
|
await self._handle_interruption(frame, direction)
|
||||||
|
elif isinstance(frame, LLMFullResponseEndFrame) or isinstance(frame, EndFrame):
|
||||||
|
sentence = self._current_sentence
|
||||||
|
self._current_sentence = ""
|
||||||
|
await self._push_tts_frames(sentence)
|
||||||
|
if isinstance(frame, LLMFullResponseEndFrame):
|
||||||
|
if self._push_text_frames:
|
||||||
|
await self.push_frame(frame, direction)
|
||||||
|
else:
|
||||||
|
await self.push_frame(frame, direction)
|
||||||
|
elif isinstance(frame, TTSSpeakFrame):
|
||||||
|
await self._push_tts_frames(frame.text)
|
||||||
|
await self.flush_audio()
|
||||||
|
elif isinstance(frame, TTSUpdateSettingsFrame):
|
||||||
|
await self._update_tts_settings(frame)
|
||||||
|
else:
|
||||||
|
await self.push_frame(frame, direction)
|
||||||
|
|
||||||
|
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
||||||
|
await super().push_frame(frame, direction)
|
||||||
|
|
||||||
|
if self._push_stop_frames and (
|
||||||
|
isinstance(frame, StartInterruptionFrame)
|
||||||
|
or isinstance(frame, TTSStartedFrame)
|
||||||
|
or isinstance(frame, TTSAudioRawFrame)
|
||||||
|
or isinstance(frame, TTSStoppedFrame)
|
||||||
|
):
|
||||||
|
await self._stop_frame_queue.put(frame)
|
||||||
|
|
||||||
async def _handle_interruption(self, frame: StartInterruptionFrame, direction: FrameDirection):
|
async def _handle_interruption(self, frame: StartInterruptionFrame, direction: FrameDirection):
|
||||||
self._current_sentence = ""
|
self._current_sentence = ""
|
||||||
@@ -276,88 +345,6 @@ class TTSService(AIService):
|
|||||||
if frame.role is not None:
|
if frame.role is not None:
|
||||||
await self.set_role(frame.role)
|
await self.set_role(frame.role)
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
|
||||||
await super().process_frame(frame, direction)
|
|
||||||
|
|
||||||
if isinstance(frame, TextFrame):
|
|
||||||
await self._process_text_frame(frame)
|
|
||||||
elif isinstance(frame, StartInterruptionFrame):
|
|
||||||
await self._handle_interruption(frame, direction)
|
|
||||||
elif isinstance(frame, LLMFullResponseEndFrame) or isinstance(frame, EndFrame):
|
|
||||||
sentence = self._current_sentence
|
|
||||||
self._current_sentence = ""
|
|
||||||
await self._push_tts_frames(sentence)
|
|
||||||
if isinstance(frame, LLMFullResponseEndFrame):
|
|
||||||
if self._push_text_frames:
|
|
||||||
await self.push_frame(frame, direction)
|
|
||||||
else:
|
|
||||||
await self.push_frame(frame, direction)
|
|
||||||
elif isinstance(frame, TTSSpeakFrame):
|
|
||||||
await self._push_tts_frames(frame.text)
|
|
||||||
elif isinstance(frame, TTSUpdateSettingsFrame):
|
|
||||||
await self._update_tts_settings(frame)
|
|
||||||
else:
|
|
||||||
await self.push_frame(frame, direction)
|
|
||||||
|
|
||||||
|
|
||||||
class AsyncTTSService(TTSService):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
# if True, TTSService will push TTSStoppedFrames, otherwise subclass must do it
|
|
||||||
push_stop_frames: bool = False,
|
|
||||||
# if push_stop_frames is True, wait for this idle period before pushing TTSStoppedFrame
|
|
||||||
stop_frame_timeout_s: float = 1.0,
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
super().__init__(sync=False, **kwargs)
|
|
||||||
self._push_stop_frames: bool = push_stop_frames
|
|
||||||
self._stop_frame_timeout_s: float = stop_frame_timeout_s
|
|
||||||
self._stop_frame_task: Optional[asyncio.Task] = None
|
|
||||||
self._stop_frame_queue: asyncio.Queue = asyncio.Queue()
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def flush_audio(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def say(self, text: str):
|
|
||||||
await super().say(text)
|
|
||||||
await self.flush_audio()
|
|
||||||
|
|
||||||
async def start(self, frame: StartFrame):
|
|
||||||
await super().start(frame)
|
|
||||||
if self._push_stop_frames:
|
|
||||||
self._stop_frame_task = self.get_event_loop().create_task(self._stop_frame_handler())
|
|
||||||
|
|
||||||
async def stop(self, frame: EndFrame):
|
|
||||||
await super().stop(frame)
|
|
||||||
if self._stop_frame_task:
|
|
||||||
self._stop_frame_task.cancel()
|
|
||||||
await self._stop_frame_task
|
|
||||||
self._stop_frame_task = None
|
|
||||||
|
|
||||||
async def cancel(self, frame: CancelFrame):
|
|
||||||
await super().cancel(frame)
|
|
||||||
if self._stop_frame_task:
|
|
||||||
self._stop_frame_task.cancel()
|
|
||||||
await self._stop_frame_task
|
|
||||||
self._stop_frame_task = None
|
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
|
||||||
await super().process_frame(frame, direction)
|
|
||||||
if isinstance(frame, TTSSpeakFrame):
|
|
||||||
await self.flush_audio()
|
|
||||||
|
|
||||||
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
|
||||||
await super().push_frame(frame, direction)
|
|
||||||
|
|
||||||
if self._push_stop_frames and (
|
|
||||||
isinstance(frame, StartInterruptionFrame)
|
|
||||||
or isinstance(frame, TTSStartedFrame)
|
|
||||||
or isinstance(frame, TTSAudioRawFrame)
|
|
||||||
or isinstance(frame, TTSStoppedFrame)
|
|
||||||
):
|
|
||||||
await self._stop_frame_queue.put(frame)
|
|
||||||
|
|
||||||
async def _stop_frame_handler(self):
|
async def _stop_frame_handler(self):
|
||||||
try:
|
try:
|
||||||
has_started = False
|
has_started = False
|
||||||
@@ -378,7 +365,7 @@ class AsyncTTSService(TTSService):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AsyncWordTTSService(AsyncTTSService):
|
class WordTTSService(TTSService):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self._initial_word_timestamp = -1
|
self._initial_word_timestamp = -1
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ from pipecat.frames.frames import (
|
|||||||
)
|
)
|
||||||
from pipecat.processors.frame_processor import FrameDirection
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
from pipecat.transcriptions.language import Language
|
from pipecat.transcriptions.language import Language
|
||||||
from pipecat.services.ai_services import AsyncWordTTSService, TTSService
|
from pipecat.services.ai_services import WordTTSService, TTSService
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ def language_to_cartesia_language(language: Language) -> str | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class CartesiaTTSService(AsyncWordTTSService):
|
class CartesiaTTSService(WordTTSService):
|
||||||
class InputParams(BaseModel):
|
class InputParams(BaseModel):
|
||||||
encoding: Optional[str] = "pcm_s16le"
|
encoding: Optional[str] = "pcm_s16le"
|
||||||
sample_rate: Optional[int] = 16000
|
sample_rate: Optional[int] = 16000
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from pipecat.frames.frames import (
|
|||||||
TTSStoppedFrame,
|
TTSStoppedFrame,
|
||||||
)
|
)
|
||||||
from pipecat.processors.frame_processor import FrameDirection
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
from pipecat.services.ai_services import AsyncWordTTSService
|
from pipecat.services.ai_services import WordTTSService
|
||||||
|
|
||||||
# See .env.example for ElevenLabs configuration needed
|
# See .env.example for ElevenLabs configuration needed
|
||||||
try:
|
try:
|
||||||
@@ -70,7 +70,7 @@ def calculate_word_times(
|
|||||||
return word_times
|
return word_times
|
||||||
|
|
||||||
|
|
||||||
class ElevenLabsTTSService(AsyncWordTTSService):
|
class ElevenLabsTTSService(WordTTSService):
|
||||||
class InputParams(BaseModel):
|
class InputParams(BaseModel):
|
||||||
language: Optional[str] = None
|
language: Optional[str] = None
|
||||||
output_format: Literal["pcm_16000", "pcm_22050", "pcm_24000", "pcm_44100"] = "pcm_16000"
|
output_format: Literal["pcm_16000", "pcm_22050", "pcm_24000", "pcm_44100"] = "pcm_16000"
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class GladiaSTTService(STTService):
|
|||||||
params: InputParams = InputParams(),
|
params: InputParams = InputParams(),
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(sync=False, **kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self._api_key = api_key
|
self._api_key = api_key
|
||||||
self._url = url
|
self._url = url
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from pipecat.frames.frames import (
|
|||||||
TTSStartedFrame,
|
TTSStartedFrame,
|
||||||
TTSStoppedFrame,
|
TTSStoppedFrame,
|
||||||
)
|
)
|
||||||
from pipecat.services.ai_services import AsyncTTSService
|
from pipecat.services.ai_services import TTSService
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ except ModuleNotFoundError as e:
|
|||||||
raise Exception(f"Missing module: {e}")
|
raise Exception(f"Missing module: {e}")
|
||||||
|
|
||||||
|
|
||||||
class LmntTTSService(AsyncTTSService):
|
class LmntTTSService(TTSService):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -47,7 +47,7 @@ class LmntTTSService(AsyncTTSService):
|
|||||||
):
|
):
|
||||||
# Let TTSService produce TTSStoppedFrames after a short delay of
|
# Let TTSService produce TTSStoppedFrames after a short delay of
|
||||||
# no activity.
|
# no activity.
|
||||||
super().__init__(sync=False, push_stop_frames=True, sample_rate=sample_rate, **kwargs)
|
super().__init__(push_stop_frames=True, sample_rate=sample_rate, **kwargs)
|
||||||
|
|
||||||
self._api_key = api_key
|
self._api_key = api_key
|
||||||
self._voice_id = voice_id
|
self._voice_id = voice_id
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from loguru import logger
|
|||||||
|
|
||||||
class BaseInputTransport(FrameProcessor):
|
class BaseInputTransport(FrameProcessor):
|
||||||
def __init__(self, params: TransportParams, **kwargs):
|
def __init__(self, params: TransportParams, **kwargs):
|
||||||
super().__init__(sync=False, **kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self._params = params
|
self._params = params
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ from pipecat.utils.time import nanoseconds_to_seconds
|
|||||||
|
|
||||||
class BaseOutputTransport(FrameProcessor):
|
class BaseOutputTransport(FrameProcessor):
|
||||||
def __init__(self, params: TransportParams, **kwargs):
|
def __init__(self, params: TransportParams, **kwargs):
|
||||||
super().__init__(sync=False, **kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self._params = params
|
self._params = params
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user