Please ⭐ this repo if you find it useful
Support this project on PayPal
ttsbot is a small, self-hosted Telegram bot that converts text to speech. Send it any text message and it replies with an MP3 file of that text read aloud by one of the Reverso text-to-speech voices. It is written in Python, uses the Telepot Telegram library and the pyttsreverso wrapper, and ships as a Docker image.
Status and limitations — please read before deploying
- Telepot is unmaintained. Its GitHub repository is archived and the last PyPI release (12.7) dates from 2018. It does not support newer Telegram Bot API features, and it may stop working if Telegram changes the API.
- Reverso's text-to-speech endpoint is unofficial and undocumented. The bot calls it through pyttsreverso (last updated in 2021). The endpoint, its parameters or the voice list may have changed or been removed since this project was written, so the bot may no longer produce audio.
- The published Docker image was last pushed in July 2020 and the Dockerfile is based on Ubuntu 18.04, which is past the end of its standard support.
Treat this project as a working example rather than a maintained service.
- Features
- How it works
- Requirements
- Installation
- Configuration
- Supported voices
- Usage
- Security and privacy notes
- Troubleshooting
- Development
- Credits
- Disclaimer
- Contributing
- License
- Replies to every text message with an MP3 file of the text spoken aloud.
- 89 Reverso voices across more than 30 languages and regional variants (see Supported voices).
- Voice, speed and MP3 bitrate set through environment variables.
- A single Python script with no database or state; runs in one Docker container.
sequenceDiagram
participant U as Telegram user
participant T as Telegram Bot API
participant B as ttsbot (telepot)
participant R as Reverso voice service
U->>T: text message
T->>B: update (long polling)
B->>R: GET GetVoiceStream (voice, Base64 text, speed, bitrate)
R-->>B: MP3 audio
B->>T: sendAudio (caption "Voice")
T-->>U: MP3 file
- The bot connects to Telegram with telepot's
MessageLoop(long polling, so no public URL or webhook is needed). - For each incoming message, it passes the message text to
pyttsreverso.ReversoTTS().convert_text(). - pyttsreverso Base64-encodes the text and sends an HTTP
GETrequest to Reverso's unofficial endpointhttps://voice.reverso.net/RestPronunciation.svc/v1/output=json/GetVoiceStream/…with the voice name in the path and the Base64 text, speed and bitrate as query parameters. - The returned audio is saved to a temporary
.mp3file in the working directory, named after the first three words of the message. - The file is sent back to the same chat with
sendAudio(captionVoice) and then deleted.
- A Telegram bot token. Create a bot with @BotFather; see Bots: An introduction for developers.
- Outbound internet access to
api.telegram.organdvoice.reverso.net. - Docker (with Docker Compose), or Python 3 with
pipto run the script directly.
The image is published as techblog/ttsbot. Only
the latest tag exists, and it is built for linux/amd64 only.
This is the repository's docker-compose.yml:
version: "3.7"
services:
ttsbot:
image: techblog/ttsbot
#build: https://github.com/t0mer/ttsbot.git
container_name: ttsbot
restart: always
labels:
- "com.ouroboros.enable=true"
environment:
- BOT_API_KEY=
- TTS_LANG=he-IL-Asaf-Hebrew
- TTS_PITCH=100
- TTS_BITRATE=128k
- PYTHONIOENCODING=utf-8
- LANG=C.UTF-8The file sets - BOT_API_KEY= explicitly (empty), so a .env file next to it is not used
for the token as-is. To keep the token out of the compose file, change that line to read it
from your shell or a .env file:
environment:
- BOT_API_KEY=${BOT_API_KEY}or remove the BOT_API_KEY entry and load it from a separate file:
env_file: .env # contains BOT_API_KEY=<your-bot-token>Choose a voice for TTS_LANG, then start it:
docker compose up -d
docker logs -f ttsbotThe script prints I am listening ... unconditionally at startup, before it has contacted
Telegram, so that line does not mean the bot is connected. Because PYTHONUNBUFFERED is not
set, the line may not appear in docker logs at all. Telegram errors (for example a bad token)
show up as Python tracebacks on stderr.
The com.ouroboros.enable=true label lets Ouroboros
update the container automatically if you run it; it has no effect otherwise.
docker run -d --name ttsbot --restart always \
-e BOT_API_KEY="<your-bot-token>" \
-e TTS_LANG="Sharon-US-English" \
-e TTS_PITCH="100" \
-e TTS_BITRATE="128k" \
techblog/ttsbotgit clone https://github.com/t0mer/ttsbot.git
cd ttsbot
docker build -t ttsbot .Or uncomment the build: line in docker-compose.yml (and remove image:) to let Compose
build from the GitHub repository.
The Dockerfile uses ubuntu:18.04, installs python3-pip, and installs telepot, requests
and pyttsreverso with pip3 (unpinned, so the latest available versions are used). The
container runs python3 /opt/ttsbot/ttsbot.py.
pip3 install telepot requests pyttsreverso
export BOT_API_KEY="<your-bot-token>"
export TTS_LANG="Sharon-US-English"
export TTS_PITCH="100"
export TTS_BITRATE="128k"
python3 ttsbot.pyAll settings are environment variables. The script itself has no built-in defaults: it reads
each variable with os.getenv() and passes the value straight through.
| Variable | Required | Example (compose) | Description |
|---|---|---|---|
BOT_API_KEY |
Yes | <your-bot-token> |
Telegram bot token from @BotFather. |
TTS_LANG |
Recommended | he-IL-Asaf-Hebrew |
Voice to use: a value from the LangCode column in Supported voices. If unset or empty, pyttsreverso falls back to Sharon (US English). An unknown value makes every conversion fail. |
TTS_PITCH |
Recommended | 100 |
Despite its name, this is sent to Reverso as the voice speed (voiceSpeed) parameter; 100 is normal speed. |
TTS_BITRATE |
Recommended | 128k |
MP3 bitrate sent to Reverso (mp3BitRate). |
PYTHONIOENCODING |
No | utf-8 |
Set in the Dockerfile and compose file so non-ASCII text prints correctly. |
LANG |
No | C.UTF-8 |
Set in the Dockerfile and compose file for a UTF-8 locale. |
Always set TTS_LANG, TTS_PITCH and TTS_BITRATE explicitly. The Dockerfile tries to
set defaults (Sharon-US-English, 100, 128k), but it writes them as ENV TTS_LANG = "…"
with spaces around =. Docker parses that as the legacy ENV key value form, so the
value includes the leading = . The published image's config confirms the effective values:
TTS_LANG="= Sharon-US-English", TTS_PITCH="= 100" and TTS_BITRATE="= 128k". All three
are broken (an invalid voice name, speed and bitrate) unless your compose file or
docker run command overrides them.
If TTS_PITCH or TTS_BITRATE is not set at all, the literal string None is sent to Reverso.
The voice, speed and bitrate apply to all chats. There are no per-user settings and no bot commands to change them.
Set TTS_LANG to a value from the LangCode column. This list matches the VoiceName table in
pyttsreverso. Reverso may have removed or renamed voices
since then.
| LangCode | Voice | Gender | Language |
|---|---|---|---|
| Leila-Arabic | Leila22k | Female | Arabic |
| Mehdi-Arabic | Mehdi22k | Male | Arabic |
| Nizar-Arabic | Nizar22k | Male | Arabic |
| Salma-Arabic | Salma22k | Female | Arabic |
| Lisa-Australian-English | Lisa22k | Female | Australian English |
| Tyler-Australian-English | Tyler22k | Male | Australian English |
| Jeroen-Belgian-Dutch | Jeroen22k | Male | Belgian Dutch |
| Sofie-Belgian-Dutch | Sofie22k | Female | Belgian Dutch |
| Zoe-Belgian-Dutch | Zoe22k | Female | Belgian Dutch |
| Alice-BE-Belgian-French | Alice-BE22k | Female | Belgian French |
| Anais-BE-Belgian-French | Anais-BE22k | Female | Belgian French |
| Antoine-BE-Belgian-French | Antoine-BE22k | Male | Belgian French |
| Bruno-BE-Belgian-French | Bruno-BE22k | Male | Belgian French |
| Claire-BE-Belgian-French | Claire-BE22k | Female | Belgian French |
| Julie-BE-Belgian-French | Julie-BE22k | Female | Belgian French |
| Justine-Belgian-French | Justine22k | Female | Belgian French |
| Manon-BE-Belgian-French | Manon-BE22k | Female | Belgian French |
| Margaux-BE-Belgian-French | Margaux-BE22k | Female | Belgian French |
| Marcia-Brazilian | Marcia22k | Female | Brazilian |
| Graham-British | Graham22k | Male | British |
| Lucy-British | Lucy22k | Female | British |
| Peter-British | Peter22k | Male | British |
| QueenElizabeth-British | QueenElizabeth22k | Female | British |
| Rachel-British | Rachel22k | Female | British |
| Louise-Canadian-French | Louise22k | Female | Canadian French |
| Laia-Catalan | Laia22k | Female | Catalan |
| Eliska-Czech | Eliska22k | Female | Czech |
| Mette-Danish | Mette22k | Female | Danish |
| Rasmus-Danish | Rasmus22k | Male | Danish |
| Daan-Dutch | Daan22k | Male | Dutch |
| Femke-Dutch | Femke22k | Female | Dutch |
| Jasmijn-Dutch | Jasmijn22k | Female | Dutch |
| Max-Dutch | Max22k | Male | Dutch |
| Samuel-Finland-Swedish | Samuel22k | Male | Finland Swedish |
| Sanna-Finnish | Sanna22k | Female | Finnish |
| Alice-French | Alice22k | Female | French |
| Anais-French | Anais22k | Female | French |
| Antoine-French | Antoine22k | Male | French |
| Bruno-French | Bruno22k | Male | French |
| Claire-French | Claire22k | Female | French |
| Julie-French | Julie22k | Female | French |
| Manon-French | Manon22k | Female | French |
| Margaux-French | Margaux22k | Female | French |
| Andreas-German | Andreas22k | Male | German |
| Claudia-German | Claudia22k | Female | German |
| Julia-German | Julia22k | Female | German |
| Klaus-German | Klaus22k | Male | German |
| Sarah-German | Sarah22k | Female | German |
| Kal-Gothenburg-Swedish | Kal22k | Male | Gothenburg Swedish |
| Dimitris-Greek | Dimitris22k | Male | Greek |
| he-IL-Asaf-Hebrew | he-IL-Asaf | Male | Hebrew |
| Deepa-Indian-English | Deepa22k | Female | Indian English |
| Chiara-Italian | Chiara22k | Female | Italian |
| Fabiana-Italian | Fabiana22k | Female | Italian |
| Vittorio-Italian | Vittorio22k | Male | Italian |
| Sakura-Japanese | Sakura22k | Female | Japanese |
| Minji-Korean | Minji22k | Female | Korean |
| Lulu-Mandarin-Chinese | Lulu22k | Female | Mandarin Chinese |
| Bente-Norwegian | Bente22k | Female | Norwegian |
| Kari-Norwegian | Kari22k | Female | Norwegian |
| Olav-Norwegian | Olav22k | Male | Norwegian |
| Ania-Polish | Ania22k | Female | Polish |
| Monika-Polish | Monika22k | Female | Polish |
| Celia-Portuguese | Celia22k | Female | Portuguese |
| ro-RO-Andrei-Romanian | ro-RO-Andrei | Male | Romanian |
| Alyona-Russian | Alyona22k | Female | Russian |
| Mia-Scanian | Mia22k | Female | Scanian |
| Antonio-Spanish | Antonio22k | Male | Spanish |
| Ines-Spanish | Ines22k | Female | Spanish |
| Maria-Spanish | Maria22k | Female | Spanish |
| Elin-Swedish | Elin22k | Female | Swedish |
| Emil-Swedish | Emil22k | Male | Swedish |
| Emma-Swedish | Emma22k | Female | Swedish |
| Erik-Swedish | Erik22k | Male | Swedish |
| Ipek-Turkish | Ipek22k | Female | Turkish |
| Heather-US-English | Heather22k | Female | US English |
| Karen-US-English | Karen22k | Female | US English |
| Kenny-US-English | Kenny22k | Male | US English |
| Laura-US-English | Laura22k | Female | US English |
| Micah-US-English | Micah22k | Male | US English |
| Nelly-US-English | Nelly22k | Female | US English |
| Rod-US-English | Rod22k | Male | US English |
| Ryan-US-English | Ryan22k | Male | US English |
| Saul-US-English | Saul22k | Male | US English |
| Sharon-US-English | Sharon22k | Female | US English |
| Tracy-US-English | Tracy22k | Female | US English |
| Will-US-English | Will22k | Male | US English |
| Rodrigo-US-Spanish | Rodrigo22k | Male | US Spanish |
| Rosa-US-Spanish | Rosa22k | Female | US Spanish |
pyttsreverso's own default voice is Sharon-US-English. The repository's docker-compose.yml
uses he-IL-Asaf-Hebrew.
- Start the bot (see Installation).
- Open a chat with your bot in Telegram and send it any text.
- The bot replies with an MP3 file of the text, captioned
Voice.
Notes on behavior (from the code):
- There are no commands. Every text message, including
/start, is converted to speech as-is. - Only text messages are handled. Stickers, photos, voice notes and other non-text messages raise an error in the handler and get no reply.
- The voice is fixed by
TTS_LANG; the bot does not detect the language of the message, so write in the language of the chosen voice. - The text is sent to Reverso Base64-encoded in the URL, so very long messages may fail.
- There is no access control. The bot answers anyone who finds it, and in groups it answers every text message it can see. Keep the bot's username private, or add your own allowlist if that matters to you.
- Everything users send is forwarded to a third party (Reverso) to generate the audio. Don't use the bot for sensitive text.
- Keep your bot token secret. Pass it through an environment variable or a local
.envfile (with- BOT_API_KEY=${BOT_API_KEY}orenv_file: .envin compose, as shown in Installation). Don't put it in a committeddocker-compose.ymlor in a README. If a token was ever committed, revoke it with @BotFather (/revoke) and generate a new one. - The container runs as root on an Ubuntu 18.04 base with unpinned Python packages. Prefer building the image yourself over running an old public image.
| Symptom | Likely cause |
|---|---|
The bot never responds, and docker logs ttsbot shows repeated Python tracebacks from Telegram requests (stderr) |
BOT_API_KEY is missing or invalid. The script does not check it at startup, and its I am listening ... message is printed regardless (and may not appear at all, since output is buffered). |
The bot is online but never replies, with a KeyError in docker logs ttsbot |
TTS_LANG is not an exact LangCode from the table (check spelling and case). This also happens if you rely on the Dockerfile's default (see Configuration). |
A TypeError in the logs and no reply |
The request to Reverso failed (network error); pyttsreverso returns the error as text, which the bot cannot write as audio. |
| The bot replies with a file that doesn't play | Reverso returned an error page instead of audio. Its unofficial endpoint may have changed. |
No reply to messages containing a / (other than a single leading one) in their first three words |
The temporary file name is built from the first three words, and a / makes it an invalid path. |
The whole bot is ttsbot.py (about 50 lines):
| File | Purpose |
|---|---|
ttsbot.py |
The bot: reads the environment variables, runs the telepot MessageLoop, converts each message with pyttsreverso and sends the MP3 back. |
Dockerfile |
Ubuntu 18.04 image that installs the Python dependencies and runs the script. |
docker-compose.yml |
Example deployment using the techblog/ttsbot image. |
License |
Apache License 2.0. |
There are no tests, no CI workflows and no GitHub releases. The Docker Hub image was pushed manually.
- Reverso for the text-to-speech voices.
- Yuval Mejahez for creating pyttsreverso.
- Telepot for the Telegram Bot API client.
This project is not affiliated with, endorsed by or sponsored by Reverso or Telegram. It uses an unofficial, undocumented Reverso endpoint through a third-party library. That endpoint may change or stop working at any time, and using it may violate Reverso's terms of service. You are responsible for checking the terms and using the bot accordingly.
Issues and pull requests are welcome. Keep changes small and describe how you tested them against a real bot token.
This project is licensed under the Apache License 2.0.