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.

Release files for podcast-tts 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for podcast-tts 0.1.1
File Size Uploaded
podcast_tts-0.1.1.tar.gz 39.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for podcast-tts 0.1.1
File Interpreter ABI Platform
podcast_tts-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 76.6 kB

Release files / podcast_tts-0.1.1.tar.gz

Download URL podcast_tts-0.1.1.tar.gz
Size 39.2 kB
Tags Source
SHA-256 checksum
How to use checksums
de4b0e9b884f8c7bcabf8aebcb0406fc49b56ae6875c1b882ad48fa66ff620af
BLAKE2b-256 checksum
How to use checksums
1a86a194b17154d89f15fe72e2530bf3103fc5137e9da652b58f0f7ca8d4167a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.12

Release files / podcast_tts-0.1.1-py3-none-any.whl

Download URL podcast_tts-0.1.1-py3-none-any.whl
Size 37.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f8d03a3c2060b65029fc4a293daf6221256e4659f9545f9f345ce5506057ec62
BLAKE2b-256 checksum
How to use checksums
6f186b0acf0c5ef132389717126670606a049a1afbb5546e41b92b104e720d28
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.12

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

0.0.1

2 release 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