TTSServices: for now just specify a single text aggregator
This commit is contained in:
@@ -21,8 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Added new `BaseTextAggregator`. Text aggregators are used by the TTS service
|
||||
to aggregate LLM tokens and decide when the aggregated text should be pushed
|
||||
to the TTS service. They also allow for the text to be manipulated while it's
|
||||
being aggregated. Multiple text aggregators can be passed with
|
||||
`text_aggregators` to the TTS service.
|
||||
being aggregated. A text aggregator can be passed via `text_aggregator` to the
|
||||
TTS service.
|
||||
|
||||
- Added new `UltravoxSTTService`.
|
||||
(see https://github.com/fixie-ai/ultravox)
|
||||
|
||||
@@ -119,7 +119,7 @@ async def main():
|
||||
tts = CartesiaTTSService(
|
||||
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||
voice_id=VOICE_IDS["narrator"],
|
||||
text_aggregators=[pattern_aggregator],
|
||||
text_aggregator=pattern_aggregator,
|
||||
)
|
||||
|
||||
# Initialize LLM
|
||||
|
||||
@@ -239,7 +239,7 @@ class TTSService(AIService):
|
||||
# TTS output sample rate
|
||||
sample_rate: Optional[int] = None,
|
||||
# Text aggregator to aggregate incoming tokens and decide when to push to the TTS.
|
||||
text_aggregators: Sequence[BaseTextAggregator] = [],
|
||||
text_aggregator: Optional[BaseTextAggregator] = None,
|
||||
# Text filter executed after text has been aggregated.
|
||||
text_filters: Sequence[BaseTextFilter] = [],
|
||||
text_filter: Optional[BaseTextFilter] = None,
|
||||
@@ -257,10 +257,7 @@ class TTSService(AIService):
|
||||
self._sample_rate = 0
|
||||
self._voice_id: str = ""
|
||||
self._settings: Dict[str, Any] = {}
|
||||
# Ensure there's at least one text aggregator.
|
||||
self._text_aggregators: Sequence[BaseTextAggregator] = text_aggregators or [
|
||||
SimpleTextAggregator()
|
||||
]
|
||||
self._text_aggregator: BaseTextAggregator = text_aggregator or SimpleTextAggregator()
|
||||
self._text_filters: Sequence[BaseTextFilter] = text_filters
|
||||
if text_filter:
|
||||
import warnings
|
||||
@@ -358,8 +355,8 @@ class TTSService(AIService):
|
||||
# pause to avoid audio overlapping.
|
||||
await self._maybe_pause_frame_processing()
|
||||
|
||||
sentence = self._text_aggregators[-1].text
|
||||
self._reset_aggregators()
|
||||
sentence = self._text_aggregator.text
|
||||
self._text_aggregator.reset()
|
||||
self._processing_text = False
|
||||
await self._push_tts_frames(sentence)
|
||||
if isinstance(frame, LLMFullResponseEndFrame):
|
||||
@@ -405,8 +402,7 @@ class TTSService(AIService):
|
||||
|
||||
async def _handle_interruption(self, frame: StartInterruptionFrame, direction: FrameDirection):
|
||||
self._processing_text = False
|
||||
for aggregator in self._text_aggregators:
|
||||
aggregator.handle_interruption()
|
||||
self._text_aggregator.handle_interruption()
|
||||
for filter in self._text_filters:
|
||||
filter.handle_interruption()
|
||||
|
||||
@@ -418,25 +414,12 @@ class TTSService(AIService):
|
||||
if self._pause_frame_processing:
|
||||
await self.resume_processing_frames()
|
||||
|
||||
def _reset_aggregators(self):
|
||||
for aggregator in self._text_aggregators:
|
||||
aggregator.reset()
|
||||
|
||||
async def _process_text_frame(self, frame: TextFrame):
|
||||
text: Optional[str] = None
|
||||
if not self._aggregate_sentences:
|
||||
text = frame.text
|
||||
else:
|
||||
current_text = frame.text
|
||||
|
||||
# Process all aggregators except the last one.
|
||||
for aggregator in self._text_aggregators[:-1]:
|
||||
aggregator.aggregate(current_text)
|
||||
current_text = aggregator.text
|
||||
|
||||
# The last aggregator decides whether we are sending text to the
|
||||
# TTS or not.
|
||||
text = self._text_aggregators[-1].aggregate(current_text)
|
||||
text = self._text_aggregator.aggregate(frame.text)
|
||||
|
||||
if text:
|
||||
await self._push_tts_frames(text)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import base64
|
||||
import json
|
||||
import uuid
|
||||
from typing import AsyncGenerator, List, Optional, Sequence, Union
|
||||
from typing import AsyncGenerator, List, Optional, Union
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
@@ -91,7 +91,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
|
||||
encoding: str = "pcm_s16le",
|
||||
container: str = "raw",
|
||||
params: InputParams = InputParams(),
|
||||
text_aggregators: Sequence[BaseTextAggregator] = [],
|
||||
text_aggregator: Optional[BaseTextAggregator] = None,
|
||||
**kwargs,
|
||||
):
|
||||
# Aggregating sentences still gives cleaner-sounding results and fewer
|
||||
@@ -109,7 +109,7 @@ class CartesiaTTSService(AudioContextWordTTSService):
|
||||
push_text_frames=False,
|
||||
pause_frame_processing=True,
|
||||
sample_rate=sample_rate,
|
||||
text_aggregators=text_aggregators or [SkipTagsAggregator([("<spell>", "</spell>")])],
|
||||
text_aggregator=text_aggregator or SkipTagsAggregator([("<spell>", "</spell>")]),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import base64
|
||||
import json
|
||||
import uuid
|
||||
from typing import AsyncGenerator, Optional, Sequence
|
||||
from typing import AsyncGenerator, Optional
|
||||
|
||||
import aiohttp
|
||||
from loguru import logger
|
||||
@@ -80,7 +80,7 @@ class RimeTTSService(AudioContextWordTTSService):
|
||||
model: str = "mistv2",
|
||||
sample_rate: Optional[int] = None,
|
||||
params: InputParams = InputParams(),
|
||||
text_aggregators: Sequence[BaseTextAggregator] = [],
|
||||
text_aggregator: Optional[BaseTextAggregator] = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize Rime TTS service.
|
||||
@@ -100,7 +100,7 @@ class RimeTTSService(AudioContextWordTTSService):
|
||||
push_stop_frames=True,
|
||||
pause_frame_processing=True,
|
||||
sample_rate=sample_rate,
|
||||
text_aggregators=text_aggregators or [SkipTagsAggregator([("spell(", ")")])],
|
||||
text_aggregator=text_aggregator or SkipTagsAggregator([("spell(", ")")]),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user