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.1.tar.gz (39.2 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.1-py3-none-any.whl (37.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: podcast_tts-0.1.1.tar.gz
  • Upload date:
  • Size: 39.2 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.1.tar.gz
Algorithm Hash digest
SHA256 de4b0e9b884f8c7bcabf8aebcb0406fc49b56ae6875c1b882ad48fa66ff620af
MD5 8b87d3ab3e190c1b79b9c5a9f768e005
BLAKE2b-256 1a86a194b17154d89f15fe72e2530bf3103fc5137e9da652b58f0f7ca8d4167a

See more details on using hashes here.

File details

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

File metadata

  • Download URL: podcast_tts-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 37.4 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 f8d03a3c2060b65029fc4a293daf6221256e4659f9545f9f345ce5506057ec62
MD5 2e44cb836561ad63af08d62f05287282
BLAKE2b-256 6f186b0acf0c5ef132389717126670606a049a1afbb5546e41b92b104e720d28

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