Skip to main content

podcast_tts

PyPI version Python versions License: MIT CI

Turn a script into a natural, multi-speaker podcast or dialogue - with background music, stereo panning, and subtitles - in a few lines of Python.

  • Multi-speaker dialogues with per-line left/right/both channel control.
  • English and Spanish (plus more), thanks to pluggable TTS engines.
  • Background music with automatic fade-in/out and ducking under speech.
  • Voice cloning & emotion (via the Chatterbox engine) or unlimited random voices (via ChatTTS).
  • Subtitles (.srt / .vtt) generated automatically, perfectly timed to the audio.
  • WAV or MP3 output, plus a simple podcast-tts command-line tool.

Listen to an example

https://github.com/user-attachments/assets/baf6aa80-2d8f-4a2c-8159-efa9d9596693


Install

# 1. System audio tools (pick your OS)
brew install ffmpeg           # macOS  (Linux: apt-get install ffmpeg)

# 2. The library + the engine you want (see "Which engine?" below)
pip install "podcast_tts[chattts]"      # English, unlimited random voices (default)
pip install "podcast_tts[chatterbox]"   # English + Spanish, voice cloning, emotion
pip install "podcast_tts[kokoro]"       # Fast & light, English + Spanish presets
pip install "podcast_tts[all]"          # Everything

Kokoro also needs espeak-ng (brew install espeak-ng / apt-get install espeak-ng).

Which engine?

Pick one based on what matters most to you:

Engine Languages Voices Emotion Speed Best for
chattts (default) English, Chinese Unlimited random + your saved profiles [laugh], breaks Medium English podcasts, spinning up many distinct voices
chatterbox 23 langs incl. Spanish Clone any voice from ~10s audio Yes (dial) Slower (GPU recommended) Spanish, cloning a real host, expressive delivery
kokoro 8 langs incl. Spanish 54 presets + blends No Fastest (CPU-friendly) Quick, clean narration; low-resource machines

You choose the engine when you create PodcastTTS(engine=...).


Quickstart

import asyncio
from podcast_tts import PodcastTTS

async def main():
    tts = PodcastTTS(engine="chattts")            # English default
    await tts.generate_tts(
        text="Hello! Welcome to our podcast.",
        speaker="male1",                           # a premade voice
        filename="hello.wav",
    )

asyncio.run(main())

A two-person dialogue

import asyncio
from podcast_tts import PodcastTTS

async def main():
    tts = PodcastTTS(engine="chattts")
    dialogue = [
        {"male1":   ["Welcome to the show!", "left"]},
        {"female2": ["Thanks for having me. [laugh]", "right"]},
        {"male1":   ["Today we talk about open source.", "left"]},
    ]
    await tts.generate_dialog(dialogue, filename="dialogue.mp3", subtitles=True)
    # -> dialogue.mp3 + dialogue.srt

asyncio.run(main())

Spanish

Use an engine that speaks Spanish (chatterbox or kokoro) and set the language.

import asyncio
from podcast_tts import PodcastTTS

async def main():
    tts = PodcastTTS(engine="kokoro", language="es")
    await tts.generate_tts(
        text="Hola, bienvenidos al pódcast. Hoy hablamos de inteligencia artificial.",
        speaker="ef_dora",                         # a Spanish Kokoro voice
        filename="hola.wav",
    )

asyncio.run(main())

You can even mix languages in one dialogue by setting the language per line:

dialogue = [
    {"Host":  ["Welcome! Today we go bilingual."]},
    {"Guest": ["Hola, gracias por la invitación.", "left", {"language": "es"}]},
]
await tts.generate_dialog(dialogue, filename="bilingual.mp3")

Podcast with background music

music = [file_or_url, full_volume_seconds, fade_seconds, volume_under_speech]

await tts.generate_podcast(
    texts=dialogue,
    music=["intro.mp3", 10, 3, 0.3],   # or a https:// URL (downloaded & cached)
    filename="episode.mp3",
    subtitles=True,
)

The music plays at full volume, fades down under the dialogue, then fades back up and out.

Clone a voice (Chatterbox)

Drop a clean 10-30s clip in your voices/ folder named after the speaker, or register it in code:

tts = PodcastTTS(engine="chatterbox", language="es")
tts.clone_voice("Ana", "samples/ana_reference.wav")   # now "Ana" sounds like the clip

await tts.generate_tts(
    "Hola, soy Ana y este es mi pódcast.",
    speaker="Ana",
    filename="ana.wav",
    emotion=0.7,          # 0.0 calm ... 1.0 dramatic
)

Command line

podcast-tts say "Hello there" --speaker male1 -o hello.wav
podcast-tts dialog script.json -o show.mp3 --subtitles srt
podcast-tts dialog script.json -o show.mp3 --engine kokoro --language es \
    --music intro.mp3 10 3 0.3

script.json is just the dialogue list:

[
  {"male1": ["Welcome to the show!", "both"]},
  {"female2": ["Hola a todos.", "left", {"language": "es"}]}
]

Web demo

Prefer clicking to coding? Launch a small local web UI:

pip install "podcast_tts[demo,chattts]"   # the demo + one engine
podcast-tts-demo                            # opens http://127.0.0.1:7860

Two tabs: synthesize a single line, or paste a dialogue script and render a full podcast (with optional background music and a downloadable subtitle file).


Voices

  • ChatTTS ships three ready-to-use profiles: male1, male2, female2. Any new name you use is generated once and saved to voices/<name>.txt so it stays consistent.

  • Chatterbox uses reference clips: put voices/<name>.wav (or call clone_voice). Without a reference it uses its default voice.

  • Kokoro uses preset ids (e.g. af_heart, ef_dora, em_alex). Blend new ones:

    tts.engine.blend_voices("myvoice", {"ef_dora": 0.6, "em_alex": 0.4})
    

Dialogue entry format

Each turn is a one-key dict: {"SpeakerName": [text, channel?, options?]}

  • text (str, required)
  • channel (str, optional): "left", "right", or "both" (default)
  • options (dict, optional): {"language": "es", "emotion": 0.7}

API at a glance

tts = PodcastTTS(engine="chattts", language="en", speed=5, device=None)

await tts.generate_tts(text, speaker, filename="out.wav", channel="both",
                       language=None, emotion=None)
await tts.generate_dialog(texts, filename="dialog.wav", pause_duration=0.5,
                          normalize=True, subtitles=False, subtitle_format="srt",
                          language=None)
await tts.generate_podcast(texts, music, filename="podcast.wav", pause_duration=0.5,
                           normalize=True, subtitles=False, subtitle_format="srt",
                           language=None)

Upgrading from 0.0.x

The old API still works: from podcast_tts import PodcastTTS, plus generate_tts, generate_dialog, and generate_podcast keep the same required arguments. New in 0.1.0: the engine/language/emotion options, Spanish support, subtitles, and the CLI. The default engine remains ChatTTS, so existing scripts behave as before.

Development

pip install -e ".[dev]"
ruff check podcast_tts tests
pytest -q

Releasing to PyPI

Releases are automated by .github/workflows/release.yml: push a version tag and CI builds and publishes the package.

# 1. Bump the version in pyproject.toml (e.g. 0.1.0 -> 0.1.1)
# 2. Tag and push (the tag must match the pyproject version):
git tag v0.1.1
git push origin v0.1.1

The workflow checks the tag matches the version, builds the sdist/wheel, and uploads with skip-existing (so re-runs never clobber an existing release).

Authentication: the publish step uses a PYPI_API_TOKEN repository secret (Settings → Secrets and variables → Actions). Create a PyPI API token scoped to this project and store it there. To switch to trusted publishing instead, drop the password: line from the publish step, add permissions: id-token: write, and register the publisher on PyPI.

Contributing

Issues and pull requests are welcome on GitHub.

License

MIT - see LICENSE. Note the underlying engines have their own model licenses (ChatTTS, Chatterbox, Kokoro); review them for commercial use.

Download files

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

Source Distribution

podcast_tts-0.1.0.tar.gz (38.6 kB view details)

Uploaded Source

Built Distribution

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

podcast_tts-0.1.0-py3-none-any.whl (36.8 kB view details)

Uploaded Python 3

File details

Details for the file podcast_tts-0.1.0.tar.gz.

File metadata

  • Download URL: podcast_tts-0.1.0.tar.gz
  • Upload date:
  • Size: 38.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for podcast_tts-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b899842ccdf49982f0325b9a6470f42a8699f40a6dce1e53e7933e095f3bcdb8
MD5 b668b9c658249f12ae1399d57d341533
BLAKE2b-256 a17febb8c4e3e3f25710ece4816318a33a6c96c0eaef454fb673a7fd6cfe0775

See more details on using hashes here.

File details

Details for the file podcast_tts-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: podcast_tts-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 36.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for podcast_tts-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2fd45f5b10ffc0488537c0546086b26d4469f8fc6900c6a80d7558b541b3b8e0
MD5 b3d82911b15ce2b96fde15c00fde5781
BLAKE2b-256 14f09758d99a18b9b932d257d9c728e27b4b7e43955eb6da0a2b9a22aafa8ead

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page