[Speechify] Add support for Speechify as a TTS provider#615
Conversation
📝 WalkthroughWalkthroughAdds a Speechify TTS plugin with environment-based API-key configuration, configurable voice/model/language settings, and streaming 24 kHz mono S16 PCM output. Registers the plugin in workspace and optional dependency configuration, documents its API, adds unit and integration tests, and provides a voice-bot example plus a standalone WAV-producing smoke test. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 34555113-4b2e-4f30-8d0e-40eabb37b338
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (15)
CHANGELOG.mdagents-core/pyproject.tomlplugins/speechify/README.mdplugins/speechify/example/.env.exampleplugins/speechify/example/README.mdplugins/speechify/example/main.pyplugins/speechify/example/pyproject.tomlplugins/speechify/example/tts_smoke.pyplugins/speechify/py.typedplugins/speechify/pyproject.tomlplugins/speechify/tests/__init__.pyplugins/speechify/tests/test_tts.pyplugins/speechify/vision_agents/plugins/speechify/__init__.pyplugins/speechify/vision_agents/plugins/speechify/tts.pypyproject.toml
| audio_stream = self.client.audio.stream( | ||
| accept="audio/pcm", | ||
| input=text, | ||
| voice_id=self.voice_id, | ||
| model=self.model, | ||
| **({"language": self.language} if self.language else {}), | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
pip install speechify-api
python -c "
import inspect
from speechify import AsyncSpeechify
print('Is coroutine:', inspect.iscoroutinefunction(AsyncSpeechify().audio.stream))
"Repository: GetStream/Vision-Agents
Length of output: 2237
🏁 Script executed:
#!/bin/bash
python3 - <<'PY'
import inspect
import speechify
from speechify import AsyncSpeechify
print("speechify module:", speechify.__file__)
print("AsyncSpeechify class:", AsyncSpeechify)
# Try to inspect the class attributes without instantiating a client.
audio_attr = getattr(AsyncSpeechify, "audio", None)
print("AsyncSpeechify.audio on class:", type(audio_attr), audio_attr)
# Locate source files for the package.
import speechify.client as client_mod
print("client module:", client_mod.__file__)
# Print relevant lines around AsyncSpeechify and any stream method definitions.
import pathlib, re
paths = [
pathlib.Path(client_mod.__file__),
]
for path in paths:
text = path.read_text()
for needle in ["class AsyncSpeechify", "async def stream", "def stream"]:
if needle in text:
print(f"FOUND {needle} in {path}")
PYRepository: GetStream/Vision-Agents
Length of output: 589
🏁 Script executed:
#!/bin/bash
python3 - <<'PY'
import pathlib
import inspect
import speechify.client as client_mod
path = pathlib.Path(client_mod.__file__)
text = path.read_text().splitlines()
# Show the AsyncSpeechify class and nearby stream-related methods.
needles = ("class AsyncSpeechify", "async def stream", "def stream")
for i, line in enumerate(text, start=1):
if any(n in line for n in needles):
start = max(1, i - 8)
end = min(len(text), i + 20)
print(f"\n--- {path}:{start}-{end} ---")
for j in range(start, end + 1):
print(f"{j:4d}: {text[j-1]}")
PYRepository: GetStream/Vision-Agents
Length of output: 1461
🏁 Script executed:
#!/bin/bash
python3 - <<'PY'
from pathlib import Path
import speechify.client as client_mod
path = Path(client_mod.__file__)
print(path)
for i, line in enumerate(path.read_text().splitlines(), start=1):
if "async def stream" in line or "def stream" in line or "class AsyncSpeechify" in line:
print(f"{i}: {line}")
PYRepository: GetStream/Vision-Agents
Length of output: 259
🏁 Script executed:
#!/bin/bash
python3 - <<'PY'
from pathlib import Path
import speechify
import speechify.client as client_mod
paths = [Path(speechify.__file__), Path(client_mod.__file__)]
for path in paths:
print(f"\nFILE: {path}")
text = path.read_text().splitlines()
for i, line in enumerate(text, start=1):
if "class AsyncSpeechify" in line or "async def stream" in line or "def stream" in line:
start = max(1, i - 6)
end = min(len(text), i + 18)
for j in range(start, end + 1):
print(f"{j:4d}: {text[j-1]}")
print()
PYRepository: GetStream/Vision-Agents
Length of output: 1213
🌐 Web query:
speechify-api Python SDK AsyncSpeechify audio stream await
💡 Result:
To perform asynchronous audio streaming using the Speechify Python SDK, you should use the AsyncSpeechify client class [1][2][3]. The SDK supports non-blocking operations, allowing you to iterate over audio chunks as they are generated by the API [4]. ### Async Streaming Implementation The SDK provides an asynchronous interface for streaming. You can use an async for loop to process the audio chunks returned by the client.audio.stream method [4]. Example usage: python import asyncio from speechify import AsyncSpeechify async def stream_audio(): # Initialize the async client client = AsyncSpeechify(token="YOUR_API_KEY") # Open the stream stream = await client.audio.stream( accept="audio/mpeg", input="Hello! This is a streaming test using the Speechify Python SDK.", voice_id="george" ) # Iterate over incoming audio chunks async for chunk in stream: # Process each binary chunk (e.g., write to a file or send to a buffer) print(f"Received chunk of size: {len(chunk)} bytes") # Run the async function asyncio.run(stream_audio()) ### Key Considerations * Async Client: Always instantiate AsyncSpeechify instead of the synchronous Speechify client to access non-blocking methods [1][2]. * Custom HTTP Client: If you need advanced control (e.g., custom timeouts or proxy settings), you can pass a httpx.AsyncClient() instance to the httpx_client parameter during the initialization of AsyncSpeechify [1][4]. * Resource Management: Ensure you are using asyncio or a compatible event loop runner to execute these calls [1][2]. * Cookbook Resources: For more complex patterns, including voice cloning or saving streams directly to disk, refer to the official Speechify API Cookbook on GitHub [5].
Citations:
- 1: https://pypi.org/project/speechify-api/
- 2: https://github.com/SpeechifyInc/speechify-api-sdk-python
- 3: https://pypi.org/project/speechify-api/3.0.1/
- 4: https://context7.com/speechifyinc/speechify-api-sdk-python/llms.txt
- 5: https://github.com/SpeechifyInc/speechify-api-cookbook
Await self.client.audio.stream(...). audio_stream is a coroutine here, so PcmData.from_response(...) will fail.
| license = "MIT" | ||
| dependencies = [ | ||
| "vision-agents", | ||
| "speechify-api>=1.2.4,<2", |
There was a problem hiding this comment.
| "speechify-api>=1.2.4,<2", | |
| "speechify-api==3.0.1", |
recommend jumping to 3.0.1 but i did break the wire when going to v3 (sorry!)
| raise ValueError("SPEECHIFY_API_KEY env var or api_key parameter required") | ||
|
|
||
| self.client = ( | ||
| client if client is not None else AsyncSpeechify(api_key=self.api_key) |
There was a problem hiding this comment.
| client if client is not None else AsyncSpeechify(api_key=self.api_key) | |
| client if client is not None else AsyncSpeechify(token=self.api_key) |
i believe this is the breaking bit (again, sorry!)
What's implemented?
speechify.TTS()simba-3.2geffen_32as the default, but others, likebeatrice_32, etc)speechify.TTS(model="simba-multilingual", language="de-DE")Demo
speechify-demo.mp4