Somewhat fancy voice command recognition software
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

blather.py 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. #!/usr/bin/env python3
  2. # This is part of Kaylee
  3. # -- this code is licensed GPLv3
  4. # Copyright 2013 Jezra
  5. # Copyright 2015 Clayton G. Hobbs
  6. from __future__ import print_function
  7. import sys
  8. import signal
  9. import hashlib
  10. import os.path
  11. import subprocess
  12. from gi.repository import GObject, GLib
  13. import json
  14. from recognizer import Recognizer
  15. from config import Config
  16. # Where are the files?
  17. conf_dir = os.path.expanduser(os.path.join(GLib.get_user_config_dir(),
  18. "blather"))
  19. lang_dir = os.path.join(conf_dir, "language")
  20. command_file = os.path.join(conf_dir, "commands.conf")
  21. strings_file = os.path.join(conf_dir, "sentences.corpus")
  22. history_file = os.path.join(conf_dir, "blather.history")
  23. hash_file = os.path.join(conf_dir, "hash.json")
  24. lang_file = os.path.join(lang_dir, 'lm')
  25. dic_file = os.path.join(lang_dir, 'dic')
  26. # Make the lang_dir if it doesn't exist
  27. if not os.path.exists(lang_dir):
  28. os.makedirs(lang_dir)
  29. class Blather:
  30. def __init__(self):
  31. self.ui = None
  32. self.options = {}
  33. ui_continuous_listen = False
  34. self.continuous_listen = False
  35. self.commands = {}
  36. # Read the commands
  37. self.read_commands()
  38. # Load the options file
  39. self.config = Config()
  40. self.options = vars(self.config.options)
  41. if self.options['interface'] != None:
  42. if self.options['interface'] == "g":
  43. from gtkui import UI
  44. elif self.options['interface'] == "gt":
  45. from gtktrayui import UI
  46. else:
  47. print("no GUI defined")
  48. sys.exit()
  49. self.ui = UI(self.options, self.options['continuous'])
  50. self.ui.connect("command", self.process_command)
  51. # Can we load the icon resource?
  52. icon = self.load_resource("icon.png")
  53. if icon:
  54. self.ui.set_icon_active_asset(icon)
  55. # Can we load the icon_inactive resource?
  56. icon_inactive = self.load_resource("icon_inactive.png")
  57. if icon_inactive:
  58. self.ui.set_icon_inactive_asset(icon_inactive)
  59. if self.options['history']:
  60. self.history = []
  61. # Update the language if necessary
  62. self.update_language()
  63. # Create the recognizer
  64. try:
  65. self.recognizer = Recognizer(lang_file, dic_file, self.options['microphone'])
  66. except Exception as e:
  67. # No recognizer? bummer
  68. print('error making recognizer')
  69. sys.exit()
  70. self.recognizer.connect('finished', self.recognizer_finished)
  71. def read_commands(self):
  72. # Read the commands file
  73. file_lines = open(command_file)
  74. strings = open(strings_file, "w")
  75. for line in file_lines:
  76. # Trim the white spaces
  77. line = line.strip()
  78. # If the line has length and the first char isn't a hash
  79. if len(line) and line[0]!="#":
  80. # This is a parsible line
  81. (key, value) = line.split(":", 1)
  82. self.commands[key.strip().lower()] = value.strip()
  83. strings.write(key.strip() + "\n")
  84. # Close the strings file
  85. strings.close()
  86. def log_history(self, text):
  87. if self.options['history']:
  88. self.history.append(text)
  89. if len(self.history) > self.options['history']:
  90. # Pop off the first item
  91. self.history.pop(0)
  92. # Open and truncate the blather history file
  93. hfile = open(history_file, "w")
  94. for line in self.history:
  95. hfile.write(line + "\n")
  96. # Close the file
  97. hfile.close()
  98. def update_language(self):
  99. """Update the language if its hash has changed"""
  100. # Load the stored hash from the hash file
  101. try:
  102. with open(hash_file, 'r') as f:
  103. hashes = json.load(f)
  104. stored_hash = hashes['language']
  105. except (IOError, KeyError, TypeError):
  106. # No stored hash
  107. stored_hash = ''
  108. # Calculate the hash the language file has right now
  109. hasher = hashlib.sha256()
  110. with open(strings_file, 'rb') as sfile:
  111. buf = sfile.read()
  112. hasher.update(buf)
  113. new_hash = hasher.hexdigest()
  114. # If the hashes differ
  115. if stored_hash != new_hash:
  116. # Update the language
  117. # FIXME: Do this with Python, not Bash
  118. self.run_command('./language_updater.sh')
  119. # Store the new hash
  120. new_hashes = {'language': new_hash}
  121. with open(hash_file, 'w') as f:
  122. json.dump(new_hashes, f)
  123. def run_command(self, cmd):
  124. """Print the command, then run it"""
  125. print(cmd)
  126. subprocess.call(cmd, shell=True)
  127. def recognizer_finished(self, recognizer, text):
  128. t = text.lower()
  129. # Is there a matching command?
  130. if t in self.commands:
  131. # Run the valid_sentence_command if there is a valid sentence command
  132. if self.options['valid_sentence_command']:
  133. subprocess.call(self.options['valid_sentence_command'], shell=True)
  134. cmd = self.commands[t]
  135. # Should we be passing words?
  136. if self.options['pass_words']:
  137. cmd += " " + t
  138. self.run_command(cmd)
  139. else:
  140. self.run_command(cmd)
  141. self.log_history(text)
  142. else:
  143. # Run the invalid_sentence_command if there is an invalid sentence command
  144. if self.options['invalid_sentence_command']:
  145. subprocess.call(self.options['invalid_sentence_command'], shell=True)
  146. print("no matching command {0}".format(t))
  147. # If there is a UI and we are not continuous listen
  148. if self.ui:
  149. if not self.continuous_listen:
  150. # Stop listening
  151. self.recognizer.pause()
  152. # Let the UI know that there is a finish
  153. self.ui.finished(t)
  154. def run(self):
  155. if self.ui:
  156. self.ui.run()
  157. else:
  158. blather.recognizer.listen()
  159. def quit(self):
  160. sys.exit()
  161. def process_command(self, UI, command):
  162. print(command)
  163. if command == "listen":
  164. self.recognizer.listen()
  165. elif command == "stop":
  166. self.recognizer.pause()
  167. elif command == "continuous_listen":
  168. self.continuous_listen = True
  169. self.recognizer.listen()
  170. elif command == "continuous_stop":
  171. self.continuous_listen = False
  172. self.recognizer.pause()
  173. elif command == "quit":
  174. self.quit()
  175. def load_resource(self, string):
  176. local_data = os.path.join(os.path.dirname(__file__), 'data')
  177. paths = ["/usr/share/blather/", "/usr/local/share/blather", local_data]
  178. for path in paths:
  179. resource = os.path.join(path, string)
  180. if os.path.exists(resource):
  181. return resource
  182. # If we get this far, no resource was found
  183. return False
  184. if __name__ == "__main__":
  185. # Make our blather object
  186. blather = Blather()
  187. # Init gobject threads
  188. GObject.threads_init()
  189. # We want a main loop
  190. main_loop = GObject.MainLoop()
  191. # Handle sigint
  192. signal.signal(signal.SIGINT, signal.SIG_DFL)
  193. # Run the blather
  194. blather.run()
  195. # Start the main loop
  196. try:
  197. main_loop.run()
  198. except:
  199. print("time to quit")
  200. main_loop.quit()
  201. sys.exit()