From e981d2d8fc2b60aad87c5e0c6cb299448dee4b80 Mon Sep 17 00:00:00 2001 From: "Hua.T" <126794161+HuaTNA@users.noreply.github.com> Date: Sun, 11 Oct 2026 17:23:05 -0400 Subject: [PATCH] Forward backend options during streaming speech generation (#1389) (#1390) --- .../txtai/pipeline/audio/texttospeech.py | 9 ++++--- .../testaudio/testtexttospeech.py | 27 ++++++++++++++++++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/python/txtai/pipeline/audio/texttospeech.py b/src/python/txtai/pipeline/audio/texttospeech.py index eaf62db8..d48d9e08 100644 --- a/src/python/txtai/pipeline/audio/texttospeech.py +++ b/src/python/txtai/pipeline/audio/texttospeech.py @@ -92,7 +92,7 @@ class TextToSpeech(Pipeline): # Streaming response if stream: - return self.stream(texts, speaker, encoding) + return self.stream(texts, speaker, encoding, **kwargs) # Transform text to speech results = [self.execute(x, speaker, encoding, **kwargs) for x in texts] @@ -137,7 +137,7 @@ class TextToSpeech(Pipeline): return exists - def stream(self, texts, speaker, encoding): + def stream(self, texts, speaker, encoding, **kwargs): """ Iterates over texts, splits into segments and yields snippets of audio. This method is designed to integrate with streaming LLM generation. @@ -146,6 +146,7 @@ class TextToSpeech(Pipeline): texts: list of input texts speaker: speaker id encoding: audio encoding format + kwargs: additional keyword args Returns: snippets of audio as NumPy arrays or audio bytes depending on encoding parameter @@ -157,11 +158,11 @@ class TextToSpeech(Pipeline): if x == "\n" or (x.strip().endswith(".") and len([y for y in buffer if y]) > 2): data, buffer = "".join(buffer), [] - yield self.execute(data, speaker, encoding) + yield self.execute(data, speaker, encoding, **kwargs) if buffer: data = "".join(buffer) - yield self.execute(data, speaker, encoding) + yield self.execute(data, speaker, encoding, **kwargs) def execute(self, text, speaker, encoding, **kwargs): """ diff --git a/test/python/testpipeline/testaudio/testtexttospeech.py b/test/python/testpipeline/testaudio/testtexttospeech.py index 09f6d70b..548d07e5 100644 --- a/test/python/testpipeline/testaudio/testtexttospeech.py +++ b/test/python/testpipeline/testaudio/testtexttospeech.py @@ -4,7 +4,9 @@ TextToSpeech module tests import unittest -from unittest.mock import patch +from unittest.mock import call, patch + +import numpy as np from txtai.pipeline import TextToSpeech @@ -80,3 +82,26 @@ class TestTextToSpeech(unittest.TestCase): # Check that data is generated self.assertGreater(len(speech), 0) self.assertEqual(rate, 22050) + + @patch("txtai.pipeline.audio.texttospeech.Kokoro") + @patch.object(TextToSpeech, "hasfile") + def testStreamingOptions(self, hasfile, kokoro): + """ + Test backend options reach both flushed and final streaming segments + """ + + hasfile.side_effect = lambda _, name: name in ("model.onnx", "voices.json") + audio = np.array([0.1, -0.1], dtype=np.float32) + kokoro.return_value.return_value = (audio, 24000) + tts = TextToSpeech("neuml/kokoro-int8-onnx", rate=None) + + chunks = list(tts(iter(["One ", "two ", "three.", "Tail"]), stream=True, speaker="af", speed=1.5, transcribe=False)) + + self.assertEqual( + kokoro.return_value.call_args_list, + [call("One two three.", "af", speed=1.5, transcribe=False), call("Tail", "af", speed=1.5, transcribe=False)], + ) + self.assertEqual(len(chunks), 2) + for data, rate in chunks: + np.testing.assert_array_equal(data, audio) + self.assertEqual(rate, 24000)