def handle(self): """ @see Handler.handle()` """ LOG.info('Playing %s' % (self._what)) self.service.play(self._token) return Result(self, '', False, True)
def handle(self): ''' @see Handler.handle()` ''' LOG.info('Playing %s' % (self._what)) self.service.play(self._filenames) return Result(self, '', 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 < 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 __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 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 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 _build_from_dirname(self, dirname): ''' Build an index based on the given directory root. @type dirname: str @param dirname: The directory name to build from. ''' # Walk the tree for (subdir, subdirs, files) in os.walk(dirname): LOG.info("Indexing %s", subdir) # Handle all the files which we can find for filename in files: try: # Use mutagen to grab details path = os.path.join(subdir, filename) info = mutagen.File(path) if isinstance(info, mutagen.mp3.MP3): self._add_entry(AudioEntry.from_mp3(info)) elif isinstance(info, mutagen.flac.FLAC): self._add_entry(AudioEntry.from_flac(info)) else: LOG.debug("Ignoring %s", path) except Exception as e: LOG.warning("Failed to index %s: %s", path, e)
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() ''' 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 _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 _run(self): """ The actual worker thread. """ # 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 # I've got something to say (it's better to burn out, than to # fade away...) command = '(SayText "%s")\n' % text LOG.info("Sending: %s" % command.strip()) self._notify(Notifier.WORKING) self._subproc.stdin.write(command) self._subproc.stdin.flush() # Wait for an approximate amount of time that we think it will # take to say what we were told to. Yes, this isn't great but we # don't have a good way to tell when festival has done talking # owing to the fact it buffers its output when it's piping. while (not self._interrupted and time.time() - start < len(text) * 0.04): time.sleep(0.1) except Exception as e: LOG.error("Failed to say '%s': %s" % (text, e)) finally: self._notify(Notifier.IDLE) # Kill off the child try: if self._subproc is not None: self._subproc.terminate() self._subproc.communicate() except: pass
def _decode(self): """ @see AudioInput._decode() """ if self._sckt is None: # No context means no tokens LOG.warning("Had no stream context to close") return [] try: # Send the EOD token self._sckt.sendall(struct.pack('!q', -1)) # Get back the result: # 8 bytes for the length # data... LOG.info("Waiting for result...") length = b'' while len(length) < 8: got = self._sckt.recv(8 - len(length)) if len(got) == 0: raise IOError("EOF in recv()") length += got (count, ) = struct.unpack("!q", length) # Read in the string LOG.info("Reading %d chars" % (count, )) result = b'' while len(result) < count: got = self._sckt.recv(count - len(result)) if len(got) == 0: raise IOError("EOF in recv()") result += got result = result.decode() LOG.info("Result is: '%s'" % (result, )) # Convert to tokens tokens = [ Token(word.strip(), 1.0, True) for word in result.split(' ') if word.strip() != '' ] return tokens except Exception as e: # Again, just grumble on exceptions LOG.info("Failed to do remote processing: %s" % e) return [] finally: # Close it out, best effort try: LOG.info("Closing connection") self._sckt.shutdown(socket.SHUT_RDWR) self._sckt.close() except: pass finally: self._sckt = None
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 _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 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 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 set_volume(self, volume): ''' Set the volume to a value between zero and eleven. @type value: float @param value: The volume level to set. This should be between 0 and 11 inclusive. ''' volume = float(value) if volume < 0 or volume > 11: raise ValueError("Volume out of [0..11] range: %s" % value) # Set as a fraction of 1 v = (volume / 11) LOG.info("Setting volume to %0.2f" % v) pygame.mixer.music.set_volume(v)
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
def run(self): ''' The main worker. ''' LOG.info("Starting the system") self._start() LOG.info("Entering main loop") while self._running: try: # What time is love? now = time.time() # Handle any events. First check to see if any time events are # pending and need to be scheduled. while len(self._timer_events) > 0 and \ self._timer_events[0].schedule_time <= now: self._events.put(heapq.heappop(self._timer_events)) # Now handle the actual events while not self._events.empty(): event = self._events.get() try: result = event.invoke() if result is not None: self._events.put(result) except Exception as e: LOG.error("Event %s raised exception: %s", event, e) # Loop over all the inputs and see if they have anything pending for input in self._inputs: # Attempt a read, this will return None if there's nothing # available tokens = input.read() if tokens is not None: # Okay, we read something, attempt to handle it LOG.info("Read from %s: %s" % (input, [str(t) for t in tokens])) result = self._handle(tokens) # If we got something back then give it back to the user if result is not None: self._respond(result) # Wait for a bit before going around again time.sleep(0.1) except KeyboardInterrupt: LOG.warning("KeyboardInterrupt received") break # We're out of the main loop, shut things down LOG.info("Stopping the system") self._stop()
def evaluate(self, tokens): ''' @see Service.evaluate() ''' # Render to lower-case, for matching purposes. words = self._words(tokens) # Look for these types of queston prefices = (('what', 'is'), ('who', 'is')) for prefix in prefices: try: # Look for the prefix in the words index = list_index(words, prefix) # If we got here then we found the prefix. Strip that from the # query which we are about to make to Wikipedia. thing = ' '.join(words[index + len(prefix):]).strip() # Let's look to see if Wikipedia returns anything when we search # for this thing. belief = 0.5 try: self._notify(Notifier.ACTIVE) results = [ result.lower().strip() for result in wikipedia.search(thing) if result is not None and len(result) > 0 ] if thing in results: belief = 1.0 except Exception as e: LOG.error("Failed to query Wikipedia for '%s': %s" % (thing, e)) finally: self._notify(Notifier.IDLE) # Turn the words into a string for the handler. return _Handler(self, tokens, belief, thing) except Exception as e: LOG.info("%s not in %s: %s" % (prefix, words, e)) # If we got here then it didn't look like a query for us. return None
def _save_bytes(self, data): """ Save the raw bytes to a wav file. :type data: str :param data: The bytes to save out. """ if self._wav_dir is None: return filename = os.path.join(self._wav_dir, "%d.wav" % time.time()) LOG.info("Saving data as %s" % filename) with wave.open(filename, 'wb') as wf: wf.setnchannels(self._channels) wf.setsampwidth(self._width) wf.setframerate(self._rate) wf.writeframes(data) wf.close()