Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
143df3e
Add Respeecher TTS plugin
mitrushchienkova Aug 22, 2025
3d82cc7
Format code with ruff
mitrushchienkova Aug 22, 2025
7efdca6
Apply code review suggetions
mitrushchienkova Aug 25, 2025
c4b10a9
Add sentence tokenizetion
mitrushchienkova Sep 2, 2025
05f1c53
Define sample_rate as integer instead of list of allowed values
mitrushchienkova Sep 3, 2025
711de2a
Move version from model to base URL
mitrushchienkova Sep 3, 2025
2cf2507
Apply review suggestions
mitrushchienkova Sep 8, 2025
fca99ff
Fix ruff formatting
mitrushchienkova Sep 8, 2025
608c43e
Make Voice a dictionary and update timeout logic for tests
mitrushchienkova Sep 8, 2025
1619a2d
Reuse websocket connection across different requests
mitrushchienkova Sep 9, 2025
d51bb14
Run mypy to type checks
mitrushchienkova Oct 13, 2025
006bd37
Remove pcm_f32le
mitrushchienkova Oct 13, 2025
34d2968
Simplify string replace
mitrushchienkova Oct 13, 2025
a170df6
Run ruff formatter
mitrushchienkova Oct 13, 2025
c5ad4c5
Reset uv.lock
mitrushchienkova Oct 13, 2025
1f8db02
Fix Respeecher rebase fallout
mitrushchienkova May 19, 2026
7c28a1a
Add Ukrainian public model and require voice_id
mitrushchienkova May 19, 2026
263afa6
Don't close shared HTTP session in Respeecher TTS aclose
mitrushchienkova May 19, 2026
ba5e743
Address Devin review on Respeecher TTS streaming
mitrushchienkova May 19, 2026
9da8d28
Address remaining Devin review findings
mitrushchienkova May 19, 2026
5e13e67
Address second Devin review pass on Respeecher streaming
mitrushchienkova May 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions examples/other/text-to-speech/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
# Text-to-speech
# Text-to-Speech Examples

This small example shows how you can generate real-time audio data from text.
These examples demonstrate real-time text-to-speech generation using various TTS plugins with LiveKit.

## Environment Variables

### Plugin API Keys
Set the API key for your chosen plugin.

### LiveKit Connection
For connecting to LiveKit Cloud:
- `LIVEKIT_URL` - Your LiveKit server URL
- `LIVEKIT_API_KEY` - LiveKit API key
- `LIVEKIT_API_SECRET` - LiveKit API secret

## Running Examples

Execute the example to connect to a LiveKit room and stream TTS audio:

```bash
uv run examples/other/text-to-speech/{your_plugin}_tts.py start
```

The agent will join the room and stream synthesized speech to participants.

### Running Locally

Running the examples with `console` mode won't play audio since the examples use `rtc.LocalAudioTrack`, which requires the LiveKit room infrastructure for audio playback. The `LocalAudioTrack` is designed to publish audio streams to LiveKit rooms where they are processed and distributed to participants. Without a room connection, the audio frames are generated but not routed to any playback device.

To test TTS output locally without a LiveKit room, you would need to modify the example file to save the generated audio frames to a WAV file instead of publishing them to a track. The saved WAV file can then be played using any audio player on your system.
1 change: 1 addition & 0 deletions examples/other/text-to-speech/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ livekit-agents>=0.12.18
livekit-plugins-openai>=0.12.2
livekit-plugins-cartesia>=0.4.11
livekit-plugins-elevenlabs>=0.8.1
livekit-plugins-respeecher>=0.0.1
livekit-plugins-speechify>=0.1.0
python-dotenv~=1.0
57 changes: 57 additions & 0 deletions examples/other/text-to-speech/respeecher_tts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import asyncio
import logging

from dotenv import load_dotenv

from livekit import rtc
from livekit.agents import AutoSubscribe, JobContext, WorkerOptions, cli
from livekit.plugins import respeecher

load_dotenv()

logger = logging.getLogger("respeecher-tts-demo")
logger.setLevel(logging.INFO)


async def entrypoint(job: JobContext):
logger.info("starting tts example agent")

tts = respeecher.TTS(voice_id="samantha")

source = rtc.AudioSource(tts.sample_rate, tts.num_channels)
track = rtc.LocalAudioTrack.create_audio_track("agent-mic", source)
options = rtc.TrackPublishOptions()
options.source = rtc.TrackSource.SOURCE_MICROPHONE

await job.connect(auto_subscribe=AutoSubscribe.SUBSCRIBE_NONE)
publication = await job.room.local_participant.publish_track(track, options)
await publication.wait_for_subscription()

async with tts.stream() as stream:

async def _playback_task():
count = 0
async for audio in stream:
count += 1
await source.capture_frame(audio.frame)

task = asyncio.create_task(_playback_task())

text = "Hello from Respeecher! I hope you are having a great day."

# split into two word chunks to simulate LLM streaming
words = text.split()
for i in range(0, len(words), 2):
chunk = " ".join(words[i : i + 2])
if chunk:
logger.info(f'pushing chunk: "{chunk} "')
stream.push_text(chunk + " ")

# Mark end of input segment
stream.flush()
stream.end_input()
await asyncio.gather(task)


if __name__ == "__main__":
cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
27 changes: 27 additions & 0 deletions livekit-plugins/livekit-plugins-respeecher/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Respeecher plugin for LiveKit Agents

Support for [Respeecher](https://respeecher.com/)'s TTS in LiveKit Agents.

More information is available in the docs for the [Respeecher](https://docs.livekit.io/agents/integrations/tts/respeecher/) integration.
Copy link
Copy Markdown
Author

@mitrushchienkova mitrushchienkova Sep 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can we add documentation for the new plugin?


## Installation

```bash
pip install livekit-plugins-respeecher
```

## Pre-requisites

You'll need an API key from Respeecher. It can be set as an environment variable: `RESPEECHER_API_KEY` or passed to the `respeecher.TTS()` constructor.

To get the key, log in to [Respeecher Space](https://space.respeecher.com/).

## Example

To try out the Respeecher plugin, run the example:

```bash
uv run python examples/other/text-to-speech/respeecher_tts.py start
```

Check [`examples/other/text-to-speech/README.md`](../../examples/other/text-to-speech/README.md) for running details.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright 2025 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Respeecher plugin for LiveKit Agents

Voice cloning and synthesis plugin for LiveKit Agents using Respeecher API.
"""

from .tts import TTS, ChunkedStream, SynthesizeStream
from .version import __version__

__all__ = ["TTS", "ChunkedStream", "SynthesizeStream", "__version__"]

from livekit.agents import Plugin

from .log import logger


class RespeecherPlugin(Plugin):
def __init__(self) -> None:
super().__init__(__name__, __version__, __package__, logger)


Plugin.register_plugin(RespeecherPlugin())

# Cleanup docs of unexported modules
_module = dir()
NOT_IN_ALL = [m for m in _module if m not in __all__]

__pdoc__ = {}

for n in NOT_IN_ALL:
__pdoc__[n] = False
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import logging

logger = logging.getLogger("livekit.plugins.respeecher")
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from dataclasses import dataclass
from typing import Any, Literal

TTSModels = Literal[
# Respeecher's public English model
"/public/tts/en-rt",
# Respeecher's public Ukrainian model
"/public/tts/ua-rt",
]

TTSEncoding = Literal["pcm_s16le",]


"""Check https://space.respeecher.com/docs/api/tts/sampling-params-guide for details"""
SamplingParams = dict[str, Any]


@dataclass
class VoiceSettings:
"""Voice settings for Respeecher TTS"""

sampling_params: SamplingParams | None = None


class Voice(dict):
"""Voice model for Respeecher - behaves like a dict with guaranteed `id` and optional `sampling_params`"""

def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
if "id" not in self:
raise ValueError("Voice must have an 'id' field")

@property
def id(self) -> str:
return str(self["id"])

@property
def sampling_params(self) -> SamplingParams | None:
return self.get("sampling_params")
Loading
Loading