Somewhat fancy voice command recognition software
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

blather.py 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. #!/usr/bin/env python2
  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 optparse import OptionParser
  13. from gi.repository import GObject
  14. try:
  15. import yaml
  16. except:
  17. print("YAML is not supported; unable to use config file")
  18. from recognizer import Recognizer
  19. # Where are the files?
  20. conf_dir = os.path.expanduser("~/.config/blather")
  21. lang_dir = os.path.join(conf_dir, "language")
  22. command_file = os.path.join(conf_dir, "commands.conf")
  23. strings_file = os.path.join(conf_dir, "sentences.corpus")
  24. history_file = os.path.join(conf_dir, "blather.history")
  25. opt_file = os.path.join(conf_dir, "options.yaml")
  26. hash_file = os.path.join(conf_dir, "hash.yaml")
  27. lang_file = os.path.join(lang_dir, 'lm')
  28. dic_file = os.path.join(lang_dir, 'dic')
  29. # Make the lang_dir if it doesn't exist
  30. if not os.path.exists(lang_dir):
  31. os.makedirs(lang_dir)
  32. class Blather:
  33. def __init__(self, opts):
  34. self.ui = None
  35. self.options = {}
  36. ui_continuous_listen = False
  37. self.continuous_listen = False
  38. self.commands = {}
  39. # Read the commands
  40. self.read_commands()
  41. # Load the options file
  42. self.load_options()
  43. # Merge the options with the ones provided by command-line arguments
  44. for k, v in opts.__dict__.items():
  45. if (not k in self.options) or opts.override:
  46. self.options[k] = v
  47. if self.options['interface'] != None:
  48. if self.options['interface'] == "g":
  49. from gtkui import UI
  50. elif self.options['interface'] == "gt":
  51. from gtktrayui import UI
  52. else:
  53. print("no GUI defined")
  54. sys.exit()
  55. self.ui = UI(args, self.options['continuous'])
  56. self.ui.connect("command", self.process_command)
  57. # Can we load the icon resource?
  58. icon = self.load_resource("icon.png")
  59. if icon:
  60. self.ui.set_icon_active_asset(icon)
  61. # Can we load the icon_inactive resource?
  62. icon_inactive = self.load_resource("icon_inactive.png")
  63. if icon_inactive:
  64. self.ui.set_icon_inactive_asset(icon_inactive)
  65. if self.options['history']:
  66. self.history = []
  67. # Update the language if necessary
  68. self.update_language()
  69. # Create the recognizer
  70. try:
  71. self.recognizer = Recognizer(lang_file, dic_file, self.options['microphone'])
  72. except Exception as e:
  73. # No recognizer? bummer
  74. print('error making recognizer')
  75. sys.exit()
  76. self.recognizer.connect('finished', self.recognizer_finished)
  77. print("Using Options: ", self.options)
  78. def read_commands(self):
  79. # Read the commands file
  80. file_lines = open(command_file)
  81. strings = open(strings_file, "w")
  82. for line in file_lines:
  83. print(line)
  84. # Trim the white spaces
  85. line = line.strip()
  86. # If the line has length and the first char isn't a hash
  87. if len(line) and line[0]!="#":
  88. # This is a parsible line
  89. (key, value) = line.split(":", 1)
  90. print(key, value)
  91. self.commands[key.strip().lower()] = value.strip()
  92. strings.write( key.strip()+"\n")
  93. # Close the strings file
  94. strings.close()
  95. def load_options(self):
  96. """If possible, load options from the options.yaml file"""
  97. # Is there an opt file?
  98. try:
  99. opt_fh = open(opt_file)
  100. text = opt_fh.read()
  101. self.options = yaml.load(text)
  102. except:
  103. # Do nothing if the options file cannot be loaded
  104. pass
  105. def log_history(self, text):
  106. if self.options['history']:
  107. self.history.append(text)
  108. if len(self.history) > self.options['history']:
  109. # Pop off the first item
  110. self.history.pop(0)
  111. # Open and truncate the blather history file
  112. hfile = open(history_file, "w")
  113. for line in self.history:
  114. hfile.write( line+"\n")
  115. # Close the file
  116. hfile.close()
  117. def update_language(self):
  118. """Update the language if its hash has changed"""
  119. try:
  120. # Load the stored hash from the hash file
  121. try:
  122. with open(hash_file, 'r') as f:
  123. text = f.read()
  124. hashes = yaml.load(text)
  125. stored_hash = hashes['language']
  126. except (IOError, KeyError, TypeError):
  127. # No stored hash
  128. stored_hash = ''
  129. # Calculate the hash the language file has right now
  130. hasher = hashlib.sha256()
  131. with open(strings_file, 'rb') as sfile:
  132. buf = sfile.read()
  133. hasher.update(buf)
  134. new_hash = hasher.hexdigest()
  135. # If the hashes differ
  136. if stored_hash != new_hash:
  137. # Update the language
  138. # FIXME: Do this with Python, not Bash
  139. self.run_command('./language_updater.sh')
  140. # Store the new hash
  141. new_hashes = {'language': new_hash}
  142. with open(hash_file, 'w') as f:
  143. f.write(yaml.dump(new_hashes))
  144. except Exception as e:
  145. # Do nothing if the hash file cannot be loaded
  146. # FIXME: This is kind of bad; maybe YAML should be mandatory.
  147. print('error updating language')
  148. print(e)
  149. pass
  150. def run_command(self, cmd):
  151. """Print the command, then run it"""
  152. print(cmd)
  153. subprocess.call(cmd, shell=True)
  154. def recognizer_finished(self, recognizer, text):
  155. t = text.lower()
  156. # Is there a matching command?
  157. if self.commands.has_key( t ):
  158. # Run the valid_sentence_command if there is a valid sentence command
  159. if self.options['valid_sentence_command']:
  160. subprocess.call(self.options['valid_sentence_command'], shell=True)
  161. cmd = self.commands[t]
  162. # Should we be passing words?
  163. if self.options['pass_words']:
  164. cmd += " " + t
  165. self.run_command(cmd)
  166. else:
  167. self.run_command(cmd)
  168. self.log_history(text)
  169. else:
  170. # Run the invalid_sentence_command if there is an invalid sentence command
  171. if self.options['invalid_sentence_command']:
  172. subprocess.call(self.options['invalid_sentence_command'], shell=True)
  173. print("no matching command {0}".format(t))
  174. # If there is a UI and we are not continuous listen
  175. if self.ui:
  176. if not self.continuous_listen:
  177. # Stop listening
  178. self.recognizer.pause()
  179. # Let the UI know that there is a finish
  180. self.ui.finished(t)
  181. def run(self):
  182. if self.ui:
  183. self.ui.run()
  184. else:
  185. blather.recognizer.listen()
  186. def quit(self):
  187. sys.exit()
  188. def process_command(self, UI, command):
  189. print(command)
  190. if command == "listen":
  191. self.recognizer.listen()
  192. elif command == "stop":
  193. self.recognizer.pause()
  194. elif command == "continuous_listen":
  195. self.continuous_listen = True
  196. self.recognizer.listen()
  197. elif command == "continuous_stop":
  198. self.continuous_listen = False
  199. self.recognizer.pause()
  200. elif command == "quit":
  201. self.quit()
  202. def load_resource(self, string):
  203. local_data = os.path.join(os.path.dirname(__file__), 'data')
  204. paths = ["/usr/share/blather/", "/usr/local/share/blather", local_data]
  205. for path in paths:
  206. resource = os.path.join(path, string)
  207. if os.path.exists( resource ):
  208. return resource
  209. # If we get this far, no resource was found
  210. return False
  211. if __name__ == "__main__":
  212. parser = OptionParser()
  213. parser.add_option("-i", "--interface", type="string", dest="interface",
  214. action='store',
  215. help="Interface to use (if any). 'g' for GTK or 'gt' for GTK system tray icon")
  216. parser.add_option("-c", "--continuous",
  217. action="store_true", dest="continuous", default=False,
  218. help="starts interface with 'continuous' listen enabled")
  219. parser.add_option("-p", "--pass-words",
  220. action="store_true", dest="pass_words", default=False,
  221. help="passes the recognized words as arguments to the shell command")
  222. parser.add_option("-o", "--override",
  223. action="store_true", dest="override", default=False,
  224. help="override config file with command line options")
  225. parser.add_option("-H", "--history", type="int",
  226. action="store", dest="history",
  227. help="number of commands to store in history file")
  228. parser.add_option("-m", "--microphone", type="int",
  229. action="store", dest="microphone", default=None,
  230. help="Audio input card to use (if other than system default)")
  231. parser.add_option("--valid-sentence-command", type="string", dest="valid_sentence_command",
  232. action='store',
  233. help="command to run when a valid sentence is detected")
  234. parser.add_option( "--invalid-sentence-command", type="string", dest="invalid_sentence_command",
  235. action='store',
  236. help="command to run when an invalid sentence is detected")
  237. (options, args) = parser.parse_args()
  238. # Make our blather object
  239. blather = Blather(options)
  240. # Init gobject threads
  241. GObject.threads_init()
  242. # We want a main loop
  243. main_loop = GObject.MainLoop()
  244. # Handle sigint
  245. signal.signal(signal.SIGINT, signal.SIG_DFL)
  246. # Run the blather
  247. blather.run()
  248. # Start the main loop
  249. try:
  250. main_loop.run()
  251. except:
  252. print("time to quit")
  253. main_loop.quit()
  254. sys.exit()