Python SDK
Drive Ojin's face models (Human Presence and Human Portrait) straight from Python. Feed in your TTS audio; get back a synchronized, lip-synced talking-avatar stream.
The ojin-client SDK is the fastest way to add a real-time talking avatar to a Python app or voice agent. You bring the words (any TTS); the SDK streams back a face that speaks them (perfectly in sync) handling buffering, the playback clock, resampling, and barge-in for you.
Requirements
Python 3.10+
An Ojin API key, get yours here
A face-model config ID, a Presence or Portrait configuration from your dashboard
New accounts start with $10 in free credits.
Install
With uv (recommended):
uv add "ojin-client[stv]"Or with pip:
pip install "ojin-client[stv]"The [stv] extra pulls in the speech-to-video helpers (numpy, opencv-python-headless, soxr) that handle resampling, decoding, and the sync math for you.
Authenticate
The client needs your API key and a configuration ID. Keep both out of source. Read them from the environment:
Or let the SDK load and validate them for you. resolve_credentials() reads an optional .env file and raises a clear error if either value is missing:
By default the client connects to wss://models.ojin.ai/realtime.
Never hardcode your API key in source or commit it to version control.
Pick your model
The SDK is identical for both face models; the config_id you pass selects which one runs.
A config_id from…
You get
Choose it when
a Human Presence config
the flagship, fully expressive generative presence, rich expressions and natural movement
quality and expressiveness matter most
a Human Portrait config
a cost-effective, real-time lip-synced talking face
you want a great talking face at the best price
Create configurations in the dashboard. The code below is the same either way; only the config_id changes.
Two clients, one package
OjinSTVClient (high-level)
OjinClient (low-level)
Import
from ojin.stv import OjinSTVClient
from ojin.ojin_client import OjinClient
You give it
TTS audio at any sample rate
16 kHz mono int16 PCM
You get
synced STVAudioFrame + STVVideoFrame on a 25 fps clock
raw protocol messages (JPEG + PCM)
It handles
buffering, playback clock, resampling, barge-in re-sync
just the WebSocket + wire protocol
Most applications want OjinSTVClient. Reach for OjinClient only when you're writing your own playback/sync layer.
Choose how the avatar reaches your viewers. By default OjinSTVClient streams frames back to your process over the WebSocket. If your viewers are in a LiveKit or Daily room, pass webrtc=WebRTCSettings(...) and Ojin publishes the avatar straight into that room instead. See Direct WebRTC (LiveKit & Daily).
Quickstart
Feed the avatar one utterance of TTS audio and consume the synchronized stream it speaks back.
say() is a one-shot helper for start_turn() + send_tts_audio(). For a live agent, call start_turn() once per utterance and stream chunks with send_tts_audio() as your TTS produces them, the user hears the exact audio you sent, lip-synced to the avatar.
The SDK shapes the input for you. Feed audio at whatever cadence your TTS produces, even tiny 40 ms fragments. OjinSTVClient batches the audio automatically; you do not need to collect several seconds before sending it. A pause in your TTS or network can still interrupt playback. It also paces output_stream() to realtime 25 fps with audio and video already in sync. You don't manage input buffering, the playback clock, or frame-dropping yourself. See Optimizing Performance for the recommended initial audio amounts and how to tune them.
Events you'll use
Register handlers with @client.on(STVEvent.X) or client.add_listener(STVEvent.X, cb). Handlers may be sync or async; a failing handler never breaks the playback loop.
SESSION_READY
the session is live and ready for audio
FIRST_FRAME
the first avatar frame is out (frame_type)
WEBRTC_CONNECTED
direct WebRTC only: the avatar has joined your room (participant_id)
BOT_STARTED_SPEAKING
the first speech frame of a turn is played
BOT_STOPPED_SPEAKING
a turn has finished playing
INTERRUPTED
interrupt() took effect (check its return value to know whether a server-side cancel was actually sent)
ERROR
a transport or server error occurred
CLOSED
the session has been torn down
Where to go next
Best practices: working-pipeline patterns for browser output, WebRTC backends, and direct WebRTC
Choose a face model: Human Presence (flagship) or Human Portrait (cost-effective)
Tune latency & stability: Optimizing Performance and the Troubleshooting guide
Full API surface: the complete client reference lives in the
ojin-clientREADME and on PyPIStarter examples:
02-realtime-speech-to-video(live streaming) and01-speech-to-video-mp4-generator(render to file)
Last updated
Was this helpful?