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.1KB

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