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 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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 importlib
  6. import sys
  7. import subprocess
  8. import signal
  9. import os.path
  10. from gi.repository import GObject, GLib
  11. from kayleevc.recognizer import Recognizer
  12. from kayleevc.util import *
  13. from kayleevc.numbers import NumberParser
  14. import kayleevc.plugins
  15. class Kaylee:
  16. def __init__(self):
  17. self.ui = None
  18. self.options = {}
  19. self.continuous_listen = False
  20. # Load configuration
  21. self.config = Config()
  22. self.options = vars(self.config.options)
  23. # Load plugins
  24. self.plugins = []
  25. for plugin in self.options['plugins'].keys():
  26. pmod = importlib.import_module(plugin, 'kayleevc.plugins')
  27. self.plugins.append(pmod.Plugin(self.config, plugin))
  28. # Create a hasher
  29. self.hasher = Hasher(self.config)
  30. # Create the strings file
  31. self.update_voice_commands_if_changed()
  32. if self.options['interface']:
  33. if self.options['interface'] == "g":
  34. from kayleevc.gui import GTKInterface as UI
  35. elif self.options['interface'] == "gt":
  36. from kayleevc.gui import GTKTrayInterface as UI
  37. else:
  38. print("no GUI defined")
  39. sys.exit()
  40. self.ui = UI(self.options, self.options['continuous'])
  41. self.ui.connect("command", self.process_command)
  42. # Can we load the icon resource?
  43. icon = self.load_resource("icon_small.png")
  44. if icon:
  45. self.ui.set_icon_active_asset(icon)
  46. # Can we load the icon_inactive resource?
  47. icon_inactive = self.load_resource("icon_inactive_small.png")
  48. if icon_inactive:
  49. self.ui.set_icon_inactive_asset(icon_inactive)
  50. if self.options['history']:
  51. self.history = []
  52. # Update the language if necessary
  53. self.language_updater = LanguageUpdater(self.config)
  54. self.language_updater.update_language_if_changed()
  55. # Create the recognizer
  56. self.recognizer = Recognizer(self.config)
  57. # Connect the recognizer's finished signal to all the plugins
  58. for plugin in self.plugins:
  59. self.recognizer.connect('finished', plugin.recognizer_finished)
  60. plugin.connect('processed', self.plugin_processed)
  61. def update_voice_commands_if_changed(self):
  62. """Use hashes to test if the voice commands have changed"""
  63. stored_hash = self.hasher['voice_commands']
  64. # Calculate the hash the voice commands have right now
  65. hasher = self.hasher.get_hash_object()
  66. for plugin in self.plugins:
  67. for string in sorted(plugin.corpus_strings):
  68. hasher.update(string.encode('utf-8'))
  69. # Add a separator to avoid odd behavior
  70. hasher.update('\n'.encode('utf-8'))
  71. new_hash = hasher.hexdigest()
  72. if new_hash != stored_hash:
  73. self.create_strings_file()
  74. self.hasher['voice_commands'] = new_hash
  75. self.hasher.store()
  76. def create_strings_file(self):
  77. # Open the strings file
  78. with open(self.config.strings_file, 'w') as strings:
  79. # Add command words to the corpus
  80. # FIXME: Doing this twice is a silly thing
  81. for plugin in self.plugins:
  82. for string in sorted(plugin.corpus_strings):
  83. strings.write(string + "\n")
  84. # Add number words to the corpus
  85. if NumberParser.number_words is not None:
  86. for word in NumberParser.number_words:
  87. strings.write(word + " ")
  88. strings.write("\n")
  89. def plugin_processed(self, plugin, text):
  90. """Callback for ``processed`` signal from plugins
  91. Runs the valid_sentence_command and logs the recognized sentence to the
  92. history file.
  93. """
  94. # Run the valid_sentence_command if it's set
  95. if self.options['valid_sentence_command']:
  96. subprocess.call(self.options['valid_sentence_command'], shell=True)
  97. # Log the command to the history file
  98. if self.options['history']:
  99. self.history.append("{}: {}".format(plugin.name, text))
  100. if len(self.history) > self.options['history']:
  101. # Pop off the first item
  102. self.history.pop(0)
  103. # Open and truncate the history file
  104. with open(self.config.history_file, 'w') as hfile:
  105. for line in self.history:
  106. hfile.write(line + '\n')
  107. def run(self):
  108. if self.ui:
  109. self.ui.run()
  110. else:
  111. self.recognizer.listen()
  112. def quit(self):
  113. sys.exit()
  114. def process_command(self, UI, command):
  115. print(command)
  116. if command == "listen":
  117. self.recognizer.listen()
  118. elif command == "stop":
  119. self.recognizer.pause()
  120. elif command == "continuous_listen":
  121. self.continuous_listen = True
  122. self.recognizer.listen()
  123. elif command == "continuous_stop":
  124. self.continuous_listen = False
  125. self.recognizer.pause()
  126. elif command == "quit":
  127. self.quit()
  128. def load_resource(self, string):
  129. # TODO: Use the Config object for this path management
  130. local_data = os.path.join(os.path.dirname(__file__), '..', 'data')
  131. paths = ["/usr/share/kaylee/", "/usr/local/share/kaylee", local_data]
  132. for path in paths:
  133. resource = os.path.join(path, string)
  134. if os.path.exists(resource):
  135. return resource
  136. # If we get this far, no resource was found
  137. return False
  138. def run():
  139. # Make our kaylee object
  140. kaylee = Kaylee()
  141. # Init gobject threads
  142. GObject.threads_init()
  143. # We want a main loop
  144. main_loop = GObject.MainLoop()
  145. # Handle sigint
  146. signal.signal(signal.SIGINT, signal.SIG_DFL)
  147. # Run the kaylee
  148. kaylee.run()
  149. # Start the main loop
  150. try:
  151. main_loop.run()
  152. except:
  153. main_loop.quit()
  154. sys.exit()