Streaming TTS in Python with Speechify

4 min read

How to stream audio from the Speechify TTS API in Python using the SDK and native requests. Covers chunked streaming to disk and piping audio to a player without waiting for the full payload.

Developer Relations · SpeechifyAI Labs

The Node.js TTS post covers the same patterns in TypeScript. This post is the Python companion.

Why streaming matters

Python TTS is going to be mostly server-side. Batch jobs, content pipelines, document-to-audio converters. Waiting for the full payload before writing is fine when you’re generating a few seconds of audio at a time, but once you are synthesizing anything substantial, the length and payload size compounds. Now, the Speechify API supports up to 20,000 characters per request, which is huge. We’re not slow, but at that length non-streaming becomes a process block until the entire clip is generated and downloaded by your service.

Streaming starts to return audio data the moment it is generated, and you can write it to disk as quickly as it arrives. Time-to-first-byte can be dramatically quicker, memory pressure drops, and you can start playing or forwarding audio before synthesis is complete.

Install and setup

uv add speechify-api python-dotenv

Or with pip:

pip install speechify-api python-dotenv

Create a .env file with your key:

SPEECHIFY_API_KEY=your_key_here

Get a key from the Speechify console.

Streaming to disk with the SDK

The speechify-api SDK exposes client.audio.stream(...), which yields raw audio bytes as they come off the wire. You iterate the stream and write each chunk to a file.

import os
from dotenv import load_dotenv
from speechify import Speechify

def main() -> None:
    load_dotenv()
    token = os.environ.get("SPEECHIFY_API_KEY")
    if not token:
        raise SystemExit("Set SPEECHIFY_API_KEY (copy .env.example to .env).")

    client = Speechify(token=token)

    # `audio.stream` yields audio chunks (bytes) as they are synthesized.
    stream = client.audio.stream(
        accept="audio/mpeg",
        input=(
            "Streaming lets you start playing audio before the whole clip is ready. "
            "This sentence is being synthesized and written to disk chunk by chunk."
        ),
        voice_id="geffen_32",
        model="simba-3.2",
    )

    out_file = "output.mp3"
    with open(out_file, "wb") as f:
        for chunk in stream:
            f.write(chunk)

    print(f"Streamed audio to {out_file}")

if __name__ == "__main__":
    main()

client.audio.stream(...) returns an iterator. Each iteration gives you a bytes object. The accept header controls the container: audio/mpeg, audio/ogg, audio/aac, or audio/pcm (returns audio/L16, 24 kHz mono). The full recipe is in the cookbook.

Native flavor: streaming with requests

If you cannot or do not want the SDK, the same result with requests directly:

import os
import requests
from dotenv import load_dotenv

def main() -> None:
    load_dotenv()
    token = os.environ.get("SPEECHIFY_API_KEY")
    if not token:
        raise SystemExit("Set SPEECHIFY_API_KEY (copy .env.example to .env).")

    with requests.post(
        "https://api.speechify.ai/v1/audio/stream",
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
            "Accept": "audio/mpeg",
        },
        json={
            "input": (
                "Streaming lets you start playing audio before the whole clip is ready. "
                "This sentence is being synthesized and written to disk chunk by chunk."
            ),
            "voice_id": "geffen_32",
            "model": "simba-3.2",
        },
        stream=True,
        timeout=60,
    ) as resp:
        resp.raise_for_status()
        out_file = "output.mp3"
        with open(out_file, "wb") as f:
            for chunk in resp.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
        print(f"Streamed audio to {out_file}")

if __name__ == "__main__":
    main()

stream=True on the requests.post call tells requests not to buffer the response body. iter_content(chunk_size=8192) gives you 8 KB chunks as they arrive. The if chunk guard skips keepalive bytes. The native recipe is in the cookbook if you want the full runnable version with uv setup.

Pipe to ffplay for live playback

During development you often just want to hear the output immediately without writing a file. Pipe the stream directly to ffplay:

import os
import subprocess
import requests
from dotenv import load_dotenv

def main() -> None:
    load_dotenv()
    token = os.environ.get("SPEECHIFY_API_KEY")
    if not token:
        raise SystemExit("Set SPEECHIFY_API_KEY")

    player = subprocess.Popen(
        ["ffplay", "-nodisp", "-autoexit", "-i", "pipe:0"],
        stdin=subprocess.PIPE,
        stderr=subprocess.DEVNULL,
    )

    try:
        with requests.post(
            "https://api.speechify.ai/v1/audio/stream",
            headers={
                "Authorization": f"Bearer {token}",
                "Content-Type": "application/json",
                "Accept": "audio/mpeg",
            },
            json={"input": "Hello from the pipe.", "voice_id": "geffen_32", "model": "simba-3.2"},
            stream=True,
            timeout=60,
        ) as resp:
            resp.raise_for_status()
            for chunk in resp.iter_content(chunk_size=8192):
                if chunk:
                    player.stdin.write(chunk)
    finally:
        player.stdin.close()
        player.wait()

if __name__ == "__main__":
    main()

ffplay reads from pipe:0 (stdin), starts playing as soon as it has enough data to decode the first frame, and exits when the stream closes. You need ffmpeg installed (brew install ffmpeg or apt install ffmpeg). This is a development tool, not a production pattern.

What’s next

The Speechify TTS docs cover the full parameter surface: voice selection, SSML, emotion presets, and word-level timestamps. The cookbook has runnable recipes for Python and TypeScript across both SDK and native flavors.