Discord bot text to speech with Piper
29th November 2025Piper is a fast TTS engine that’s performant enough to run on the cheapest Hetzner VPS. It comes with a wide array of available voices offering a good selection of size and speed tradeoffs.
Conveniently, it also ships with a web server, so you can load the model once and then get TTS responses with low latency. This makes it perfect for using it as a TTS server for Discord bots.
Below is a sample Dockerfile to expose Piper on port 5000.
FROM python:3.11-slim
RUN pip install piper-tts[http]
RUN python3 -m piper.download_voices en_US-lessac-high
EXPOSE 5000
CMD ["python3", "-m", "piper.http_server", "-m", "en_US-lessac-high", "--host", "0.0.0.0", "--port", "5000"]
To reduce the delay between requesting audio and playing it with a bot, you can use a Passthrough.
The idea is simple: instead of requesting the full audio clip and only then have your bot play it, make a stream of audio and have the bot continuously play it as it keeps coming in.
function ttsStream(text: string) {
const passThrough = new PassThrough();
const request = http.request("http://tts-server:5000", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
}, (res) => {
if (res.statusCode !== 200) {
passThrough.destroy(new Error(`TTS request failed: ${res.statusCode}`));
return;
}
res.pipe(passThrough);
},
);
request.on("error", (error) => {
console.error(error);
passThrough.destroy(error);
});
request.write(JSON.stringify({ text }));
request.end();
return passThrough;
}
Following that, playing audio into a VoiceBasedChannel using discord.js is straightforward, error handling etc omitted for brevity:
const player = createAudioPlayer();
const connection = joinVoiceChannel({
channelId: channel.id,
guildId: guild.id,
adapterCreator: guild.voiceAdapterCreator,
});
connection.subscribe(player);
const stream = ttsStream(text);
const resource = createAudioResource(stream);
await entersState(connection, VoiceConnectionStatus.Ready, 5_000);
player.play(resource);