Skip to main content

🎙️ vocalbin

vocalbin — typed, async voice APIs

vocalbin is a small, typed, asynchronous wrapper around OpenAI and Cartesia speech APIs. It validates known model capabilities up front, forwards future model IDs as strings, normalizes responses without discarding useful data, and stays independent of application-specific settings or domain code.

Inhaltsverzeichnis

Installation

uv add vocalbin

Realtime support is optional so the base package does not install a WebSocket stack:

uv add "vocalbin[realtime]"  # custom audio input
uv add "vocalbin[audio]"     # WebSockets plus microphone input
uv add "vocalbin[cartesia]"  # Cartesia TTS plus WebSocket streaming

Set OPENAI_API_KEY in the environment, or pass an API key directly when creating a service. The default path reads the environment through OpenAICredentials:

from vocalbin import OpenAICredentials

credentials = OpenAICredentials()
api_key = credentials.api_key.get_secret_value()

An explicit api_key takes precedence over the environment. An injected AsyncOpenAI client does not load credentials at all.

Speech to text

from pathlib import Path

from vocalbin import OpenAISpeechToText, SpeechToTextRequest


async def transcribe() -> str:
    async with OpenAISpeechToText() as speech_to_text:
        response = await speech_to_text.transcribe(
            SpeechToTextRequest(audio_path=Path("speech.wav"), language="de")
        )
    return response.text

Audio can also be supplied directly as bytes; filename only sets the multipart upload name:

request = SpeechToTextRequest(audio=audio_bytes, filename="speech.wav")

Every request carries the transcript on response.text and the untouched provider payload on response.raw (a dict for JSON-like formats, a str for text, srt and vtt).

Text to speech

from vocalbin import (
    OpenAITextToSpeech,
    TextToSpeechFormat,
    TextToSpeechRequest,
    TextToSpeechVoice,
)


async def synthesize() -> bytes:
    async with OpenAITextToSpeech() as text_to_speech:
        response = await text_to_speech.synthesize(
            TextToSpeechRequest(
                text="Hallo aus vocalbin!",
                voice=TextToSpeechVoice.MARIN,
                response_format=TextToSpeechFormat.MP3,
                instructions="Sprich ruhig und freundlich.",
            )
        )
    return response.audio

response.content_type gives the matching MIME type (e.g. audio/mpeg).

Cartesia text to speech

Cartesia is an alternative text-to-speech provider, grouped under vocalbin.cartesia. Install it with uv add "vocalbin[cartesia]" and set CARTESIA_API_KEY in the environment:

from vocalbin.cartesia import (
    CartesiaTextToSpeech,
    CartesiaTextToSpeechRequest,
    CartesiaWavOutputFormat,
)


async def synthesize(voice_id: str) -> bytes:
    async with CartesiaTextToSpeech() as text_to_speech:
        response = await text_to_speech.synthesize(
            CartesiaTextToSpeechRequest(
                text="Hallo aus vocalbin mit Cartesia!",
                voice_id=voice_id,
                language="de",
                output_format=CartesiaWavOutputFormat(),
            )
        )
    return response.audio

CartesiaTextToSpeech also implements StreamingTextToSpeech. stream() returns one full request as an audio chunk stream; stream_text() takes an async iterable of text chunks and streams matching audio back over the same WebSocket connection, so text can be sent incrementally as it becomes available:

from collections.abc import AsyncIterator

from vocalbin.cartesia import CartesiaTextToSpeechConfig


async def stream_text(voice_id: str, text_chunks: AsyncIterator[str]) -> bytes:
    config = CartesiaTextToSpeechConfig(voice_id=voice_id, language="de")
    audio = bytearray()

    async with CartesiaTextToSpeech() as text_to_speech:
        async for chunk in text_to_speech.stream_text(text_chunks, config):
            audio.extend(chunk)
    return bytes(audio)

WebSocket streaming requires output_format=CartesiaRawOutputFormat() (the default), which returns raw 16-bit PCM audio.

Realtime transcription

Realtime transcription uses gpt-realtime-whisper and streams partial and final transcripts. Its public API is grouped under vocalbin.openai.realtime:

from vocalbin.openai.realtime import (
    OpenAIRealtimeTranscriber,
    RealtimeTranscriptCompleted,
    RealtimeTranscriptDelta,
    RealtimeTranscriptionConfig,
)


async def transcribe_live() -> None:
    async with OpenAIRealtimeTranscriber(
        RealtimeTranscriptionConfig(language="de")
    ) as transcriber:
        async for event in transcriber.stream():
            match event:
                case RealtimeTranscriptDelta(delta=delta):
                    print(delta, end="", flush=True)
                case RealtimeTranscriptCompleted(transcript=transcript):
                    print(f"\n{transcript}")

The default MicrophoneInput sends raw 24 kHz mono PCM16 chunks. Pass an AudioInput implementation or wrap an async byte source with AudioStreamInput from vocalbin.openai.realtime when audio already comes from a media pipeline. flush() manually commits the current transcription buffer.

Realtime translation

Live interpretation uses the dedicated gpt-realtime-translate endpoint. It continuously returns translated 24 kHz PCM16 audio and target-language transcript deltas. Optional source-language transcripts use gpt-realtime-whisper on the same session:

from vocalbin.openai.realtime import (
    OpenAIRealtimeTranslator,
    RealtimeTranslationAudioDelta,
    RealtimeTranslationConfig,
    RealtimeTranslationLanguage,
    RealtimeTranslationTranscriptDelta,
)


async def translate_live() -> None:
    config = RealtimeTranslationConfig(
        target_language=RealtimeTranslationLanguage.ENGLISH
    )
    translated_audio = bytearray()

    async with OpenAIRealtimeTranslator(config) as translator:
        async for event in translator.stream():
            match event:
                case RealtimeTranslationTranscriptDelta(delta=delta):
                    print(delta, end="", flush=True)
                case RealtimeTranslationAudioDelta(audio=audio):
                    translated_audio.extend(audio)

Translation sessions have no assistant turns and do not use response.create. For finite custom inputs, vocalbin sends session.close after the last chunk and keeps draining output until session.closed.

The same realtime namespace also provides audio inputs, providers, shared events, and session enums:

from vocalbin.openai.realtime import (
    AudioInput,
    AudioStreamInput,
    MicrophoneInput,
    OpenAIRealtimeProvider,
    RealtimeError,
    RealtimeNoiseReduction,
    RealtimeSessionConnected,
    RealtimeSessionType,
)

Supported models, voices and formats

Speech to textgpt-4o-transcribe, gpt-4o-mini-transcribe, gpt-4o-transcribe-diarize, whisper-1. Response formats and options are validated per model (for example, timestamp_granularities require whisper-1 with verbose_json, and include=["logprobs"] requires a GPT transcription model with json).

Text to speechgpt-4o-mini-tts, tts-1, tts-1-hd; output formats mp3, opus, aac, flac, wav, pcm. The legacy tts-1/tts-1-hd models accept only the legacy voices and do not support instructions.

Cartesia text to speechsonic-3.5, sonic-3, dated model snapshots, and sonic-latest; output containers raw (16-bit PCM, WAV, µ-law or A-law encoding), wav, and mp3. WebSocket streaming via stream()/stream_text() requires the raw container.

Realtimegpt-realtime-whisper for live transcription and gpt-realtime-translate for live speech-to-speech translation. Translation targets are English, Spanish, Portuguese, French, Japanese, Russian, Chinese, German, Korean, Hindi, Indonesian, Vietnamese, and Italian.

Examples

The examples/ directory holds runnable, integration-testable scripts that exercise every model/voice/format combination and double as documentation. Scripts are grouped by provider. OpenAI's realtime transcription and translation examples and their shared terminal renderer live under examples/openai/realtime/. With a valid OPENAI_API_KEY set:

uv run python examples/openai/text_to_speech.py   # every TTS model, voice and format
uv run python examples/openai/speech_to_text.py   # every STT model and response format
uv run python examples/openai/round_trip.py       # synthesize -> transcribe, self-checking
uv run python examples/openai/shared_client.py    # one AsyncOpenAI client for both services
uv run python examples/openai/realtime/transcription.py
uv run python examples/openai/realtime/translation.py

Cartesia's request-response and WebSocket streaming calls are demonstrated in one script. Set CARTESIA_API_KEY and CARTESIA_VOICE_ID, then run:

uv run --extra cartesia python examples/cartesia/text_to_speech.py

Generated audio and transcripts are written to examples/output/ (git-ignored). speech_to_text.py synthesizes its own sample.wav on first run, so it needs no external audio file.

Bring your own client

Both concrete services accept an existing AsyncOpenAI instance via client=, which lets you share one configured client (custom base_url, timeouts, retries) across both services. Injected clients remain owned by the caller and are not closed by vocalbin:

from openai import AsyncOpenAI

from vocalbin import OpenAISpeechToText, OpenAITextToSpeech

client = AsyncOpenAI()
tts = OpenAITextToSpeech(client=client)
stt = OpenAISpeechToText(client=client)
# ... use both, then close it yourself:
await client.close()

Ports

The provider-independent SpeechToText and TextToSpeech ports are abstract base classes (vocalbin/ports.py); the realtime ports AudioInput, RealtimeProvider, RealtimeTranscription and RealtimeTranslation live in vocalbin/openai/realtime/ports.py. They mark the boundary of the library, so callers can depend on the interface rather than the OpenAI implementation.

Development

uv sync
uv run pytest

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vocalbin-0.3.0.tar.gz (5.7 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vocalbin-0.3.0-py3-none-any.whl (24.7 kB view details)

Uploaded Python 3

File details

Details for the file vocalbin-0.3.0.tar.gz.

File metadata

  • Download URL: vocalbin-0.3.0.tar.gz
  • Upload date:
  • Size: 5.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.2

File hashes

Hashes for vocalbin-0.3.0.tar.gz
Algorithm Hash digest
SHA256 f0ac8d166612c129387cfa009324711108a756b73b638d6753b2f4a23af44993
MD5 39d1631ce50e0e7d96479380212bb13d
BLAKE2b-256 081385be8217aed2fed4a6a1f55cced252fa9209a58d3c00f97ac66de20790de

See more details on using hashes here.

File details

Details for the file vocalbin-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: vocalbin-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 24.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.2

File hashes

Hashes for vocalbin-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 38eb532534078ef8906eacae6e3951f39be3f439f6cb125b86d689bc355d8c02
MD5 deb160b3cc639b724c7b89b04196b977
BLAKE2b-256 0e58fa688cf6888438ef0566d5e9d487789f0203c84dec3b04ebcc812106d57d

See more details on using hashes here.

Release history Release notifications | RSS feed

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.1

2 files

This release

0.3.0 This release

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page