示例#1
0
    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)
示例#2
0
    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)
示例#3
0
    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)
示例#4
0
    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)
示例#5
0
    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)
示例#6
0
    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
示例#7
0
 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))
示例#8
0
 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))
示例#9
0
    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()
示例#10
0
    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', 'a'), ('what', 'is', 'the'), ('what', 'is'),
                    ('who', 'is', 'the'), ('who', 'is'))
        match = None
        for prefix in prefices:
            try:
                # Look for the prefix 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)
                if start == 0 and (match is None or match[2] < score):
                    match = (start, end, score)
            except ValueError:
                pass

        # If we got a good match then use it
        if match:
            (start, end, score) = match
            thing = ' '.join(words[end:]).strip().lower()

            # Let's look to see if Wikipedia returns anything when we search
            # for this thing
            best = None
            try:
                self._notify(Notifier.ACTIVE)
                for result in wikipedia.search(thing):
                    if result is None or len(result) == 0:
                        continue
                    score = fuzz.ratio(thing, result.lower())
                    LOG.debug("'%s' matches '%s' with a score of %d", result,
                              thing, score)
                    if best is None or best[1] < score:
                        best = (result, score)
            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
            if best is not None:
                return _Handler(self, tokens, best[1] / 100, best[0])

        # If we got here then it didn't look like a query for us
        return None
示例#11
0
    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))
示例#12
0
    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
示例#13
0
文件: pandora.py 项目: iamsrp/dexter
    def _get_handler_for(self, tokens, platform_match, genre, artist,
                         song_or_album):
        """
        @see MusicService._get_handler_for()
        """
        # Do nothing if we have no name
        if song_or_album is None or len(song_or_album) == 0:
            return None

        # Normalise to strings
        name = ' '.join(song_or_album)
        if artist is None or len(artist) == 0:
            artist = None
        else:
            artist = ' '.join(artist)

        # Construct the search string
        search = name
        if artist is not None:
            search += " by " + artist

        # Do the search
        LOG.error("Looking for '%s'", search)
        result = self._pandora.search(search)
        LOG.error("Got: %s", result)

        # See if we got something back
        if len(result.songs) > 0:
            # Pick the best
            song = sorted(result.songs,
                          key=lambda item: item.score,
                          reverse=True)[0]

            # Grab what the handler needs
            what = "%s by %s" % (song.song_name, song.artist)
            score = song.score / 100.0
            token = song.token

            # And give back the handler to play it
            return _PandoraServicePlayHandler(self, tokens, what, token, score)

        else:
            # We got nothing
            return None
示例#14
0
    def _handler(self):
        """
        Pulls values from the decoder queue and handles them appropriately. Runs in
        its own thread.
        """
        LOG.info("Started decoding handler")
        while True:
            try:
                # Get a handle on the queue. This will be nulled out when we're
                # done.
                queue = self._decode_queue
                if queue is None:
                    break

                # Anything?
                if len(queue) > 0:
                    item = queue.popleft()
                    if item is None:
                        # A None denotes the end of the data so we look to
                        # decode what we've been given
                        LOG.info("Decoding audio")
                        self._notify(Notifier.WORKING)
                        self._output.append(self._decode())
                        self._notify(Notifier.IDLE)
                    elif isinstance(item, bytes):
                        # Something to feed the decoder
                        LOG.debug("Feeding %d bytes" % len(item))
                        self._feed_raw(item)
                    else:
                        LOG.warning("Ignoring junk on decode queue: %r" % (item,))

                    # Go around again
                    continue

            except Exception as e:
                # Be robust but log it
                LOG.error("Got an error in the decoder queue: %s" % (e,))

            # Don't busy-wait
            time.sleep(0.001)

        # And we're done!
        LOG.info("Stopped decoding handler")
示例#15
0
    def _respond(self, response):
        '''
        Given back the response to the user via the outputs.

        @type  response: str
        @param response:
            The text to send off to the user in the real world.
        '''
        # Give back nothing if we have no response
        if response is None:
            return None

        # Simply hand it to all the outputs
        for output in self._outputs:
            try:
                output.write(response)
            except:
                LOG.error("Failed to respond with %s:\n%s" %
                          (output, traceback.format_exc()))
示例#16
0
    def _handle(self, sckt):
        """
        Handle reading from a socket
        """
        LOG.info("Started new socket handler")

        # We'll build these up
        tokens = []
        cur = b''

        # Loop until they go away
        while True:
            c = sckt.recv(1)
            if c is None or len(c) == 0:
                LOG.info("Peer closed connection")
                return

            if len(cur) == 0 and ord(c) == 4:
                LOG.info("Got EOT")
                try:
                    sckt.close()
                except:
                    pass
                return

            if c in b' \t\n':
                if len(cur.strip()) > 0:
                    try:
                        tokens.append(Token(cur.strip().decode(), 1.0, True))
                    except Exception as e:
                        LOG.error("Error handling '%s': %s", cur, e)
                    cur = b''

                if c == b'\n':
                    if len(tokens) > 0:
                        if self._prefix:
                            tokens = self._prefix + tokens
                        self._output.append(tokens)
                        tokens = []

            else:
                cur += c
示例#17
0
    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()
                command = '(SayText "%s")\n' % text.replace('"', '')
                LOG.info("Sending: %s" % command.strip())
                self._notify(Notifier.WORKING)
                self._subproc.stdin.write(command)
                self._subproc.stdin.flush()

                # Wait for a bit, since festival can sometimes return with a
                # response right away
                while time.time() - start < len(text) * 0.05:
                    time.sleep(0.1)

                # And read in the result, which should mean it's done
                for line in self._readlines():
                    LOG.info("Received: %s" % line)

            except Exception as e:
                LOG.error("Failed to say '%s': %s" % (text, e))

            finally:
                self._notify(Notifier.IDLE)

        # Kill off the child
        try:
            self._subproc.terminate()
            self._subproc.communicate()
        except:
            pass
示例#18
0
文件: pandora.py 项目: iamsrp/dexter
    def play(self, token):
        """
        Set the song(s) to play.
        """
        # Out with the old
        if self._station is not None:
            try:
                self._player.end_station()
            except:
                pass
            self._pandora.delete_station(self._station.token)

        # In with the new
        self._station = self._pandora.create_station(search_token=token)
        LOG.error("Playing station %s", self._station)

        # And play it, in a new thread since it is blocking
        thread = Thread(target=self._play_station)
        thread.daemon = True
        thread.start()
示例#19
0
        def update_status(self, component, status):
            '''
            @see Notifier.update_status()
            '''
            # See if this is a speaker, if so then we have to account for that
            if component.is_speech:
                if status == Notifier.IDLE:
                    if component in self._speakers:
                        self._speakers.remove(component)
                        LOG.info("%s is no longer speaking" % (component, ))
                else:
                    self._speakers.add(component)
                    LOG.info("%s is speaking" % (component, ))

            # And tell the notifiers
            for notifier in self._notifiers:
                try:
                    notifier.update_status(component, status)
                except Exception as e:
                    LOG.error("Failed to update %s with (%s,%s): %s" %
                              (notifier, component, status, e))
示例#20
0
文件: music.py 项目: iamsrp/dexter
 def create_index():
     try:
         self._media_index = MusicIndex(dirname)
     except Exception as e:
         LOG.error("Failed to create music index: %s", e)
示例#21
0
文件: chronos.py 项目: iamsrp/dexter
    def handle(self):
        """
        @see Handler.handle()
        """
        # If we got nothing then grumble in a vaguely (un)helpful way
        if len(self._times) == 0:
            return Result(self, "I'm sorry, I didn't catch that", False, True)

        # Look for the times in the words
        try:
            # Try to find the various units of time in something like:
            #   seven hours and fifteen days
            indices = []
            for (words, seconds) in _PERIODS:
                for word in words:
                    if word in self._times:
                        index = self._times.index(word)
                        LOG.info("Found '%s' at index %d" % (word, index))
                        indices.append((index, seconds))

            # Anything?
            if len(indices) == 0:
                raise ValueError("Found no units in %s" %
                                 ' '.join(self._times))

            # Put them in order and look for the numbers, accumulating into the
            # total
            total = 0
            indices = sorted(indices, key=lambda pair: pair[0])
            prev = 0
            for (index, seconds) in indices:
                # Get the words leading up to the time unit, these should be the
                # number (possibly with other bits)
                value = self._times[prev:index]

                # Strip off any "and"s
                while value[0] == "and":
                    value = value[1:]

                # Now try to parse it
                LOG.info("Parsing %s" % (value, ))
                amount = parse_number(' '.join(value))

                # Accumulate
                total = amount * seconds

            LOG.info("Got value of %s seconds from %s" % (total, self._times))

            # Handle accordingly
            if total > 0:
                # That's a valid timer value. Set the timer and say we did it.
                self.service.add_timer(total)
                return Result(
                    self, "Okay, timer set for %s" % (' '.join(self._times), ),
                    False, True)
            else:
                # We can't have an alarm in the past
                return Result(
                    self, "%s was not a time in the future" %
                    (' '.join(self._times), ), False, True)

        except Exception:
            LOG.error("Problem parsing timer '%s':\n%s" %
                      (self._times, traceback.format_exc()))
            return Result(
                self, "Sorry, I don't know how to set the timer for %s" %
                (' '.join(self._times), ), False, True)
示例#22
0
文件: pandora.py 项目: iamsrp/dexter
 def input(self, cmd, song):
     LOG.error("Got command '%s' for song '%s'", cmd, song)
示例#23
0
文件: __init__.py 项目: iamsrp/dexter
    def _handle(self, tokens):
        """
        Handle a list of L{Token}s from the input.

        :type  tokens: list(L{Token})
        :param tokens:
            The tokens to handle.

        :rtype: str
        :return:
            The textual response, if any.
        """
        # Give back nothing if we have no tokens
        if tokens is None:
            return None

        # Get the words from this text
        words = [
            to_letters(token.element).lower() for token in tokens
            if token.verbal
        ]
        LOG.info("Handling: \"%s\"" % ' '.join(words))

        # Anything?
        if len(words) == 0:
            LOG.info("Nothing to do")
            return None

        # See if the key-phrase is in the tokens and use it to determine the
        # offset of the command.
        offset = None
        for key_phrase in self._key_phrases:
            try:
                offset = (list_index(words, key_phrase) + len(key_phrase))
                LOG.info("Found key-phrase %s at offset %d in %s" %
                         (key_phrase, offset - len(key_phrase), words))
            except ValueError:
                pass

        # If we don't have an offset then we haven't found a key-phrase. Try a
        # fuzzy match, but only if we are not waiting for someone to say
        # something after priming with the keypharse.
        now = time.time()
        if now - self._last_keyphrase_only < Dexter._KEY_PHRASE_ONLY_TIMEOUT:
            # Okay, treat what we got as the command, so we have no keypharse
            # and so the offset is zero.
            LOG.info("Treating %s as a command", (words, ))
            offset = 0
        elif offset is None:
            # Not found, but if we got something which sounded like the key
            # phrase then set the offset this way too. This allows someone to
            # just say the keyphrase and we can handle it by waiting for them to
            # then say something else.
            LOG.info("Key pharses %s not found in %s" %
                     (self._key_phrases, words))

            # Check for it being _almost_ the key phrase
            ratio = 75
            what = ' '.join(words)
            for key_phrase in self._key_phrases:
                if fuzz.ratio(' '.join(key_phrase), what) > ratio:
                    offset = len(key_phrase)
                    LOG.info("Fuzzy-matched key-pharse %s in %s" %
                             (key_phrase, words))

        # Anything?
        if offset is None:
            return None

        # If we have the keyphrase and no more then we just got a priming
        # command, we should be ready for the rest of it to follow
        if offset == len(words):
            # Remember that we were primed
            LOG.info("Just got keyphrase")
            self._last_keyphrase_only = now

            # To drop the volume down so that we can hear what's coming. We'll
            # need a capturing function to do this.
            def make_fn(v):
                def fn():
                    set_volume(v)
                    return None

                return fn

            try:
                # Get the current volume
                volume = get_volume()

                # If the volume is too high then we will need to lower it
                if volume > Dexter._LISTENING_VOLUME:
                    # And schedule an event to bump it back up in a little while
                    LOG.info(
                        "Lowering volume from %d to %d while we wait for more",
                        volume, Dexter._LISTENING_VOLUME)
                    set_volume(Dexter._LISTENING_VOLUME)
                    heapq.heappush(
                        self._timer_events,
                        TimerEvent(now + Dexter._KEY_PHRASE_ONLY_TIMEOUT,
                                   runnable=make_fn(volume)))
            except Exception as e:
                LOG.warning("Failed to set listening volume: %s", e)

            # Nothing more to do until we hear the rest of the command
            return None

        # Special handling if we have active outputs and someone said "stop"
        if offset == len(words) - 1 and words[-1] == "stop":
            stopped = False
            for output in self._outputs:
                # If this output is busy doing something then we tell it to stop
                # doing that thing with interrupt(). (stop() means shutdown.)
                if output.status in (Notifier.ACTIVE, Notifier.WORKING):
                    try:
                        # Best effort
                        LOG.info("Interrupting %s", output)
                        output.interrupt()
                        stopped = True
                    except:
                        pass

            # If we managed to stop one of the output components then we're
            # done. We don't want another component to pick up this command.
            if stopped:
                return None

        # See which services want them
        handlers = []
        for service in self._services:
            try:
                # This service is being woken to so update the status
                self._state.update_status(service, Notifier.ACTIVE)

                # Get any handler from the service for the given tokens
                handler = service.evaluate(tokens[offset:])
                if handler is not None:
                    handlers.append(handler)

            except:
                LOG.error(
                    "Failed to evaluate %s with %s:\n%s" %
                    ([str(token)
                      for token in tokens], service, traceback.format_exc()))
                return "Sorry, there was a problem"

            finally:
                # This service is done working now
                self._state.update_status(service, Notifier.IDLE)

        # Anything?
        if len(handlers) == 0:
            return "I'm sorry, I don't know how to help with that"

        # Okay, put the handlers into order of belief and try them. Notice that
        # we want the higher beliefs first so we reverse the sign in the key.
        handlers = sorted(handlers, key=lambda h: -h.belief)

        # If any of the handlers have marked themselves as exclusive then we
        # restrict the list to just them. Hopefully there will be just one but,
        # if there are more, then they should be in the order of belief so we'll
        # pick the "best" exclusive one first.
        if True in [h.exclusive for h in handlers]:
            handlers = [h for h in handlers if h.exclusive]

        # Now try each of the handlers
        response = []
        error_service = None
        for handler in handlers:
            try:
                # Update the status of this handler's service to "working" while
                # we call it
                self._state.update_status(handler.service, Notifier.WORKING)

                # Invoked the handler and see what we get back
                result = handler.handle()
                if result is None:
                    continue

                # Accumulate into the resultant text
                if result.text is not None:
                    response.append(result.text)

                # Stop here?
                if handler.exclusive or result.exclusive:
                    break

            except:
                error_service = handler.service
                LOG.error(
                    "Handler %s with tokens %s for service %s yielded:\n%s" %
                    (handler, [str(t) for t in handler.tokens
                               ], handler.service, traceback.format_exc()))

            finally:
                # This service is done working now
                self._state.update_status(handler.service, Notifier.IDLE)

        # Give back whatever we had, if anything
        if error_service:
            return "Sorry, there was a problem with %s" % (error_service, )
        elif len(response) > 0:
            return '\n'.join(response)
        else:
            return None
示例#24
0
    def _handle(self, tokens):
        '''
        Handle a list of L{Token}s from the input.

        @type  tokens: list(L{Token})
        @param tokens:
            The tokens to handle.

        @rtype: str
        @return:
            The textual response, if any.
        '''
        # Give back nothing if we have no tokens
        if tokens is None:
            return None

        # See if the key-phrase is in the tokens and use it to determine the
        # offset of the command.
        words = [to_letters(token.element).lower() for token in tokens]
        offset = None
        for key_phrase in self._key_phrases:
            try:
                offset = (list_index(words, key_phrase) + len(key_phrase))
                LOG.info("Found key-pharse %s at offset %d in %s" %
                         (key_phrase, offset - len(key_phrase), words))
            except ValueError:
                pass

        # If we don't have an offset then we haven't found a key-phrase. Try a
        # fuzzy match, but only if we are not waiting for someone to say
        # something after priming with the keypharse.
        now = time.time()
        if now - self._last_keyphrase_only < Dexter._KEY_PHRASE_ONLY_TIMEOUT:
            # Okay, treat what we got as the command, so we have no keypharse
            # and so the offset is zero.
            LOG.info("Treating %s as a command", (words, ))
            offset = 0
        elif offset is None:
            # Not found, but if we got something which sounded like the key
            # phrase then set the offset this way too. This allows someone to
            # just say the keyphrase and we can handle it by waiting for them to
            # then say something else.
            LOG.info("Key pharses %s not found in %s" %
                     (self._key_phrases, words))

            # Check for it being _almost_ the key phrase
            ratio = 75
            what = ' '.join(words)
            for key_phrase in self._key_phrases:
                if fuzz.ratio(' '.join(key_phrase), what) > ratio:
                    offset = len(key_phrase)
                    LOG.info("Fuzzy-matched key-pharse %s in %s" %
                             (key_phrase, words))

        # Anything?
        if offset is None:
            return None

        # If we have the keyphrase and no more then we just got a priming
        # command, we should be ready for the rest of it to follow
        if offset == len(words):
            # Remember that we were primed
            self._last_keyphrase_only = now

            # Drop the volume down on any services so that we can hear what's
            # coming. We'll need a capturing function to do this.
            def make_fn(s, v):
                def fn():
                    s.set_volume(v)
                    return None

            # Look at all the services and see which have volume controls
            for service in self._services:
                if not (hasattr(service, 'get_volume')
                        and hasattr(service, 'set_volume')):
                    continue

                # Get the current volume
                try:
                    volume = service.get_volume()
                except Exception:
                    continue

                # If it's too loud then drop it down, and schedule an event to
                # bump it back up in a little bit,
                if volume > Dexter._LISTENING_VOLUME:
                    service.set_volume(Dexter._LISTENING_VOLUME)
                    heapq.heappush(
                        self._timer_events.put,
                        TimerEvent(now + Dexter._KEY_PHRASE_ONLY_TIMEOUT,
                                   make_fn(service, volume)))

            # Nothing more to do until we hear the rest of the command
            return None

        # See which services want them
        handlers = []
        for service in self._services:
            try:
                # This service is being woken to so update the status
                self._state.update_status(service, Notifier.ACTIVE)

                # Get any handler from the service for the given tokens
                handler = service.evaluate(tokens[offset:])
                if handler is not None:
                    handlers.append(handler)

            except:
                LOG.error(
                    "Failed to evaluate %s with %s:\n%s" %
                    ([str(token)
                      for token in tokens], service, traceback.format_exc()))
                return "Sorry, there was a problem"

            finally:
                # This service is done working now
                self._state.update_status(service, Notifier.IDLE)

        # Anything?
        if len(handlers) == 0:
            return "I'm sorry, I don't know how to help with that"

        # Okay, put the handlers into order of belief and try them. Notice that
        # we want the higher beliefs first so we reverse the sign in the key.
        handlers = sorted(handlers, key=lambda h: -h.belief)

        # If any of the handlers have marked themselves as exclusive then we
        # restrict the list to just them. Hopefully there will be just one but,
        # if there are more, then they should be in the order of belief so we'll
        # pick the "best" exclusive one first.
        if True in [h.exclusive for h in handlers]:
            handlers = [h for h in handlers if h.exclusive]

        # Now try each of the handlers
        response = []
        error = False
        for handler in handlers:
            try:
                # Update the status of this handler's service to "working" while
                # we call it
                self._state.update_status(handler.service, Notifier.WORKING)

                # Invoked the handler and see what we get back
                result = handler.handle()
                if result is None:
                    continue

                # Accumulate into the resultant text
                if result.text is not None:
                    response.append(result.text)

                # Stop here?
                if handler.exclusive or result.exclusive:
                    break

            except:
                error = True
                LOG.error(
                    "Handler %s with tokens %s for service %s yielded:\n%s" %
                    (handler, [str(t) for t in handler.tokens
                               ], handler.service, traceback.format_exc()))

            finally:
                # This service is done working now
                self._state.update_status(handler.service, Notifier.IDLE)

        # Give back whatever we had, if anything
        if error:
            return "Sorry, there was a problem"
        elif len(response) > 0:
            return '\n'.join(response)
        else:
            return None
示例#25
0
    def _get_handler_for(self, tokens, platform_match, genre, artist,
                         song_or_album):
        """
        @see MusicService._get_handler_for()
        """
        # Do nothing if we have no name
        if song_or_album is None or len(song_or_album) == 0:
            return None

        # Normalise to strings
        name = ' '.join(song_or_album).lower()
        if artist is None or len(artist) == 0:
            artist = None
        else:
            artist = ' '.join(artist).lower()

        # We will put all the track URIs in here
        uris = []

        # Search by track name then album name, these are essentially the same
        # logic
        for which in ('track', 'album'):
            LOG.info("Looking for '%s'%s as a %s", name,
                     " by '%s'" % artist if artist else '', which)

            # This is the key in the results
            plural = which + 's'

            # Try using the song_or_album as the name
            result = self._spotify.search(name, type=which)
            if not result:
                LOG.info("No results")
                continue

            # Did we get back any tracks
            if plural not in result:
                LOG.error("%s was not in result keys: %s", plural,
                          result.keys())
                continue

            # We got some results back, let's assign scores to them all
            results = result[plural]
            matches = []
            for item in results.get('items', []):
                # It must have a uri
                if 'uri' not in item and item['uri']:
                    LOG.error("No URI in %s", item)

                # Look at all the candidate entries
                if 'name' in item:
                    # See if this is better than any existing match
                    name_score = fuzz.ratio(name, item['name'].lower())
                    LOG.debug("'%s' matches '%s' with score %d", item['name'],
                              name, name_score)

                    # Check to make sure that we have an artist match as well
                    if artist is None:
                        # Treat as a wildcard
                        artist_score = 100
                    else:
                        artist_score = 0
                        for entry in item.get('artists', []):
                            score = fuzz.ratio(artist,
                                               entry.get('name', '').lower())
                            LOG.debug("Artist match score for '%s' was %d",
                                      entry.get('name', ''), score)
                            if score > artist_score:
                                artist_score = score
                    LOG.debug("Artist match score was %d", artist_score)

                    # Only consider cases where the scores look "good enough"
                    if name_score > 75 and artist_score > 75:
                        LOG.debug("Adding match")
                        matches.append((item, name_score, artist_score))

            # Anything?
            if len(matches) > 0:
                LOG.debug("Got %d matches", len(matches))

                # Order them accordingly
                matches.sort(key=lambda e: (e[1], e[2]))

                # Now, pick the top one
                best = matches[0]
                item = best[0]
                LOG.debug("Best match was: %s", item)

                # Extract the info
                item_name = item.get('name', None) or name
                artists = item.get('artists', [])
                artist_name = (artists[0].get('name', None)
                               if len(artists) > 0 else None) or artist

                # Description of what we are playing
                what = item_name if item_name else name
                if artist_name:
                    what += " by " + artist_name
                what += " on Spotify"

                # The score is the geometric value of the two
                score = sqrt(best[1] * best[1] + best[2] * best[2]) / 100.0

                # The should be here
                assert 'uri' in item, "Missing URI in %s" % (item, )
                uri = item['uri']

                # If we are an album then grab the track URIs
                if which == 'album':
                    tracks = self._spotify.album_tracks(uri)
                    if tracks and 'items' in tracks:
                        uris = [track['uri'] for track in tracks['items']]
                else:
                    # Just the track
                    uris = [uri]

                # And we're done
                break

        # Otherwise assume that it's an artist
        if len(uris) == 0 and artist is None:
            LOG.info("Looking for '%s' as an artist", name)
            result = self._spotify.search(name, type='artist')
            LOG.debug("Got: %s", result)

            if result and 'artists' in result and 'items' in result['artists']:
                items = sorted(result['artists']['items'],
                               key=lambda entry: fuzz.ratio(
                                   name,
                                   entry.get('name', '').lower()),
                               reverse=True)

                # Look at the best one, if any
                LOG.debug("Got %d matches", len(items))
                if len(items) > 0:
                    match = items[0]
                    who = match['name']
                    what = "%s on Spotify" % (who, )
                    score = fuzz.ratio(who.lower(), name)

                    # Find all their albums
                    if 'uri' in match:
                        LOG.debug("Got match: %s", match['uri'])
                        artist_albums = self._spotify.artist_albums(
                            match['uri'])
                        for album in artist_albums.get('items', []):
                            # Append all the tracks
                            LOG.debug("Looking at album: %s", album)
                            if 'uri' in album:
                                tracks = self._spotify.album_tracks(
                                    album['uri'])
                                if tracks and 'items' in tracks:
                                    LOG.debug(
                                        "Adding tracks: %s",
                                        ' '.join(track['name']
                                                 for track in tracks['items']))
                                    uris.extend([
                                        track['uri']
                                        for track in tracks['items']
                                    ])

        # And now we can give it back, if we had something
        if len(uris) > 0:
            return _SpotifyServicePlayHandler(self, tokens, what, uris, score)
        else:
            # We got nothing
            return None
示例#26
0
文件: util.py 项目: blundercon/dexter
def parse_number(words):
    '''
    Turn a set of words into a number. These might be complex ("One thousand
    four hundred and eleven") or simple ("Seven").

    >>> parse_number('one')
    1
    >>> parse_number('one point eight')
    1.8
    >>> parse_number('minus six')
    -6
    >>> parse_number('minus four point seven eight nine')
    -4.789

    @type  words: str
    @parse words:
        The string words to parse. E.g. C{'twenty seven'}.
    '''
    # Sanity
    if words is None:
        return None

    # Make sure it's a string
    words = str(words)

    # Trim surrounding whitespace
    words = words.strip()

    # Not a lot we can do if we have no words
    if len(words) == 0:
        return None

    # First try to parse the string as an integer or a float directly
    if not re.search(r'\s', words):
        try:
            return int(words)
        except:
            pass
        try:
            return float(words)
        except:
            pass

    # Sanitise it since we're now going to attempt to parse it. Collapse
    # multiple spaces to one and strip out non-letters
    words = ' '.join(to_letters(s) for s in re.split(r'\s+', words))
    LOG.debug("Parsing '%s'" % (words, ))

    # Recheck for empty
    if words == '':
        return None

    # See if we have to negate the result
    mult = 1
    for neg in ("minus ", "negative "):
        if words.startswith(neg):
            words = words[len(neg):]
            mult = -1
            break

    # Look for "point" in the words since it might be "six point two" or
    # something
    if ' point ' in words:
        # Determine the integer and decimal portions
        (integer, decimal) = words.split(' point ', 1)
        LOG.debug("'%s' becomes '%s' and '%s'" % (words, integer, decimal))

        # Parsing the whole number is easy enough
        whole = parse_number(integer)
        if whole is None:
            return None

        # S;plit up the digits to parse them.
        digits = numpy.array(
            [parse_number(digit) for digit in decimal.split(' ')])
        if None in digits        or \
           numpy.any(digits < 0) or \
           numpy.any(digits > 9):
            LOG.error("'%s' was not a valid decimal" % (words, ))
            return None

        # Okay, use some cheese to parse into a float
        return mult * float('%d.%s' % (whole, ''.join(str(d) for d in digits)))

    else:
        # No ' point ' in it, parse directly
        try:
            return mult * _WORDS_TO_NUMBERS.parse(words)
        except Exception as e:
            LOG.error("Failed to parse '%s': %s" % (words, e))
            return None
示例#27
0
    def _handler(self):
        """
        Pulls values from the decoder queue and handles them appropriately. Runs in
        its own thread.
        """
        # Whether we are skipping the current input
        gobble = False

        LOG.info("Started decoding handler")
        while True:
            try:
                # Get a handle on the queue. This will be nulled out when we're
                # done.
                queue = self._decode_queue
                if queue is None:
                    break

                # Anything?
                if len(queue) > 0:
                    item = queue.popleft()
                    if item is None:
                        # A None denotes the end of the data so we look to
                        # decode what we've been given if we're not throwing it
                        # away.
                        if gobble:
                            LOG.info("Dropped audio")
                        else:
                            LOG.info("Decoding audio")
                            self._notify(Notifier.WORKING)
                            self._output.append(self._decode())
                            self._notify(Notifier.IDLE)
                    elif isinstance(item, float):
                        # This is the timestamp of the clip. If it's too old
                        # then we throw it away.
                        age = time.time() - item
                        if int(age) > 0:
                            LOG.info("Upcoming audio clip is %0.2fs old" %
                                     (age, ))
                        gobble = age > self._GOBBLE_LIMIT
                    elif isinstance(item, bytes):
                        # Something to feed the decoder
                        if gobble:
                            LOG.debug("Ignoring %d bytes" % len(item))
                        else:
                            LOG.debug("Feeding %d bytes" % len(item))
                            self._feed_raw(item)
                    else:
                        LOG.warning("Ignoring junk on decode queue: %r" %
                                    (item, ))

                    # Go around again
                    continue

            except Exception as e:
                # Be robust but log it
                LOG.error("Got an error in the decoder queue: %s" % (e, ))

            # Don't busy-wait
            time.sleep(0.001)

        # And we're done!
        LOG.info("Stopped decoding handler")