def handle(self): ''' @see Handler.handle() ''' try: value = parse_number(self._volume) LOG.info("Got value of %s from %s" % (value, self._volume)) if value < 0 or value > 11: # Bad value return Result( self, "Sorry, volume needs to be between zero and eleven", False, True) else: # Acknowledge that it happened set_volume(value) return Result(self, "Okay, volume now %s" % (value, ), False, True) except Exception: LOG.error("Problem parsing volume '%s':\n%s" % (self._volume, traceback.format_exc())) return Result( self, "Sorry, I don't know how to set the volume to %s" % (self._volume, ), False, True)
def evaluate(self, tokens): """ @see Service.evaluate() """ # Get the words, ready to match with words = self._words(tokens) # Look for these prefixes prefices = (('spell', ), ('how', 'do', 'you', 'spell')) match = None for prefix in prefices: try: # Look for the prefix and suffix in the words (start, end, score) = fuzzy_list_range(words, prefix) LOG.debug("%s matches %s with from %d to %d with score %d", prefix, words, start, end, score) # Get the best one if (start == 0 and (match is None or match[2] < score)): match = (start, end, score) except ValueError: pass # Did we get anything? if match is not None: # Simply give these to the handler (start, end, score) = match return _SpellingHandler(self, tokens, score / 100.0, words[end:]) else: # Nope, we got nothing return None
def acceptor(): while self._running: (sckt, addr) = self._socket.accept() LOG.info("Got connection from %s" % (addr, )) thread = Thread(target=lambda: self._handle(sckt)) thread.daemon = True thread.start()
def handle(self): ''' @see Handler.handle()` ''' LOG.info('Playing %s' % (self._what)) self.service.play(self._filenames) return Result(self, '', False, True)
def evaluate(self, tokens): """ @see Service.evaluate() """ # Get the words, ready to match with words = self._words(tokens) # This is how it could be phrased fixes = ((('define', ), tuple()), (('what', 'is', 'the', 'meaning', 'of'), tuple()), (('what', 'does'), ('mean', ))) match = None for (prefix, suffix) in fixes: try: # Look for the prefix and suffix in the words if len(prefix) > 0: (pre_start, pre_end, pre_score) = fuzzy_list_range(words, prefix) else: (pre_start, pre_end, pre_score) = (0, 0, 100) if len(suffix) > 0: (suf_start, suf_end, suf_score) = fuzzy_list_range(words, suffix) else: (suf_start, suf_end, suf_score) = (len(words), len(words), 100) LOG.debug( "%s matches %s with from %d to %d with score %d, " "and %s matches from %d to %d with score %d", prefix, words, pre_start, pre_end, pre_score, suffix, suf_start, suf_end, suf_score) # We expect there to be only one word in the middle of the # prefix and suffix when we match if (pre_start == 0 and pre_end + 1 == suf_start and suf_end == len(words) and (match is None or match[2] < score)): match = (pre_start, pre_end, pre_score, suf_start, suf_end, suf_score) except ValueError: pass # Did we get anything? if match is not None: # Pull back the values (pre_start, pre_end, pre_score, suf_start, suf_end, suf_score) = match # The belief is the geometric distance of the scores belief = sqrt(pre_score * pre_score + suf_score * suf_score) / 100.0 # The word is the one at pre_end (since it's non-inclusive) word = words[pre_end] # And give back the handler return _DictionaryHandler(self, tokens, belief, word, self._limit) else: # Nope, we got nothing return None
def play_files(self, filenames): """ Play a list of files. :type filenames: tuple(str) :param filenames: The list of filenames to play. """ # First we stop everything self.stop() # Make sure that we have at least one file if len(filenames) == 0: return # Now we load the first file. We do this directly so that errors may # propagate. filename = filenames[0] LOG.info("Playing %s", filename) get_pygame().mixer.music.load(filename) get_pygame().mixer.music.play() # And enqueue the rest for filename in filenames[1:]: self._queue.put(filename)
def __init__(self, roots): """ :type roots: tuple(str) :param roots: The list of URLs to search for music on. In the simplest form these will likely just be a bunch of strings looking something like: C{file:///home/pi/Music} """ # The various indices. These are of the form <str,tuple(_Entry)>. self._by_name = {} self._by_artist = {} self._by_album = {} # Tupleize, just in case someone passed in a string or whathaveyou if isinstance(roots, str): roots = (roots, ) elif not isinstance(roots, (tuple, list)): roots = tuple(roots) if roots is not None: for root in roots: start = time.time() self._build(root) end = time.time() LOG.info("Indexed %s in %0.1f seconds", root, end - start)
def handle(self): ''' @see Handler.handle() ''' try: LOG.info("Querying Wikipedia for '%s'" % (self._thing, )) summary = wikipedia.summary(self._thing) except Exception as e: LOG.error("Failed to query Wikipedia about '%s': %s" % (self._thing, e)) return Result( self, "Sorry, there was a problem asking Wikipedia about %s" % (self._thing, ), False, True) # Anything? if summary is None or len(summary.strip()) == 0: return None # Strip the summary down a little, some of these can be pretty # long. First just grab the first paragraph. Next, stop after about # 400 chars. shortened = summary.split('\n')[0] if '. ' in shortened and len(shortened) > 400: index = shortened.index('. ') shortened = shortened[:index + 1] # And give it back. We use a period after "says" here so that the speech # output will pause appropriately. It's not good gramma though. Since we # got back a result then we mark ourselves as exclusive; there is # probably not a lot of point in having others also return information. return Result(self, "Wikipedia says.\n%s" % shortened, False, True)
def handle(self): """ @see Handler.handle() """ try: # Make the change, capping at min and max cur = get_volume() new = max(MIN_VOLUME, min(MAX_VOLUME, cur + self._delta)) # Any change? if cur != new: # Acknowledge that it happened set_volume(new) direction = "Up" if self._delta > 0 else "Down" return Result(self, direction, False, True) else: # Nothing to do return None except Exception: LOG.error("Problem setting changing the volume by %s:\n%s" % (self._delta, traceback.format_exc())) return Result(self, "Sorry, there was a problem changing the volume", False, True)
def handle(self): """ @see Handler.handle() """ try: value = parse_number(self._volume) LOG.info("Got value of %s from %s" % (value, self._volume)) if value < MIN_VOLUME or value > MAX_VOLUME: # Bad value return Result( self, "Sorry, volume needs to be between %d and %d" % (MIN_VOLUME, MAX_VOLUME), False, True) else: # Acknowledge that it happened set_volume(value) return Result(self, "Okay, volume now %s" % (value, ), False, True) except Exception: LOG.error("Problem parsing volume '%s':\n%s" % (self._volume, traceback.format_exc())) return Result( self, "Sorry, I don't know how to set the volume to %s" % (self._volume, ), False, True)
def handle(self): """ @see Handler.handle()` """ LOG.info('Playing %s' % (self._what)) self.service.play(self._token) return Result(self, '', False, True)
def _decode(self): """ @see AudioInput._decode() """ # Collect anything remaining self._add_result(self._recognizer.FinalResult()) # Ensure it's clear for next time self._recognizer.Reset() # Tokenize tokens = [] LOG.debug("Decoding: %s" % self._results) for result in self._results: word = result.get('word', '').strip() conf = result.get('conf', 0.0) if word and conf: tokens.append(Token(word, conf, True)) # Done self._results = [] # And give them all back LOG.debug("Got: %s" % ' '.join(str(i) for i in tokens)) return tokens
def _run(self): """ The actual worker thread. """ # Festival is a little hokey and so we need to start it in the same # thread that we use it. Otherwise we get an message saying: # SIOD ERROR: the currently assigned stack limit has been exceeded # As such we need to do everything here and communicate back success or # failure to the _start() method via member variables. Lovely. try: import festival festival.execCommand(self._voice) self._boot_strapped = True except Exception as e: self._start_error = e return # Keep going until we're told to stop while self.is_running: if len(self._queue) == 0: time.sleep(0.1) continue # Else we have something to say try: # Get the text, make sure that '"'s in it won't confuse things start = time.time() text = self._queue.pop() text = text.replace('"', '') # Festvial pauses for too long with commas so just ignore them text = text.replace(',', '') # Ignore empty strings if not text: LOG.info("Nothing to say...") continue # We're about to say something, clear any interrupted flag ready # for any new one self._interrupted = False # We're talking so mark ourselves as active accordingly self._notify(Notifier.WORKING) # Break up the text into bits and say them so that we can # interrupt the output. Then say each non-empty part. We break # on natural pauses in the speech. for part in re.split(r'[\.,;:]', text): if self._interrupted: break if part: festival.sayText(part) except Exception as e: LOG.error("Failed to say '%s': %s" % (text, e)) finally: self._notify(Notifier.IDLE)
def _start(self): """ @see Startable._start() """ # Create the socket LOG.info("Opening socket to %s:%d" % (self._host, self._port)) self._socket = socket.socket() self._socket.connect((self._host, self._port))
def _on_button(self, button): """ Handle a button press. """ number = button.pin.number LOG.info("Got button on pin GPIO%d" % (number,)) tokens = self._bindings.get(number, []) if len(tokens) > 0: self._output.append(tuple(self._prefix + tokens))
def _on_key(self, app, what): """ Handle an incoming media key. """ if app == self._APP: LOG.info("Got media key '%s'" % (what, )) tokens = self._bindings.get(what, []) if len(tokens) > 0: self._output.append(tuple(self._prefix + tokens))
def update_status(self, component, status): """ @see Notifier.update_status() """ # Sanity if component is None or status is None: return LOG.info("Component %s is now %s", component, status)
def _updater(self): """ The method which will update the dongle. """ # Some state variables i_mult = 0.0 s_mult = 0.0 o_mult = 0.0 # And off we go! LOG.info("Started update thread") while self.is_running: # Don't busy-wait time.sleep(0.01) # What time is love? now = time.time() # How long since these components went non-idle i_since = now - self._input_time s_since = now - self._service_time o_since = now - self._output_time # Compute an level value from this level_scale = math.pi * 2 i_level = 255 * (1 + math.sin(i_since * level_scale)) / 2 s_level = 255 * (1 + math.sin(s_since * level_scale)) / 2 o_level = 255 * (1 + math.sin(o_since * level_scale)) / 2 # See what state we want these guys to be in. After 30s we figure # that the component is hung and turn it off. i_state = 1.0 if i_since < 30.0 else 0.0 s_state = 1.0 if s_since < 30.0 else 0.0 o_state = 1.0 if o_since < 30.0 else 0.0 # Slide the multiplier accordingly f = 0.1 i_mult = (1.0 - f) * i_mult + f * i_state s_mult = (1.0 - f) * s_mult + f * s_state o_mult = (1.0 - f) * o_mult + f * o_state # The RGB values r = int(max(0, min(255, o_level * o_mult))) g = int(max(0, min(255, s_level * s_mult))) b = int(max(0, min(255, i_level * i_mult))) # And set the dots for i in range(self._dots.n): new = [r, g, b, 1.0] if self._dots[i] != new: self._dots[i] = new self._dots.show() # And we're done for i in range(self._dots.n): self._dots[i] = (0, 0, 0) LOG.info("Stopped update thread")
def stop(self): ''' Stop all the notifiers. ''' for notifier in self._notifiers: try: LOG.info("Stopping %s" % notifier) notifier.stop() except Exception as e: LOG.error("Failed to stop %s: %s" % (notifier, e))
def start(self): ''' Start all the notifiers going. ''' for notifier in self._notifiers: try: LOG.info("Starting %s" % notifier) notifier.start() except Exception as e: LOG.error("Failed to start %s: %s" % (notifier, e))
def _add_entry(self, entry): ''' Add an entry to the index. @type entry: _Entry @param entry: The entry to add to the index. ''' # Ignore entries with no name if entry.name is None: return # We're adding it then LOG.info("Adding %s", entry.url) # Sanitise the strings thusly. This attempts to do a little data # cleaning along the way. def tidy(string): # Deal with empty strings string = _clean_string(string) if string is None: return None # Handle "_"s instead of spaces string = string.replace('_', ' ') string = _clean_string(string) if string is None: return None # Put ", The" back on the front if string.endswith(' The'): if string.endswith(', The'): string = "The " + string[:-5] else: string = "The " + string[:-4] # And, finally, make it all lower case so that we don't get fooled # by funny capitalisation string = string.lower() # And give it back return string.lower() # How we add the entry to an index def add(key, index, entry_): if key is not None: if key in index: index[key].append(entry_) else: index[key] = [entry_] # Add to the various indices add(tidy(entry.name), self._by_name, entry) add(tidy(entry.artist), self._by_artist, entry) add(tidy(entry.album), self._by_album, entry)
def interrupt(self): """ @see Output.interrupt """ self._interrupted = True if self._subproc is not None: # We do this by sending it a signal signal = subprocess.signal.SIGINT LOG.info("Sending %s to child process", signal) self._subproc.send_signal(signal)
def _decode_raw(self, data): ''' @see AudioInput._decode_raw() ''' audio = numpy.frombuffer(data, numpy.int16) words = self._model.stt(audio, self._rate) LOG.info("Got: %s" % (words, )) tokens = [ Token(word.strip(), 1.0, True) for word in words.split(' ') if len(word.strip()) > 0 ] return tokens
def _start(self): """ @see Startable._start() """ builder = clientbuilder.PydoraConfigFileBuilder('') if not builder.file_exists: raise ValueError("Unable to find config file; " "have you run pydora-config yet?") self._pandora = builder.build() LOG.info("Configured as device %s", self._pandora.device) self._player = VLCPlayer(Callbacks(self), sys.stdin) self._player.start()
def evaluate(self, tokens): ''' @see Service.evaluate() ''' # Get stripped text, for matching text = ' '.join(to_letters(w) for w in self._words(tokens)) # See if it matches an expected string for want in ('whats the time', 'what is the time', 'what time is it'): if text == want: LOG.info("Matched input on '%s'" % text) return _ClockHandler(self, tokens) # If we got here, then we didn't get an exact match return None
def _stop(self): ''' Stop the system. ''' # Stop the notifiers self._state.stop() # And the components for component in self._inputs + self._outputs + self._services: # Best effort, since we're likely shutting down try: LOG.info("Stopping %s" % (component, )) component.stop() except Exception as e: LOG.error("Failed to stop %s: %$s" % (component, e))
def evaluate(self, tokens): """ @see Service.evaluate() """ words = self._words(tokens) for (what, handler) in self._HANDLERS: for prefix in self._PREFICES: phrase = (prefix + what) try: (s, e, _) = fuzzy_list_range(words, phrase) if s == 0 and e == len(phrase): return handler(self, tokens) except Exception as e: LOG.debug("Failed to handle '%s': %s" % (' '.join(words), e)) return None
def _add_result(self, json_result): """ Add in any result we have from the given JSON string. """ result = json.loads(json_result) LOG.debug("Got %s" % json_result) # See what we got, if anything if 'result' in result: # A full result, which is the best self._results.extend(result['result']) elif 'text' in result: # A decoded text string for word in result['text'].split(): if word: self._results.append({'word': word, 'conf': 1.0})
def main(config=None): ''' Main entry point. ''' if config is not None: try: with open(config) as fh: configuration = json.load(fh) except Exception as e: LOG.fatal("Failed to parse config file '%s': %s" % (config, e)) sys.exit(1) else: configuration = CONFIG # And spawn it dexter = Dexter(configuration) dexter.run()
def __init__(self, notifier, rate=None, wav_dir=None, model=os.path.join(_MODEL_DIR, 'models.pbmm'), scorer=os.path.join(_MODEL_DIR, 'models.scorer')): """ @see AudioInput.__init__() :type rate: :param rate: The override for the rate, if not the model's one. :type wav_dir: :param wav_dir: Where to save the wave files, if anywhere. :type model: :param model: The path to the DeepSpeech model file. :type scorer: :param scorer: The path to the DeepSpeech scorer file. """ # If these don't exist then DeepSpeech will segfault when inferring! if not os.path.exists(model): raise IOError("Not found: %s" % (model, )) # Load in and configure the model. LOG.info("Loading model from %s" % (model, )) self._model = Model(model) if os.path.exists(scorer): LOG.info("Loading scorer from %s" % (scorer, )) self._model.enableExternalScorer(scorer) # Handle any rate override if rate is None: rate = self._model.sampleRate() # Wen can now init the superclass super(DeepSpeechInput, self).__init__(notifier, format=pyaudio.paInt16, channels=1, rate=rate, wav_dir=wav_dir) # Where we put the stream context self._context = None