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.

kaylee.py 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. # This is part of Kaylee
  2. # -- this code is licensed GPLv3
  3. # Copyright 2015-2016 Clayton G. Hobbs
  4. # Portions Copyright 2013 Jezra
  5. import sys
  6. import signal
  7. import os.path
  8. import subprocess
  9. from gi.repository import GObject, GLib
  10. from kayleevc.recognizer import Recognizer
  11. from kayleevc.util import *
  12. from kayleevc.numbers import NumberParser
  13. class Kaylee:
  14. def __init__(self):
  15. self.ui = None
  16. self.options = {}
  17. self.continuous_listen = False
  18. # Load configuration
  19. self.config = Config()
  20. self.options = vars(self.config.options)
  21. self.commands = self.options['commands']
  22. # Create number parser for later use
  23. self.number_parser = NumberParser()
  24. # Create a hasher
  25. self.hasher = Hasher(self.config)
  26. # Create the strings file
  27. self.update_voice_commands_if_changed()
  28. if self.options['interface']:
  29. if self.options['interface'] == "g":
  30. from kayleevc.gui import GTKInterface as UI
  31. elif self.options['interface'] == "gt":
  32. from kayleevc.gui import GTKTrayInterface as UI
  33. else:
  34. print("no GUI defined")
  35. sys.exit()
  36. self.ui = UI(self.options, self.options['continuous'])
  37. self.ui.connect("command", self.process_command)
  38. # Can we load the icon resource?
  39. icon = self.load_resource("icon_small.png")
  40. if icon:
  41. self.ui.set_icon_active_asset(icon)
  42. # Can we load the icon_inactive resource?
  43. icon_inactive = self.load_resource("icon_inactive_small.png")
  44. if icon_inactive:
  45. self.ui.set_icon_inactive_asset(icon_inactive)
  46. if self.options['history']:
  47. self.history = []
  48. # Update the language if necessary
  49. self.language_updater = LanguageUpdater(self.config)
  50. self.language_updater.update_language_if_changed()
  51. # Create the recognizer
  52. self.recognizer = Recognizer(self.config)
  53. self.recognizer.connect('finished', self.recognizer_finished)
  54. def update_voice_commands_if_changed(self):
  55. """Use hashes to test if the voice commands have changed"""
  56. stored_hash = self.hasher['voice_commands']
  57. # Calculate the hash the voice commands have right now
  58. hasher = self.hasher.get_hash_object()
  59. for voice_cmd in self.commands.keys():
  60. hasher.update(voice_cmd.encode('utf-8'))
  61. # Add a separator to avoid odd behavior
  62. hasher.update('\n'.encode('utf-8'))
  63. new_hash = hasher.hexdigest()
  64. if new_hash != stored_hash:
  65. self.create_strings_file()
  66. self.hasher['voice_commands'] = new_hash
  67. self.hasher.store()
  68. def create_strings_file(self):
  69. # Open the strings file
  70. with open(self.config.strings_file, 'w') as strings:
  71. # Add command words to the corpus
  72. for voice_cmd in sorted(self.commands.keys()):
  73. strings.write(voice_cmd.strip().replace('%d', '') + "\n")
  74. # Add number words to the corpus
  75. for word in self.number_parser.number_words:
  76. strings.write(word + " ")
  77. strings.write("\n")
  78. def log_history(self, text):
  79. if self.options['history']:
  80. self.history.append(text)
  81. if len(self.history) > self.options['history']:
  82. # Pop off the first item
  83. self.history.pop(0)
  84. # Open and truncate the history file
  85. with open(self.config.history_file, 'w') as hfile:
  86. for line in self.history:
  87. hfile.write(line + '\n')
  88. def run_command(self, cmd):
  89. """Print the command, then run it"""
  90. print(cmd)
  91. subprocess.call(cmd, shell=True)
  92. def recognizer_finished(self, recognizer, text):
  93. t = text.lower()
  94. numt, nums = self.number_parser.parse_all_numbers(t)
  95. # Is there a matching command?
  96. if t in self.commands:
  97. # Run the valid_sentence_command if it's set
  98. if self.options['valid_sentence_command']:
  99. subprocess.call(self.options['valid_sentence_command'],
  100. shell=True)
  101. cmd = self.commands[t]
  102. # Should we be passing words?
  103. if self.options['pass_words']:
  104. cmd += " " + t
  105. self.run_command(cmd)
  106. self.log_history(text)
  107. elif numt in self.commands:
  108. # Run the valid_sentence_command if it's set
  109. if self.options['valid_sentence_command']:
  110. subprocess.call(self.options['valid_sentence_command'],
  111. shell=True)
  112. cmd = self.commands[numt]
  113. cmd = cmd.format(*nums)
  114. # Should we be passing words?
  115. if self.options['pass_words']:
  116. cmd += " " + t
  117. self.run_command(cmd)
  118. self.log_history(text)
  119. else:
  120. # Run the invalid_sentence_command if it's set
  121. if self.options['invalid_sentence_command']:
  122. subprocess.call(self.options['invalid_sentence_command'],
  123. shell=True)
  124. print("no matching command {0}".format(t))
  125. # If there is a UI and we are not continuous listen
  126. if self.ui:
  127. if not self.continuous_listen:
  128. # Stop listening
  129. self.recognizer.pause()
  130. # Let the UI know that there is a finish
  131. self.ui.finished(t)
  132. def run(self):
  133. if self.ui:
  134. self.ui.run()
  135. else:
  136. self.recognizer.listen()
  137. def quit(self):
  138. sys.exit()
  139. def process_command(self, UI, command):
  140. print(command)
  141. if command == "listen":
  142. self.recognizer.listen()
  143. elif command == "stop":
  144. self.recognizer.pause()
  145. elif command == "continuous_listen":
  146. self.continuous_listen = True
  147. self.recognizer.listen()
  148. elif command == "continuous_stop":
  149. self.continuous_listen = False
  150. self.recognizer.pause()
  151. elif command == "quit":
  152. self.quit()
  153. def load_resource(self, string):
  154. # TODO: Use the Config object for this path management
  155. local_data = os.path.join(os.path.dirname(__file__), '..', 'data')
  156. paths = ["/usr/share/kaylee/", "/usr/local/share/kaylee", local_data]
  157. for path in paths:
  158. resource = os.path.join(path, string)
  159. if os.path.exists(resource):
  160. return resource
  161. # If we get this far, no resource was found
  162. return False
  163. def run():
  164. # Make our kaylee object
  165. kaylee = Kaylee()
  166. # Init gobject threads
  167. GObject.threads_init()
  168. # We want a main loop
  169. main_loop = GObject.MainLoop()
  170. # Handle sigint
  171. signal.signal(signal.SIGINT, signal.SIG_DFL)
  172. # Run the kaylee
  173. kaylee.run()
  174. # Start the main loop
  175. try:
  176. main_loop.run()
  177. except:
  178. main_loop.quit()
  179. sys.exit()