talking-clock/src/talking-clock

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