murmron PyPI

Python SDK

Official Python client for the murmr TTS API. Async-first with full sync support, powered by httpx and Pydantic v2.

Installation

bash
pip install murmr

Requirements

Python 3.9 or later. Dependencies: httpx (HTTP client) and pydantic v2 (response models).

Client

Two client classes are available. Both support context managers for automatic cleanup.

Sync client
from murmr import MurmrClient

client = MurmrClient(api_key="murmr_sk_live_...")

# Or as a context manager
with MurmrClient(api_key="murmr_sk_live_...") as client:
    wav = client.voices.design(
        input="Hello!",
        voice_description="A warm, friendly voice",
    )
ParameterTypeDescription
api_keyrequiredstrYour murmr API key. Sent as a Bearer token.
base_urlstrOverride the API base URL.
timeoutfloatRequest timeout in seconds (5 minutes default).

Both clients expose three resource namespaces:

client.speech

Generate audio from text

client.voices

Design voices with natural language

client.jobs

Track async batch jobs

client.speech.create()

Submits a batch job and returns an AsyncJobResponse with a job ID. Use create_and_wait() for a synchronous experience.

python
job = client.speech.create(
    input="Hello, world!",
    voice="voice_abc123",
    language="English",
)

# job.id = "job_xyz", job.status = "queued"

# Poll for completion
result = client.jobs.wait_for_completion(job.id)
with open("output.wav", "wb") as f:
    f.write(result.audio_bytes)
ParameterTypeDescription
inputrequiredstrText to synthesize. Max 4,096 characters.
voicerequiredstrSaved voice ID (e.g. "voice_abc123").
languagestrOutput language. 10 supported + "auto".
response_formatstrAudio format: mp3, opus, aac, flac, wav, or pcm.
webhook_urlstrHTTPS URL for async delivery. Returns 202 with job ID.

client.speech.create_and_wait()

Convenience method that submits a batch job and polls until completion. Returns a JobStatus with audio data.

python
result = client.speech.create_and_wait(
    input="Hello, world!",
    voice="voice_abc123",
    language="English",
    response_format="wav",
)

with open("output.wav", "wb") as f:
    f.write(result.audio_bytes)

client.speech.stream()

Stream audio using a saved voice via SSE. Returns a context manager that yields PCM audio chunks.

Streaming audio
with client.speech.stream(
    input="Real-time audio streaming.",
    voice="voice_abc123",
) as stream:
    for chunk in stream:
        pcm = chunk.audio_bytes  # 24kHz mono 16-bit PCM
        # Process: pipe to speaker, save, etc.
        if chunk.done:
            break

client.speech.create_long_form()

Generate audio for text of any length. Handles sentence-boundary chunking, sequential generation, retry with exponential backoff, progress callbacks, and WAV concatenation automatically.

python
result = client.speech.create_long_form(
    input=long_article_text,
    voice="voice_abc123",
    language="English",
    chunk_size=3500,
    silence_between_chunks_ms=400,
    max_retries=3,
    on_progress=lambda current, total, pct: print(f"{pct}%"),
)

with open("article.wav", "wb") as f:
    f.write(result.audio)
print(f"{result.total_chunks} chunks, {result.duration_ms}ms total")
ParameterTypeDescription
inputrequiredstrText of any length. Automatically chunked at sentence boundaries.
voicerequiredstrSaved voice ID.
chunk_sizeintMax characters per chunk. Range: 100-4096.
silence_between_chunks_msintMilliseconds of silence between chunks.
max_retriesintRetry count per chunk. Backoff: 1s, 2s, 4s.
start_from_chunkintResume from a specific chunk index after failure.
on_progressCallableCallback after each chunk. Receives (current, total, percent).

Return Value

python
@dataclass(frozen=True)
class LongFormResult:
    audio: bytes          # WAV audio with silence gaps
    total_chunks: int     # Number of chunks processed
    duration_ms: float    # Total audio duration
    character_count: int  # Total characters processed

Resuming After Failure

If a chunk fails after all retries, a MurmrChunkError is thrown with the chunk index. Use start_from_chunk to resume.

python
from murmr import MurmrChunkError

try:
    result = client.speech.create_long_form(input=text, voice=voice)
except MurmrChunkError as err:
    print(f"Failed at chunk {err.chunk_index}/{err.total_chunks}")

    # Retry from the failed chunk
    result = client.speech.create_long_form(
        input=text,
        voice=voice,
        start_from_chunk=err.chunk_index,
    )

client.voices.design()

Generate audio with a natural-language voice description. Returns WAV audio as bytes.

python
wav = client.voices.design(
    input="Welcome to the show.",
    voice_description="A deep, gravelly male voice, slow and deliberate",
    language="English",
)

with open("output.wav", "wb") as f:
    f.write(wav)
ParameterTypeDescription
inputrequiredstrText to synthesize. Max 4,096 characters.
voice_descriptionrequiredstrNatural language voice description. Max 500 characters.
languagestrOutput language.

client.voices.design_stream()

Stream audio with a voice description via SSE. Returns a context manager yielding PCM audio chunks.

Stream voice design
with client.voices.design_stream(
    input="Streaming with a designed voice.",
    voice_description="A deep, authoritative male news anchor",
) as stream:
    for chunk in stream:
        pcm = chunk.audio_bytes
        if chunk.done:
            break

client.voices.extract_embeddings()

Extract portable voice embeddings from audio. Store the returned prompt_data in your own database and pass it via voice_clone_prompt in any TTS request. See Portable Embeddings.

python
result = client.voices.extract_embeddings(
    audio=open("reference.wav", "rb").read(),
    ref_text="Transcript of the reference audio.",
)

# Use the embedding in a TTS request
with client.speech.stream(
    input="Hello from a portable voice!",
    voice="inline",
    voice_clone_prompt=result.prompt_data,
) as stream:
    for chunk in stream:
        pcm = chunk.audio_bytes
ParameterTypeDescription
audiorequiredbytesWAV audio to extract embeddings from.
ref_textrequiredstrTranscript of the reference audio (improves extraction quality).

Async Jobs

speech.create() always returns a job ID. Use these methods to poll for completion, or pass a webhook_url for async delivery.

client.jobs.get()

python
status = client.jobs.get("job_xyz")
# status.id, status.status ("queued"|"processing"|"completed"|"failed")

client.jobs.wait_for_completion()

Polls until the job reaches completed or failed.

python
result = client.jobs.wait_for_completion(
    "job_xyz",
    poll_interval=3.0,     # default: 3s
    timeout=900.0,         # default: 15 min
)

Note

Raises MurmrError with code='JOB_FAILED' if the job fails, or code='TIMEOUT' if the deadline is exceeded.

Error Handling

python
from murmr import MurmrError, MurmrChunkError

try:
    audio = client.speech.create(input=text, voice=voice)
except MurmrError as err:
    print(err.message)    # "Usage limit exceeded..."
    print(err.status)     # 429
    print(err.code)       # "JOB_FAILED", "TIMEOUT", etc.
ClassWhen RaisedExtra Properties
MurmrErrorAPI errors, validation, timeoutsstatus, code, cause
MurmrChunkErrorLong-form chunk failure after retrieschunk_index, completed_chunks, total_chunks

Response Models

All response types are immutable Pydantic v2 models (frozen=True).

python
from murmr import (
    MurmrClient,
    AsyncMurmrClient,
    MurmrError,
    MurmrChunkError,
)

# Response models (Pydantic v2, frozen)
from murmr._types import (
    AsyncJobResponse,     # id, status, created_at
    JobStatus,            # id, status, audio_base64, audio_bytes
    AudioStreamChunk,     # audio_bytes, done
    LongFormResult,       # audio, total_chunks, duration_ms, character_count
)

Audio Constants

ValueDescription
24,000 HzSample rate (Qwen3-TTS native)
1 channelMono
16-bitPCM bit depth
2 bytes/sampleBytes per sample
44 bytesWAV header size

See Also