talking-clock 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. #!/bin/bash
  2. #talking-clock written by Storm Dragon
  3. #project first created on Wednesday, March 23, 2011
  4. #license WTFPL: http://wtfpl.net
  5. display_help()
  6. {
  7. cat << EOF
  8. Talking-clock by Storm Dragon
  9. Talking-clock accepts the following arguments:
  10. --cron number:
  11. If you use --cron the only valid entry is a number following it. 1 will cause the clock to chime every hour, 2 every
  12. half-hour, 4 every quarter, and anything else will delete your existing settings if they exist.
  13. -a --audio Command for playing sound. The default is play -q provided from the sox package.
  14. -c --nochime Turn off chimes.
  15. -f --format: 12 or 24 hour format. Default is 12 hour time.
  16. -n --nospeak turn off spoken time.
  17. -s --soundpack Set path to soundpack. Sound packs should be in ogg format and contain 1.ogg, 2.ogg, ... 11.ogg, 12.ogg and
  18. 15, 30, and 45.oggfor the quarter-hour chimes.
  19. -v --voice Select voice. Default is espeak other options are
  20. cepstral, espeak, festival, pico, speech-dispatcher and custom.
  21. To set a custom voice enter the command as in:
  22. -v 'espeak -v en-us+klatt2'
  23. -t --torify retrieve temperature anonymously using torify, used with -z --zipcode
  24. -z --zipcode postal code Used for current temperature, may not be available inn all areas.
  25. For complete information read the README located at /usr/share/talking-clock/README"
  26. EOF
  27. }
  28. number_to_text()
  29. {
  30. # If the first argument is not numeric, then it is the return variable.
  31. if ! [[ "$1" =~ ^[0-9]+$ ]]; then
  32. local __numberVariable=$1
  33. shift
  34. fi
  35. local number=$1
  36. local digit
  37. # Check for negative numbers.
  38. if [ "${number:0:1}" = "-" ]; then
  39. local textNumber="negative "
  40. number="${number:1}"
  41. else
  42. local textNumber=""
  43. fi
  44. # This loop processes the numbers, only up to 999 for now, but can be expanded easily, I think.
  45. while [ ${#number} -gt 0 ]; do
  46. # To expand, add or to the if, 2 or 5 or 8, etc
  47. if [ ${#number} -eq 2 ]; then
  48. case $number in
  49. 0[1-9])
  50. textNumber="${textNumber}and "
  51. number="${number:1}"
  52. ;;
  53. 10)
  54. textNumber="${textNumber}ten"
  55. break
  56. ;;
  57. 11)
  58. textNumber="${textNumber}eleven"
  59. break
  60. ;;
  61. 12)
  62. textNumber="${textNumber}twelve"
  63. break
  64. ;;
  65. 13)
  66. textNumber="${textNumber}thirteen"
  67. break
  68. ;;
  69. 14)
  70. textNumber="${textNumber}fourteen"
  71. break
  72. ;;
  73. 15)
  74. textNumber="${textNumber}fifteen"
  75. break
  76. ;;
  77. 16)
  78. textNumber="${textNumber}sixteen"
  79. break
  80. ;;
  81. 17)
  82. textNumber="${textNumber}seventeen"
  83. break
  84. ;;
  85. 18)
  86. textNumber="${textNumber}eightteen"
  87. break
  88. ;;
  89. 19)
  90. textNumber="${textNumber}nineteen"
  91. break
  92. ;;
  93. 2*)
  94. textNumber="${textNumber}twenty"
  95. ;;
  96. 3*)
  97. textNumber="${textNumber}thirty"
  98. ;;
  99. 4*)
  100. textNumber="${textNumber}forty"
  101. ;;
  102. 5*)
  103. textNumber="${textNumber}fifty"
  104. ;;
  105. 6*)
  106. textNumber="${textNumber}sixty"
  107. ;;
  108. 7*)
  109. textNumber="${textNumber}seventy"
  110. ;;
  111. 8*)
  112. textNumber="${textNumber}eighty"
  113. ;;
  114. 9*)
  115. textNumber="${textNumber}ninety"
  116. ;;
  117. esac
  118. fi
  119. # This stops the loop if the final digit is 0 otherwise we get a repeat of the digit before, 90 becomes 99 etc.
  120. if [ ${#number} -eq 2 -a ${number:$((${#number} - 1)):1} -eq 0 ]; then
  121. break
  122. fi
  123. # Put dashes in the proper places.
  124. if [ ${#number} -eq 2 -a $number -gt 20 ]; then
  125. if [ $(($number % 10)) -ne 0 ]; then
  126. textNumber="${textNumber}-"
  127. fi
  128. fi
  129. # Process the correct digit based on number length.
  130. case ${#number} in
  131. 3)
  132. digit=${number:0:1}
  133. ;;
  134. 2)
  135. digit=${number:$((${#number} - 1)):1}
  136. ;;
  137. 1)
  138. digit=${number:$((${#number} - 1)):1}
  139. esac
  140. case $digit in
  141. 1)
  142. textNumber="${textNumber}one"
  143. ;;
  144. 2)
  145. textNumber="${textNumber}two"
  146. ;;
  147. 3)
  148. textNumber="${textNumber}three"
  149. ;;
  150. 4)
  151. textNumber="${textNumber}four"
  152. ;;
  153. 5)
  154. textNumber="${textNumber}five"
  155. ;;
  156. 6)
  157. textNumber="${textNumber}six"
  158. ;;
  159. 7)
  160. textNumber="${textNumber}seven"
  161. ;;
  162. 8)
  163. textNumber="${textNumber}eight"
  164. ;;
  165. 9)
  166. textNumber="${textNumber}nine"
  167. esac
  168. # Add the correct bit to the text number, million, thousand, hundred, etc. Trim off already processed digits.
  169. case ${#number} in
  170. 3)
  171. if [ $(($number % 100)) -eq 0 ]; then
  172. textNumber="${textNumber} hundred"
  173. else
  174. textNumber="${textNumber} hundred "
  175. fi
  176. number="${number:1}"
  177. ;;
  178. 2)
  179. number="${number:2}"
  180. ;;
  181. *)
  182. number="${number:1}"
  183. esac
  184. done
  185. # If we have a return variable set its value to the text.
  186. if [ -n "$__numberVariable" ]; then
  187. eval $__numberVariable="'$textNumber'"
  188. # Else just echo the text.
  189. else
  190. echo "$textNumber"
  191. fi
  192. }
  193. #Create or modify cron jobs
  194. if [ "$1" == "--cron" ] ; then
  195. if [ "$#" -ne "2" ] ; then
  196. echo -e "Invalid cron option:\n"\
  197. "to set a cron enter:\n"\
  198. "talking-clock --cron chimes-per-hour\n"\
  199. "If you would like cron to chime 4 times per hour (every quarter hour) you would enter:\n"\
  200. "talking-clock --cron 4\n"\
  201. "Valid entries are 1, 2, and 4. Anything else causes the cron to be removed if it exists.\n"\
  202. "After you have made changes you can use the command:\n"\
  203. "crontab -l\n"\
  204. "to view your settings."
  205. exit 1
  206. fi
  207. crontabFile="$(mktemp)"
  208. crontab -l > "$crontabFile"
  209. 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
  210. if [ "${answer^}" != "Y" ]; then
  211. echo "talking-clock settings unchanged."
  212. rm "$crontabFile"
  213. exit 0
  214. fi
  215. sed -i -r '/^.*talking-clock.*$/d' "$crontabFile"
  216. case "$2" in
  217. "1")
  218. #Chime once per hour.
  219. echo "# talking-clock settings" >> "$crontabFile"
  220. echo '0 * * * * /usr/bin/talking-clock &> /dev/null' >> "$crontabFile"
  221. ;;
  222. "2")
  223. #Chime twice per hour.
  224. echo "# talking-clock settings" >> "$crontabFile"
  225. echo '0,30 * * * * talking-clock &> /dev/null' >> "$crontabFile"
  226. ;;
  227. "4")
  228. #Chime four times per hour.
  229. echo "# talking-clock settings" >> "$crontabFile"
  230. echo '0,15,30,45 * * * * talking-clock &> /dev/null' >> "$crontabFile"
  231. esac
  232. #install the new cron file.
  233. if [ -f/"$crontabFile" ]; then
  234. if crontab "$crontabFile" ; then
  235. echo "talking-clock settings updated."
  236. rm "$crontabFile"
  237. exit 0
  238. else
  239. echo "There was an error installing the new crontab file."
  240. rm "$crontabFile"
  241. exit 1
  242. fi
  243. else
  244. echo "Couldn't create new crontab."
  245. exit 1
  246. fi
  247. exit 0
  248. fi
  249. #initialize variables
  250. xdgPath="${XDG_CONFIG_HOME:-$HOME/.config}"
  251. #Check for settings files in order of importants
  252. if [ -f "$xdgPath/talking-clock/talking-clockrc" ] ; then
  253. #Read from local settings
  254. source "$xdgPath/talking-clock/talking-clockrc"
  255. elif [ -f "/etc/talking-clockrc" ] ; then
  256. #Read from global settings
  257. source "/etc/talking-clockrc"
  258. fi
  259. hour=$(date +'%-l')
  260. minute=$(date +'%-M')
  261. timeOfDay=$(date +'%p' | sed 's/AM/A M/')
  262. #play chimes?
  263. chime="${chime:-true}"
  264. #command used to play sounds
  265. soundCommand="${sound:-play -qV0}"
  266. #default voice for speaking time is espeak
  267. voice="${voice:-espeak -v en-us -a 150}"
  268. #should the time be spoken?
  269. speakTime="${speak:-true}"
  270. # Load soundpack.
  271. soundPack="${soundpack:-/usr/share/talking-clock}"
  272. # Time format
  273. format="${format:-12}"
  274. #Get and process commandline args which override all other settings.
  275. while [ $# -gt 0 ] ; do
  276. case "$1" in
  277. "-a" | "--audio")
  278. shift
  279. soundCommand="$1"
  280. ;;
  281. "-f" | "--format")
  282. shift
  283. if [ "$1" == "12" -o "$1" == "24" ] ; then
  284. if [ $1 -eq 24 ] ; then
  285. format="24"
  286. fi
  287. else
  288. echo "Invalid time format: Valid options are 12 and 24."
  289. exit 1
  290. fi
  291. ;;
  292. "-s" | "--soundpack")
  293. shift
  294. if [ -d "$1" ] ; then
  295. soundPack="$1"
  296. else
  297. echo "Directory $1 does not exist."
  298. exit 1
  299. fi
  300. ;;
  301. "-n" | "--nospeak")
  302. speakTime="false"
  303. ;;
  304. "-t" | "--torify")
  305. torify="true"
  306. ;;
  307. "-c" | "--nochime")
  308. chime="false"
  309. ;;
  310. "-v" | "--voice")
  311. shift
  312. voice="$1"
  313. ;;
  314. "-z" | "--zipcode")
  315. shift
  316. zipcode="$1"
  317. ;;
  318. *)
  319. display_help
  320. exit 0
  321. esac
  322. shift
  323. done
  324. #Speak the time
  325. #Safely create voice files for tts who write to file
  326. voiceFile="$(mktemp --tmpdir tlkclkXXXX.wav)"
  327. if [ "$speakTime" == "true" ] ; then
  328. if [ "$minute" -eq "0" ] ; then
  329. timeString="$hour o clock $timeOfDay"
  330. elif [ "$minute" -lt "10" ] ; then
  331. timeString="$(number_to_text $hour) o $(number_to_text $minute) $timeOfDay"
  332. else
  333. timeString="$(number_to_text $hour) $(number_to_text $minute) $timeOfDay"
  334. fi
  335. #if 24 time is set, override the above with correct settings.
  336. if [ "$format" = "24" ] ; then
  337. #Make it read purdy for speech synthesizers.
  338. timeStringHour="$(date +'%-H')"
  339. if [ $timeStringHour -eq "0" ]; then
  340. timeString="zero"
  341. else
  342. timeString="$(number_to_text $timeStringHour)"
  343. fi
  344. if [ $minute -eq "0" ]; then
  345. timeString="${timeString} hundred hours"
  346. elif [ $minute -lt 10 ]; then
  347. timeString="${timeString} O $(number_to_text $minute)"
  348. else
  349. timeString="${timeString} $(number_to_text $minute)"
  350. fi
  351. fi
  352. #Add temperature if zipcode is set
  353. if [ -n "$zipcode" ] ; then
  354. if [ "$torify" == "true" ] ; then
  355. temperature="$(torify curl -s "http://mobile.wunderground.com/cgi-bin/findweather/getForecast?brand=mobile&query=${zipcode}" | grep -A 2 '<tr><td>Temperature</td>' | tr -cd '[:digit:]-.' | cut -d . -f1)"
  356. else
  357. temperature="$(curl -s "http://mobile.wunderground.com/cgi-bin/findweather/getForecast?brand=mobile&query=${zipcode}" | grep -A 2 '<tr><td>Temperature</td>' | tr -cd '[:digit:]-.' | cut -d . -f1)"
  358. fi
  359. if [ -n "$temperature" ] ; then
  360. if [ "$format" = "24" ]; then
  361. timeString="$timeString and $(number_to_text $temperature) degrees."
  362. else
  363. timeString="$timeString $(number_to_text $temperature) degrees."
  364. fi
  365. fi
  366. fi
  367. case "$voice" in
  368. "cepstral")
  369. swift -o $voiceFile "$timeString"
  370. #If the default sound command is used write time to file and normalize it before playing, cause Cepstral is kind of low volume.
  371. if [ "$soundCommand" == "play -qV0" ] ; then
  372. $soundCommand $voiceFile norm
  373. else
  374. $soundCommand $voiceFile
  375. fi
  376. ;;
  377. "espeak")
  378. espeak -v en-us -a 175 "$timeString"
  379. ;;
  380. "festival")
  381. #If the default sound command is used write time to file and normalize it before playing, cause festival is kind of low volume.
  382. if [ "$soundCommand" == "play -qV0" ] ; then
  383. echo "$timeString" | text2wave -o $voiceFile
  384. $soundCommand $voiceFile norm
  385. else
  386. echo "$timeString" | festival --tts
  387. fi
  388. ;;
  389. "flite")
  390. #For some reason flite has trouble with ##:## format.
  391. echo "$timeString" | flite
  392. ;;
  393. "googletts")
  394. /usr/bin/translate-shell -speak -b "$timeString"
  395. ;;
  396. "flite_time")
  397. flite_time $(date +'%H:%M') &> /dev/null
  398. ;;
  399. "speech-dispatcher")
  400. spd-say -w -P important "$timeString"
  401. ;;
  402. "pico")
  403. pico2wave -w $voiceFile "$timeString"
  404. #ogg123 doesn't do wav, so hopefully everyone has aplay...
  405. if [[ "$soundCommand" == "ogg123" || "$soundCommand" == "ogg123 -q" ]] ; then
  406. aplay -q $voiceFile
  407. else
  408. $soundCommand $voiceFile
  409. fi
  410. ;;
  411. *)
  412. $voice "$timeString"
  413. esac
  414. rm $voiceFile
  415. fi
  416. #Play the prepended sound if one is selected
  417. #There will be a slight gap between the prepended sound and the actual chiming.
  418. #This is to simulate real clocks based on my experience.
  419. if [ "$minute" -eq "0" ]; then
  420. if [[ -f "$soundPack/prepend.ogg" && "$chime" = "true" ]]; then
  421. $soundCommand "$soundPack/prepend.ogg"
  422. fi
  423. fi
  424. #chime for quarter hour
  425. if [[ "$minute" -eq "15" || "$minute" -eq "30" || "$minute" -eq "45" ]] ; then
  426. #Play correct sound pack file
  427. if [ -f "$soundPack/$minute.ogg" ] ; then
  428. if [ $chime != "false" ] ; then
  429. $soundCommand "$soundPack/$minute.ogg"
  430. fi
  431. fi
  432. fi
  433. #Chime on the hour
  434. if [ "$minute" -eq "0" ] ; then
  435. #Check if soundpack has hour chimes or uses the bell sound.
  436. if ! [ -f "$soundPack/$hour.ogg" ] ; then
  437. if [ $chime != "false" ] ; then
  438. i=0
  439. #create chime string for sound players that can handle more than one sound argument.
  440. soundString=""
  441. while [ "$i" -lt "$hour" ] ; do
  442. if [[ "$soundCommand" == "play" || "$soundCommand" == "play -q" || "$soundCommand" == "ogg123" || "$soundCommand" == "ogg123 -q" ]] ; then
  443. soundString="$soundString $soundPack/bell.ogg"
  444. else
  445. $soundCommand "$soundPack/bell.ogg"
  446. fi
  447. i=$(($i + 1))
  448. done
  449. if [ -n "$soundString" ] ; then
  450. #No quotes around soundString here because it will not work.
  451. $soundCommand $soundString
  452. fi
  453. fi
  454. else
  455. if [ $chime != "false" ] ; then
  456. $soundCommand "$soundPack/$hour.ogg"
  457. fi
  458. fi
  459. fi
  460. exit 0