Time & Station ID Announcement (TOTH) in AzuraCast using Text-To-Speech
#azuracast #radio #linux #picotts
Many of us want (or are required to have) a periodic time and station ID announcement for our internet radio stations. We could make sound files for every hour and minute and write crazy long scripts to play those at the top of the hour (TOTH), but there is a much easier way:
How about having a simple MP3 file that you can use in your playlists any time and that always has the correct time and station ID?
I will show you how
We will use PicoTTS, a very lightweight and local Text-to-Speech engine, and sox to generate the MP3 file. Using a crontab job, we will regenerate the file every minute, so the time announcement is always current, whenever you play it.
PicoTTS supports British English, American English, German, French, Italian and Spanish. It runs offline and thus can be used on servers with no or restricted Internet access. With a little optimization in sox, the synthesized voices don’t sound too bad. They aren’t as good as online Google TTS using gTTS, but much better than eSpeak, Festival and pyttsx3, which I also tested.
All this will run in the host (so not within AzuraCast’s docker containers), and mount a folder into the station’s media folder that in turn contains the announcement MP3 files, ready to use.
You’ll need
- A working AzuraCast station in a virtual machine or cloud server, running under a Debian derivative, say an Ubuntu 22.04 server.
- SSH access to your server, the bash shell, and the possibility to install software and modify crontab entries on your server.
Prepare the host system
Install PicoTTS and sox
SSH into your server and become root (sudo su
). Then install the following:
apt install libttspico-utils sox libsox-fmt-mp3
Install any needed server language packs
Note: For any other language than English, the correct system locale must be installed on your server and specified in the script! Example installation for the German language pack:
apt install language-pack-de
Install and modify the scripts
We will use small bash scripts for each language desired (PicoTTS supports British English, American English, German, French, Italian and Spanish).
Here are sample scripts saytime-en
and saytime-de
for British English and German, respectively. Modify these as desired, adapt the station ID message, and don’t forget to set the correct time zone for your station. If desired, you can also modify the silence padding (default: 3s of silence at beginning and end).
You can use nano as an editor on the server. Save the scripts into the /usr/local/bin
folder.
saytime-en
#!/bin/bash
# saytime-en
# 2015-07-16 Moonbase
# 2023-03-09 Moonbase (update to PicoTTS; pico2wave & sox must be installed)
# Create /tmp/time.en-GB.wav for Liquidsoap to announce current time.
# Put this script into your ~/bin folder (or ~/.local/bin, or /usr/local/bin).
# 2023-05-28 Moonbase Adapted to AzuraCast
# On a Ubuntu Server, install required modules as follows:
# sudo apt install libttspico-utils sox libsox-fmt-mp3
# Install this file into /usr/local/bin and use root's crontab to run every minute.
# 2023-05-29 Moonbase Simplified silence generation
#
# crontab entry, to keep the file current, like so:
# */1 * * * * /usr/local/bin/saytime-en > /dev/null
# Possible output languages (might require different sox post-processing):
# de-DE - German
# en-GB - British Englisch
# en-US - US Englisch
# fr-FR - French
# it-IT - Italian
# es-ES - Spanish
lang="en-GB" # language for PicoTTS
locale="en_GB.utf8" # (installed!) OS locale
tz='Europe/Berlin' # desired timezone
# In case you wish to copy the file from /tmp into your media folder, specify it here.
# Use a trailing slash for folder names ("/folder/").
# You can also specify a full MP3 file name ("/folder/time-signal.mp3")
media_folder="/var/azuracast/time/"
# silence to add before/after the spoken text, in seconds; empty string for none
pad="pad 3 3"
# English
AMPM="$(LC_ALL=$locale TZ=$tz date +'%p')"
AMPM="${AMPM^^}" # uppercase it
TIME="$(LC_ALL=$locale TZ=$tz date +'%l:%M ')${AMPM}"
TOTH="$(LC_ALL=$locale TZ=$tz date +"%l o'clock ")${AMPM}"
MIN="$(LC_ALL=$locale TZ=$tz date +'%-M')"
DAY="$(LC_ALL=$locale TZ=$tz date +'%A')"
ZERO="oh" # to pronounce the zero when minutes < 10 ("three oh two PM")
# German
#AMPM="$(LC_ALL=$locale TZ=$tz date +'%p')"
#AMPM="${AMPM^^}" # uppercase it
#TIME="$(LC_ALL=$locale TZ=$tz date +'%-H Uhr %-M')"
#TOTH="$(LC_ALL=$locale TZ=$tz date +'genau %-H Uhr')"
#MIN="$(LC_ALL=$locale TZ=$tz date +'%-M')"
#DAY="$(LC_ALL=$locale TZ=$tz date +'%A')"
#ZERO="Uhr" # to pronounce the zero when minutes < 10 ("three oh two PM")
if [ "$MIN" -lt 10 ]; then
TIME="$(LC_ALL=$locale TZ=$tz date +'%l') $ZERO $MIN $AMPM"
fi
if [ "$MIN" -eq 0 ]; then
TIME="$TOTH"
fi
# diag only
#echo $DAY, $TIME
# English
# build text (adapt volume level 0..100)
text='<volume level="100">'"It's $DAY, $TIME, and you're listening to Night Radio.</volume>"
# German
# build text (adapt volume level 0..100), 2s silence at beginning and end
#text='<volume level="100">'"Es ist $DAY, $TIME, und du hörst Nait Räidio.</volume>"
# create WAV audio file using PicoTTS
pico2wave -l=$lang -w=/tmp/time.$lang.wav "$text"
# use sox to make output better understandable (voices are rather muffled)
# we also add some silence at the beginning and end here, if so requested
# adding some treble in the range of +3 to +6 dB helps
# some voices might need a little bass reduction, use s/th like "bass -6 400"
# to avoid clipping, give headroom (gain -h) and reclaim afterwards (gain -r)
# skip the "compand" part if you don’t want compression/limiting.
sox /tmp/time.$lang.wav -c2 -r 44100 -C 128 /tmp/time.$lang.mp3 gain -h bass +2 treble +3 gain -r compand 0.008,0.024 -60,-60,-40,-35,0,-20 10 -60 $pad
# copy it into our media folder
cp /tmp/time.$lang.mp3 "$media_folder"
# diag only: play it loud
#play /tmp/time.$lang.mp3
saytime-de
#!/bin/bash
# saytime-de
# 2015-07-16 Moonbase
# 2023-03-09 Moonbase (update to PicoTTS; pico2wave & sox must be installed)
# Create /tmp/time.en-GB.wav for Liquidsoap to announce current time.
# Put this script into your ~/bin folder (or ~/.local/bin, or /usr/local/bin).
# 2023-05-28 Moonbase Adapted to AzuraCast
# On a Ubuntu Server, install required modules as follows:
# sudo apt install libttspico-utils sox libsox-fmt-mp3
# Install this file into /usr/local/bin and use root's crontab to run every minute.
# 2023-05-29 Moonbase Simplified silence generation
#
# crontab entry, to keep the file current, like so:
# */1 * * * * /usr/local/bin/saytime-de > /dev/null
# Possible output languages (might require different sox post-processing):
# de-DE - German
# en-GB - British Englisch
# en-US - US Englisch
# fr-FR - French
# it-IT - Italian
# es-ES - Spanish
lang="de-DE" # language for PicoTTS
locale="de_DE.utf8" # (installed!) OS locale
tz='Europe/Berlin' # desired timezone
# In case you wish to copy the file from /tmp into your media folder, specify it here.
# Use a trailing slash for folder names ("/folder/").
# You can also specify a full MP3 file name ("/folder/time-signal.mp3")
media_folder="/var/azuracast/time/"
# silence to add before/after the spoken text, in seconds; empty string for none
pad="pad 3 3"
# English
#AMPM="$(LC_ALL=$locale TZ=$tz date +'%p')"
#AMPM="${AMPM^^}" # uppercase it
#TIME="$(LC_ALL=$locale TZ=$tz date +'%l:%M ')${AMPM}"
#TOTH="$(LC_ALL=$locale TZ=$tz date +"%l o'clock ")${AMPM}"
#MIN="$(LC_ALL=$locale TZ=$tz date +'%-M')"
#DAY="$(LC_ALL=$locale TZ=$tz date +'%A')"
#ZERO="oh" # to pronounce the zero when minutes < 10 ("three oh two PM")
# German
AMPM="$(LC_ALL=$locale TZ=$tz date +'%p')"
AMPM="${AMPM^^}" # uppercase it
TIME="$(LC_ALL=$locale TZ=$tz date +'%-H Uhr %-M')"
TOTH="$(LC_ALL=$locale TZ=$tz date +'genau %-H Uhr')"
MIN="$(LC_ALL=$locale TZ=$tz date +'%-M')"
DAY="$(LC_ALL=$locale TZ=$tz date +'%A')"
ZERO="Uhr" # to pronounce the zero when minutes < 10 ("three oh two PM")
if [ "$MIN" -lt 10 ]; then
TIME="$(LC_ALL=$locale TZ=$tz date +'%-H') $ZERO $MIN $AMPM"
fi
if [ "$MIN" -eq 0 ]; then
TIME="$TOTH"
fi
# diag only
#echo $DAY, $TIME
# English
# build text (adapt volume level 0..100)
#text='<volume level="100">'"It's $DAY, $TIME, and you're listening to Night Radio.</volume>"
# German
# build text (adapt volume level 0..100), 2s silence at beginning and end
text='<volume level="100">'"Es ist $DAY, $TIME, und du hörst Nait Räidio.</volume>"
# create WAV audio file using PicoTTS
pico2wave -l=$lang -w=/tmp/time.$lang.wav "$text"
# use sox to make output better understandable (voices are rather muffled)
# we also add some silence at the beginning and end here, if so requested
# adding some treble in the range of +3 to +6 dB helps
# some voices might need a little bass reduction, use s/th like "bass -6 400"
# to avoid clipping, give headroom (gain -h) and reclaim afterwards (gain -r)
# skip the "compand" part if you don’t want compression/limiting.
sox /tmp/time.$lang.wav -c2 -r 44100 -C 128 /tmp/time.$lang.mp3 gain -h bass +2 treble +3 gain -r compand 0.008,0.024 -60,-60,-40,-35,0,-20 10 -60 $pad
# copy it into our media folder
cp /tmp/time.$lang.mp3 "$media_folder"
# diag only: play it loud
#play /tmp/time.$lang.mp3
Prepare a media folder time
This folder will hold the auto-generated time announcements time.de-DE.mp3
and time.en-GB.mp3
. It will later be mounted into your Docker container, as the station’s media/time
folder.
mkdir /var/azuracast/time
Test if the scripts work
saytime-de
saytime-en
You should now have two new announcement files in your /var/azuracast/time
folder:
ls -l /var/azuracast/time
total 468
-rw-r--r-- 1 root root 193515 Mai 28 16:24 time.de-DE.mp3
-rw-r--r-- 1 root root 180976 Mai 28 16:24 time.en-GB.mp3
Mount the time
folder into your station’s media folder
Let’s assume your station is called “niteradio”. Edit the file /var/azuracast/docker-compose.override.yml
to include the new volume (but don’t modify other entries that might already be there):
nano /var/azuracast/docker-compose.override.yml
and add the new time
folder to the station’s media folder:
services:
web:
volumes:
- /var/azuracast/time:/var/azuracast/stations/niteradio/media/time
Now, restart your station:
cd /var/azuracast
docker compose down
docker compose up -d
In your station, under Media → Music Files, you should now see a new folder time
Open it, and check the files by playing them
You should hear your time and station ID announcements (but not current yet).
Set up crontab to regenerate the announcement every minute
Time for automation! Set up crontab jobs to regenerate the announcement files every minute, so you can forget them and just play them whenever needed.
As root, edit your crontab
file and set everything up:
crontab -e
At the end, add the following lines (modify to your needs), and save:
*/1 * * * * /usr/local/bin/saytime-de > /dev/null
*/1 * * * * /usr/local/bin/saytime-en > /dev/null
Don’t worry, PicoTTS is rather leightweight, and will not eat up much CPU, even when regenerating your announcements every minute. And it’s local, so no extra Internet accesses are needed.
Prepare your station to use the announcements
Check if the files play the current time
Check if the cron job works, by playing each “time” announcement a few times. It should play the current time and your station ID, in all languages you have set up.
If something is wrong, check your /usr/local/bin/saytime-*
scripts. Use the correct time zone for your station, be sure the system has the needed language pack(s) installed, and you used the correct specifier, i.e. de_DE.utf8
.
Set up correct fading
For the time & station ID announcement files, set up correct fading because they will later interrupt the AutoDJ stream. Under Media → Media Files → time, click on Edit for each “time” file, go to Advanced and enter the following values:
This assumes you haven’t changed the default 3s silence padding at the start and end of the files.
Hint: Don’t use the Visual Cue Editor to set up fading for these files, since their lengths change every minute!
Create a Top-of-the-Hour playlist
If you wish to play a time & station ID at the top of the hour (TOTH), you can create a playlist like the following:
Refine and adapt to your needs
You can always adapt and enhance the scripts, remove or add to the station IDs, and create wild playlists using the newly created announcements.
Since we installed PicoTTS, you could in theory now even use Liquidsoap’s pico2wave
protocol and let it speak other things in your Liquidsoap script.
Further reading: SVOX Pico Manual.pdf
Enjoy!
Use, modify, steal, enhance, adapt to your heart’s content!
Don’t forget to share this, and possibly even add your great enhancements to the AzuraCast discussion thread.