470 lines
15 KiB
Bash
Executable File
470 lines
15 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
#talking-clock written by Storm Dragon
|
|
#project first created on Wednesday, March 23, 2011
|
|
#license WTFPL: http://wtfpl.net
|
|
|
|
display_help()
|
|
{
|
|
cat << EOF
|
|
Talking-clock by Storm Dragon
|
|
Talking-clock accepts the following arguments:
|
|
--cron number:
|
|
If you use --cron the only valid entry is a number following it. 1 will cause the clock to chime every hour, 2 every
|
|
half-hour, 4 every quarter, and anything else will delete your existing settings if they exist.
|
|
-a --audio Command for playing sound. The default is play -q provided from the sox package.
|
|
-c --nochime Turn off chimes.
|
|
-f --format: 12 or 24 hour format. Default is 12 hour time.
|
|
-n --nospeak turn off spoken time.
|
|
-s --soundpack Set path to soundpack. Sound packs should be in ogg format and contain 1.ogg, 2.ogg, ... 11.ogg, 12.ogg and
|
|
15, 30, and 45.oggfor the quarter-hour chimes.
|
|
-v --voice Select voice. Default is espeak other options are
|
|
cepstral, espeak, festival, gama_tts, rhvoice-(voicename), sam, speech-dispatcher and custom.
|
|
To set a custom voice enter the command as in:
|
|
-v 'espeak -v en-us+klatt2'
|
|
rhvoice is a special case, you must include the name of the voice you want as in rhvoice-bdl
|
|
-z --zipcode postal code Used for current temperature, may not be available inn all areas.
|
|
For complete information read the README located at /usr/share/talking-clock/README"
|
|
EOF
|
|
}
|
|
|
|
number_to_text()
|
|
{
|
|
# If the first argument is not numeric, then it is the return variable.
|
|
if ! [[ "$1" =~ ^[0-9]+$ ]]; then
|
|
local __numberVariable=$1
|
|
shift
|
|
fi
|
|
local number=$1
|
|
local digit
|
|
# Check for negative numbers.
|
|
if [ "${number:0:1}" = "-" ]; then
|
|
local textNumber="negative "
|
|
number="${number:1}"
|
|
else
|
|
local textNumber=""
|
|
fi
|
|
# This loop processes the numbers, only up to 999 for now, but can be expanded easily, I think.
|
|
while [ ${#number} -gt 0 ]; do
|
|
# To expand, add or to the if, 2 or 5 or 8, etc
|
|
if [ ${#number} -eq 2 ]; then
|
|
case $number in
|
|
0[1-9])
|
|
textNumber="${textNumber}and "
|
|
number="${number:1}"
|
|
;;
|
|
10)
|
|
textNumber="${textNumber}ten"
|
|
break
|
|
;;
|
|
11)
|
|
textNumber="${textNumber}eleven"
|
|
break
|
|
;;
|
|
12)
|
|
textNumber="${textNumber}twelve"
|
|
break
|
|
;;
|
|
13)
|
|
textNumber="${textNumber}thirteen"
|
|
break
|
|
;;
|
|
14)
|
|
textNumber="${textNumber}fourteen"
|
|
break
|
|
;;
|
|
15)
|
|
textNumber="${textNumber}fifteen"
|
|
break
|
|
;;
|
|
16)
|
|
textNumber="${textNumber}sixteen"
|
|
break
|
|
;;
|
|
17)
|
|
textNumber="${textNumber}seventeen"
|
|
break
|
|
;;
|
|
18)
|
|
textNumber="${textNumber}eightteen"
|
|
break
|
|
;;
|
|
19)
|
|
textNumber="${textNumber}nineteen"
|
|
break
|
|
;;
|
|
2*)
|
|
textNumber="${textNumber}twenty"
|
|
;;
|
|
3*)
|
|
textNumber="${textNumber}thirty"
|
|
;;
|
|
4*)
|
|
textNumber="${textNumber}forty"
|
|
;;
|
|
5*)
|
|
textNumber="${textNumber}fifty"
|
|
;;
|
|
6*)
|
|
textNumber="${textNumber}sixty"
|
|
;;
|
|
7*)
|
|
textNumber="${textNumber}seventy"
|
|
;;
|
|
8*)
|
|
textNumber="${textNumber}eighty"
|
|
;;
|
|
9*)
|
|
textNumber="${textNumber}ninety"
|
|
;;
|
|
esac
|
|
fi
|
|
# This stops the loop if the final digit is 0 otherwise we get a repeat of the digit before, 90 becomes 99 etc.
|
|
if [ ${#number} -eq 2 -a ${number:$((${#number} - 1)):1} -eq 0 ]; then
|
|
break
|
|
fi
|
|
# Put dashes in the proper places.
|
|
if [ ${#number} -eq 2 -a $number -gt 20 ]; then
|
|
if [ $(($number % 10)) -ne 0 ]; then
|
|
textNumber="${textNumber}-"
|
|
fi
|
|
fi
|
|
# Process the correct digit based on number length.
|
|
case ${#number} in
|
|
3)
|
|
digit=${number:0:1}
|
|
;;
|
|
2)
|
|
digit=${number:$((${#number} - 1)):1}
|
|
;;
|
|
1)
|
|
digit=${number:$((${#number} - 1)):1}
|
|
esac
|
|
case $digit in
|
|
1)
|
|
textNumber="${textNumber}one"
|
|
;;
|
|
2)
|
|
textNumber="${textNumber}two"
|
|
;;
|
|
3)
|
|
textNumber="${textNumber}three"
|
|
;;
|
|
4)
|
|
textNumber="${textNumber}four"
|
|
;;
|
|
5)
|
|
textNumber="${textNumber}five"
|
|
;;
|
|
6)
|
|
textNumber="${textNumber}six"
|
|
;;
|
|
7)
|
|
textNumber="${textNumber}seven"
|
|
;;
|
|
8)
|
|
textNumber="${textNumber}eight"
|
|
;;
|
|
9)
|
|
textNumber="${textNumber}nine"
|
|
esac
|
|
# Add the correct bit to the text number, million, thousand, hundred, etc. Trim off already processed digits.
|
|
case ${#number} in
|
|
3)
|
|
if [ $(($number % 100)) -eq 0 ]; then
|
|
textNumber="${textNumber} hundred"
|
|
else
|
|
textNumber="${textNumber} hundred "
|
|
fi
|
|
number="${number:1}"
|
|
;;
|
|
2)
|
|
number="${number:2}"
|
|
;;
|
|
*)
|
|
number="${number:1}"
|
|
esac
|
|
done
|
|
# If we have a return variable set its value to the text.
|
|
if [ -n "$__numberVariable" ]; then
|
|
eval $__numberVariable="'$textNumber'"
|
|
# Else just echo the text.
|
|
else
|
|
echo "$textNumber"
|
|
fi
|
|
}
|
|
|
|
#Create or modify cron jobs
|
|
if [ "$1" == "--cron" ] ; then
|
|
if [ "$#" -ne "2" ] ; then
|
|
echo -e "Invalid cron option:\n"\
|
|
"to set a cron enter:\n"\
|
|
"talking-clock --cron chimes-per-hour\n"\
|
|
"If you would like cron to chime 4 times per hour (every quarter hour) you would enter:\n"\
|
|
"talking-clock --cron 4\n"\
|
|
"Valid entries are 1, 2, and 4. Anything else causes the cron to be removed if it exists.\n"\
|
|
"After you have made changes you can use the command:\n"\
|
|
"crontab -l\n"\
|
|
"to view your settings."
|
|
exit 1
|
|
fi
|
|
crontabFile="$(mktemp)"
|
|
crontab -l > "$crontabFile"
|
|
read -n1 -p "You are about to modify talking-clock settings. Are you sure you want to do this? (Only y or Y confirms, anything else cancels) " answer
|
|
echo
|
|
if [ "${answer^}" != "Y" ]; then
|
|
echo "talking-clock settings unchanged."
|
|
rm "$crontabFile"
|
|
exit 0
|
|
fi
|
|
sed -i -r '/^.*talking-clock.*$/d' "$crontabFile"
|
|
case "$2" in
|
|
"1")
|
|
#Chime once per hour.
|
|
echo "# talking-clock settings" >> "$crontabFile"
|
|
echo "0 * * * * XDG_RUNTIME_DIR=/run/user/$UID /usr/bin/talking-clock &> /dev/null" >> "$crontabFile"
|
|
;;
|
|
"2")
|
|
#Chime twice per hour.
|
|
echo "# talking-clock settings" >> "$crontabFile"
|
|
echo "0,30 * * * * XDG_RUNTIME_DIR=/run/user/$UID talking-clock &> /dev/null" >> "$crontabFile"
|
|
;;
|
|
"4")
|
|
#Chime four times per hour.
|
|
echo "# talking-clock settings" >> "$crontabFile"
|
|
echo "0,15,30,45 * * * * XDG_RUNTIME_DIR=/run/user/$UID talking-clock &> /dev/null" >> "$crontabFile"
|
|
esac
|
|
#install the new cron file.
|
|
if [ -f/"$crontabFile" ]; then
|
|
if crontab "$crontabFile" ; then
|
|
echo "talking-clock settings updated."
|
|
rm "$crontabFile"
|
|
exit 0
|
|
else
|
|
echo "There was an error installing the new crontab file."
|
|
rm "$crontabFile"
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "Couldn't create new crontab."
|
|
exit 1
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
#initialize variables
|
|
xdgPath="${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
#Check for settings files in order of importants
|
|
if [ -f "$xdgPath/talking-clock/talking-clockrc" ] ; then
|
|
#Read from local settings
|
|
source "$xdgPath/talking-clock/talking-clockrc"
|
|
elif [ -f "/etc/talking-clockrc" ] ; then
|
|
#Read from global settings
|
|
source "/etc/talking-clockrc"
|
|
fi
|
|
hour=$(date +'%-l')
|
|
minute=$(date +'%-M')
|
|
timeOfDay=$(date +'%p' | sed 's/AM/A M/')
|
|
#play chimes?
|
|
chime="${chime:-true}"
|
|
#command used to play sounds
|
|
soundCommand="${sound:-play -qV0}"
|
|
#default voice for speaking time is espeak
|
|
voice="${voice:-espeak -v en-us -a 150}"
|
|
#should the time be spoken?
|
|
speakTime="${speak:-true}"
|
|
# Load soundpack.
|
|
soundPack="${soundpack:-/usr/share/talking-clock}"
|
|
# Time format
|
|
format="${format:-12}"
|
|
|
|
#Get and process commandline args which override all other settings.
|
|
while [ $# -gt 0 ] ; do
|
|
case "$1" in
|
|
"-a" | "--audio")
|
|
shift
|
|
soundCommand="$1"
|
|
;;
|
|
"-f" | "--format")
|
|
shift
|
|
if [ "$1" == "12" -o "$1" == "24" ] ; then
|
|
if [ $1 -eq 24 ] ; then
|
|
format="24"
|
|
fi
|
|
else
|
|
echo "Invalid time format: Valid options are 12 and 24."
|
|
exit 1
|
|
fi
|
|
;;
|
|
"-s" | "--soundpack")
|
|
shift
|
|
if [ -d "$1" ] ; then
|
|
soundPack="$1"
|
|
else
|
|
echo "Directory $1 does not exist."
|
|
exit 1
|
|
fi
|
|
;;
|
|
"-n" | "--nospeak")
|
|
speakTime="false"
|
|
;;
|
|
"-c" | "--nochime")
|
|
chime="false"
|
|
;;
|
|
"-v" | "--voice")
|
|
shift
|
|
voice="$1"
|
|
;;
|
|
"-z" | "--zipcode")
|
|
shift
|
|
zipcode="$1"
|
|
;;
|
|
*)
|
|
display_help
|
|
exit 0
|
|
esac
|
|
shift
|
|
done
|
|
|
|
#Speak the time
|
|
#Safely create voice files for tts who write to file
|
|
voiceFile="$(mktemp --tmpdir tlkclkXXXX.wav)"
|
|
if [ "$speakTime" == "true" ] ; then
|
|
if [ "$minute" -eq "0" ] ; then
|
|
timeString="$hour o clock $timeOfDay"
|
|
elif [ "$minute" -lt "10" ] ; then
|
|
timeString="$(number_to_text $hour) o $(number_to_text $minute) $timeOfDay"
|
|
else
|
|
timeString="$(number_to_text $hour) $(number_to_text $minute) $timeOfDay"
|
|
fi
|
|
#if 24 time is set, override the above with correct settings.
|
|
if [ "$format" = "24" ] ; then
|
|
#Make it read purdy for speech synthesizers.
|
|
timeStringHour="$(date +'%-H')"
|
|
if [ $timeStringHour -eq "0" ]; then
|
|
timeString="zero"
|
|
else
|
|
timeString="$(number_to_text $timeStringHour)"
|
|
fi
|
|
if [ $minute -eq "0" ]; then
|
|
timeString="${timeString} hundred hours"
|
|
elif [ $minute -lt 10 ]; then
|
|
timeString="${timeString} O $(number_to_text $minute)"
|
|
else
|
|
timeString="${timeString} $(number_to_text $minute)"
|
|
fi
|
|
fi
|
|
#Add temperature if zipcode is set
|
|
if [ -n "$zipcode" ] ; then
|
|
zipcode="${zipcode//[[:space:]]/+}"
|
|
zipcode="${zipcode#ip}"
|
|
temperature="$(curl -s "https://wttr.in/${zipcode}?format=%t%20degrees%20and%20%C")"
|
|
temp="${temperature//[^-[:digit:]]/}"
|
|
temperature="${temp} ${temperature#* }"
|
|
temperature="${temperature/-/Negative }"
|
|
if [ "${#temperature}" -ge 3 ] ; then
|
|
timeString="$timeString. It is currently $temperature."
|
|
fi
|
|
fi
|
|
case "$voice" in
|
|
"cepstral")
|
|
swift -o $voiceFile "$timeString"
|
|
#If the default sound command is used write time to file and normalize it before playing, cause Cepstral is kind of low volume.
|
|
if [ "$soundCommand" == "play -qV0" ] ; then
|
|
$soundCommand $voiceFile norm
|
|
else
|
|
$soundCommand $voiceFile
|
|
fi
|
|
;;
|
|
"espeak")
|
|
espeak -v en-us -a 175 "$timeString"
|
|
;;
|
|
"festival")
|
|
#If the default sound command is used write time to file and normalize it before playing, cause festival is kind of low volume.
|
|
if [ "$soundCommand" == "play -qV0" ] ; then
|
|
echo "$timeString" | text2wave -o $voiceFile
|
|
$soundCommand $voiceFile norm
|
|
else
|
|
echo "$timeString" | festival --tts
|
|
fi
|
|
;;
|
|
"flite")
|
|
#For some reason flite has trouble with ##:## format.
|
|
echo "$timeString" | flite
|
|
;;
|
|
"gama_tts")
|
|
gama_tts_wav="$(mktemp --suffix gama_talking_clock.wav)"
|
|
echo "$timeString" | gama_tts tts "${gama_tts_path:-/usr/local/share/gama_tts/data/english/vtm5}" $gama_tts_wav &> /dev/null
|
|
eval "$soundCommand $gama_tts_wav"
|
|
rm $gama_tts_wav
|
|
;;
|
|
"googletts")
|
|
/usr/bin/trans -speak -b "$timeString"
|
|
;;
|
|
"flite_time")
|
|
flite_time $(date +'%H:%M') &> /dev/null
|
|
;;
|
|
"sam")
|
|
sam "$(echo "$timeString" | sed -e 's/A M/ AY EM/' -e 's/PM/ PEE EM/' -e 's/10/ ten /g' -e 's/11/ eleven /g' -e 's/12/ twelve /g' -e 's/0*//g')"
|
|
;;
|
|
"speech-dispatcher")
|
|
spd-say -w -P important "$timeString"
|
|
;;
|
|
"rhvoice-"*)
|
|
echo "$timeString" | RHVoice-test -p "${voice##*-}" 2> /dev/null
|
|
;;
|
|
*)
|
|
$voice "$timeString"
|
|
esac
|
|
rm $voiceFile
|
|
fi
|
|
|
|
#Play the prepended sound if one is selected
|
|
#There will be a slight gap between the prepended sound and the actual chiming.
|
|
#This is to simulate real clocks based on my experience.
|
|
if [ "$minute" -eq "0" ]; then
|
|
if [[ -f "$soundPack/prepend.ogg" && "$chime" = "true" ]]; then
|
|
$soundCommand "$soundPack/prepend.ogg"
|
|
fi
|
|
fi
|
|
|
|
#chime for quarter hour
|
|
if [[ "$minute" -eq "15" || "$minute" -eq "30" || "$minute" -eq "45" ]] ; then
|
|
#Play correct sound pack file
|
|
if [ -f "$soundPack/$minute.ogg" ] ; then
|
|
if [ $chime != "false" ] ; then
|
|
$soundCommand "$soundPack/$minute.ogg"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
#Chime on the hour
|
|
if [ "$minute" -eq "0" ] ; then
|
|
#Check if soundpack has hour chimes or uses the bell sound.
|
|
if ! [ -f "$soundPack/$hour.ogg" ] ; then
|
|
if [ $chime != "false" ] ; then
|
|
i=0
|
|
#create chime string for sound players that can handle more than one sound argument.
|
|
soundString=""
|
|
while [ "$i" -lt "$hour" ] ; do
|
|
if [[ "$soundCommand" == "play" || "$soundCommand" == "play -q" || "$soundCommand" == "ogg123" || "$soundCommand" == "ogg123 -q" ]] ; then
|
|
soundString="$soundString $soundPack/bell.ogg"
|
|
else
|
|
$soundCommand "$soundPack/bell.ogg"
|
|
fi
|
|
i=$(($i + 1))
|
|
done
|
|
if [ -n "$soundString" ] ; then
|
|
#No quotes around soundString here because it will not work.
|
|
$soundCommand $soundString
|
|
fi
|
|
fi
|
|
else
|
|
if [ $chime != "false" ] ; then
|
|
$soundCommand "$soundPack/$hour.ogg"
|
|
fi
|
|
fi
|
|
fi
|
|
exit 0
|
|
|