Beispiel #1
0
    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)
Beispiel #2
0
    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
Beispiel #3
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()
Beispiel #4
0
    def _start(self):
        """
        @see Startable._start()
        """
        # This is what we need to be able to do
        scope = ','.join(('user-library-read', 'user-read-playback-state',
                          'user-modify-playback-state'))

        # Create the authorization manager, and then use that to create the
        # client
        auth_manager = SpotifyOAuth(client_id=self._client_id,
                                    client_secret=self._client_secret,
                                    redirect_uri=self._redirect_uri,
                                    scope=scope)
        self._spotify = Spotify(auth_manager=auth_manager)

        # See what devices we have to hand
        try:
            if self._device_name is not None:
                LOG.info("Looking for device named '%s'", self._device_name)

            devices = self._spotify.devices()
            for device in devices['devices']:
                # Say what we see
                name = device['name']
                id_ = device['id']
                type_ = device['type']
                active = device['is_active']
                vol = device['volume_percent']
                LOG.info("Found %sactive %s device: '%s'",
                         '' if active else 'in', type_, name)

                # See if we're looking for a specific device, if not just snoop
                # the volume from the first active one
                if self._device_name is not None:
                    if name == self._device_name:
                        LOG.info("Matched '%s' to ID '%s'", name, id_)
                        self._device_id = id_
                        self._volume = vol / 100.0 * MAX_VOLUME
                else:
                    if active and self._volume is None:
                        self._volume = vol / 100.0 * MAX_VOLUME

        except Exception as e:
            LOG.warning("Unable to determine active Spoify devices: %s", e)

        # If we were looking for a specific device then make sure that we found
        # it in the list
        if self._device_name is not None and self._device_id is None:
            raise ValueError("Failed to find device with name '%s'" %
                             (self._device_name, ))
Beispiel #5
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")
Beispiel #6
0
    def _decode(self):
        """
        @see AudioInput._decode()
        """
        if self._context is None:
            # No context means no tokens
            LOG.warning("Had no stream context to close")
            tokens = []
        else:
            # Finish up by finishing the decoding
            words = self._context.finishStream()
            LOG.info("Got: %s" % (words, ))
            self._context = None

            # And tokenize
            tokens = [
                Token(word.strip(), 1.0, True) for word in words.split(' ')
                if len(word.strip()) > 0
            ]
        return tokens
Beispiel #7
0
    def _controller(self):
        """
        The main controller thread. This handles keeping things going in the
        background. It does not do much heavy lifting however.
        """
        while True:
            # Tum-ti-tum
            time.sleep(0.1)

            # Do nothing while the song is playing.
            if get_pygame().mixer.music.get_busy():
                continue

            # Get the next song to play, this will block
            song = self._queue.get()
            LOG.info("Playing %s", song)

            # Attempt to play it
            try:
                get_pygame().mixer.music.load(song)
                get_pygame().mixer.music.play()
            except Exception as e:
                LOG.warning("Failed to play %s: %s", song, e)
Beispiel #8
0
    def sound_alarm(self, timer):
        """
        Ring the alarm for the given timer.
        """
        # Let the terminal know
        LOG.info("DING DING DING!!! Timer '%s' has expired..." % (timer, ))

        # Play any sound...
        if self._timer_audio is not None:
            LOG.info("Playing timer sound")

            # ...for about 5 seconds
            end = time.time() + 5
            while time.time() < end:
                try:
                    self._timer_audio.play()
                except Exception as e:
                    LOG.warning("Failed to play timer sound: %s", e)

        # And remove the timer (this should not fail but...)
        try:
            self._timers.remove(timer)
        except:
            pass
Beispiel #9
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

        # 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
Beispiel #10
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")