Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
78 changes: 78 additions & 0 deletions livekit-plugins/livekit-plugins-lokutor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# LiveKit Plugins Lokutor

**Lokutor** TTS integration for [LiveKit Agents](https://docs.livekit.io/agents/).

Lokutor is a cost-effective Text-to-Speech API that runs on CPU infrastructure, supporting 10 voices (F1-F5, M1-M5) and 30+ languages. See [lokutor.com](https://lokutor.com) for more details.

## Installation

```bash
pip install livekit-plugins-lokutor
```

## Usage

### In an Agent pipeline

```python
from livekit.agents import Agent, AgentSession
from livekit.plugins import lokutor, openai, deepgram, silero

session = AgentSession(
stt=deepgram.STT(),
llm=openai.LLM(),
tts=lokutor.TTS(
api_key="your-api-key",
voice="F1",
language="en",
),
vad=silero.VAD.load(),
)
```

### Standalone TTS

```python
import asyncio
from livekit import rtc
from livekit.plugins import lokutor

async def main():
tts = lokutor.TTS(api_key="...", voice="F1")
async with tts:
stream = tts.stream()
stream.push_text("Hello, world!")
stream.end_input()
async for audio in stream:
print(f"Got audio: {audio.frame.duration:.2f}s")

asyncio.run(main())
```

## Configuration

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `api_key` | `str` | env `LOKUTOR_API_KEY` | Lokutor API key |
| `voice` | `str` | `"F1"` | Voice ID (`F1`-`F5`, `M1`-`M5`) |
| `language` | `str \| None` | `"en"` | Language code (`en`, `es`, `fr`, `pt`, `ko`, etc.) |
| `speed` | `float` | `1.05` | Speed multiplier (0.5–2.0) |
| `steps` | `int` | `5` | Diffusion steps: lower = faster, higher = quality (3–10) |
| `visemes` | `bool` | `False` | Enable lip-sync viseme data |
| `sample_rate` | `int` | `44100` | Audio sample rate in Hz |
| `base_url` | `str` | `"wss://api.lokutor.com"` | API base URL |

## Voices

| ID | Gender |
|----|--------|
| F1–F5 | Female |
| M1–M5 | Male |

## Languages

English, Spanish, French, Portuguese, Korean, and 30+ more. Full list at [docs.lokutor.com](https://docs.lokutor.com/voices-languages-models).

## License

Apache 2.0
39 changes: 39 additions & 0 deletions livekit-plugins/livekit-plugins-lokutor/examples/basic-agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""
Basic LiveKit voice agent using Lokutor TTS.

Run:
export LOKUTOR_API_KEY="your-api-key"
export OPENAI_API_KEY="your-openai-key"
export DEEPGRAM_API_KEY="your-deepgram-key"
python examples/basic-agent.py
"""

from livekit.agents import Agent, AgentSession
from livekit.plugins import deepgram, lokutor, openai, silero


async def main():
session = AgentSession(
stt=deepgram.STT(model="nova-3"),
llm=openai.LLM(model="gpt-4.1-mini"),
tts=lokutor.TTS(
voice="F1",
language="en",
speed=1.05,
steps=5,
),
vad=silero.VAD.load(),
)

agent = Agent(
instructions="You are a helpful voice assistant.",
)

await session.start(agent=agent)
await session.wait_for_end()


if __name__ == "__main__":
import asyncio

asyncio.run(main())
54 changes: 54 additions & 0 deletions livekit-plugins/livekit-plugins-lokutor/examples/standalone-tts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
Standalone TTS example using Lokutor outside of the agent pipeline.

Run:
LOKUTOR_API_KEY="your-api-key" uv run python examples/standalone-tts.py
"""

import asyncio
import os

import aiohttp

from livekit import rtc
from livekit.plugins import lokutor


async def main():
async with aiohttp.ClientSession() as session:
tts = lokutor.TTS(
api_key=os.environ["LOKUTOR_API_KEY"],
voice="F1",
language="en",
speed=1.05,
steps=5,
http_session=session,
)

async with tts:
stream = tts.stream()
stream.push_text(
"Hello! This is a test of the Lokutor TTS integration with LiveKit Agents."
)
stream.end_input()

frames = []
async for audio in stream:
frames.append(audio.frame)

if frames:
combined = rtc.combine_audio_frames(frames)
import wave

with wave.open("output.wav", "wb") as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(combined.sample_rate)
wf.writeframes(combined.data.tobytes())
print(f"Saved output.wav ({combined.sample_rate} Hz, {combined.duration:.2f}s)")
else:
print("No audio generated")


if __name__ == "__main__":
asyncio.run(main())
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Lokutor plugin for LiveKit Agents.

See https://docs.livekit.io/agents/integrations/tts/lokutor/ for more information.
"""

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

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

from livekit.agents import Plugin

from .log import logger


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


Plugin.register_plugin(LokutorPlugin())

_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.lokutor")
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from __future__ import annotations

from typing import Literal

VoiceID = Literal[
"F1",
"F2",
"F3",
"F4",
"F5",
"M1",
"M2",
"M3",
"M4",
"M5",
]

TTSLanguage = Literal[
"en",
"es",
"fr",
"pt",
"ko",
]

TTSModels = Literal["versa-1.0"]

DEFAULT_VOICE_ID: VoiceID = "F1"
DEFAULT_LANGUAGE: TTSLanguage = "en"
DEFAULT_SAMPLE_RATE: int = 44100
Empty file.
Loading
Loading