def translatorItem(query, source, target, str):
    item = makeItem(
        query, "View in Google Translate to {}".format(lang.toName(target)),
        "https://translate.google.com/#{}/{}/{}".format(
            source, target, quote_url(str, safe='')))
    item.addAction(
        UrlAction(
            "View in Google Translate",
            "https://translate.google.com/#{}/{}/{}".format(
                source, target, quote_url(str, safe=''))))
    return item
Exemple #2
0
    def from_music_service(cls, music_service, content_dict):
        """Return an element instantiated from the information that a music
        service has (alternative constructor)

        Args:
            music_service (MusicService): The music service that content_dict
                originated from
            content_dict (OrderedDict): The data to instantiate the music
                service item from

        Returns:
            MusicServiceItem: A MusicServiceItem instance
        """
        # Form the item_id
        quoted_id = quote_url(content_dict["id"].encode("utf-8"))
        # The hex prefix remains a mistery for now
        item_id = "0fffffff{}".format(quoted_id)
        # Form the uri
        is_track = cls == get_class("MediaMetadataTrack")
        uri = form_uri(item_id, music_service, is_track)
        # Form resources and get desc
        resources = [DidlResource(uri=uri, protocol_info="DUMMY")]
        desc = music_service.desc
        return cls(item_id,
                   desc,
                   resources,
                   uri,
                   content_dict,
                   music_service=music_service)
Exemple #3
0
def url_escape_path(path):
    """Escape a string value for a URL request path.

    Args:
        str: The path to escape

    Returns:
        str: The escaped path

    >>> url_escape_path("Foo, bar & baz / the hackers")
    u'Foo%2C%20bar%20%26%20baz%20%2F%20the%20hackers'
    """
    # Using 'safe' arg does not seem to work for python 2.6
    return quote_url(path.encode("utf-8")).replace("/", "%2F")
def responseToItem(response, str, source, target, query):
    translation = response.translations[0]
    if source == "auto" and len(
            targets) > 1 and target == translation.detected_language_code:
        return None

    item = makeItem(
        query, translation.translated_text, "Translated to {} from {}".format(
            lang.toName(target),
            lang.toName(translation.detected_language_code if source ==
                        "auto" else source)))
    item.addAction(ClipAction("Copy to clipboard", item.text))
    item.addAction(
        UrlAction(
            "View in Google Translate",
            "https://translate.google.com/#{}/{}/{}".format(
                source, target, quote_url(str, safe=''))))
    return item
    def sonos_uri_from_id(self, item_id):
        """Get a uri which can be sent for playing.

        Args:
            item_id (str): The unique id of a playable item for this music
                service, such as that returned in the metadata from
                `get_metadata`, eg ``spotify:track:2qs5ZcLByNTctJKbhAZ9JE``

        Returns:
            str: A URI of the form: ``soco://spotify%3Atrack
            %3A2qs5ZcLByNTctJKbhAZ9JE?sid=2311&sn=1`` which encodes the
            ``item_id``, and relevant data from the account for the music
            service. This URI can be sent to a Sonos device for playing,
            and the device itself will retrieve all the necessary metadata
            such as title, album etc.
        """
        # Real Sonos URIs look like this:
        # x-sonos-http:tr%3a92352286.mp3?sid=2&flags=8224&sn=4 The
        # extension (.mp3) presumably comes from the mime-type returned in a
        # MusicService.get_metadata() result (though for Spotify the mime-type
        # is audio/x-spotify, and there is no extension. See
        # http://musicpartners.sonos.com/node/464 for supported mime-types and
        # related extensions). The scheme (x-sonos-http) presumably
        # indicates how the player is to obtain the stream for playing. It
        # is not clear what the flags param is used for (perhaps bitrate,
        # or certain metadata such as canSkip?). Fortunately, none of these
        # seems to be necessary. We can leave them out, (or in the case of
        # the scheme, use 'soco' as dummy text, and the players still seem
        # to do the right thing.

        # quote_url will break if given unicode on Py2.6, and early 2.7. So
        # we need to encode.
        item_id = quote_url(item_id.encode("utf-8"))
        # Add the account info to the end as query params

        # FIXME we no longer have accounts, so for now the serial
        # numbers is assumed to be 0. Originally it was read from
        # account.serial_numbers
        # account = self.account

        result = "soco://{}?sid={}&sn={}".format(item_id, self.service_id, 0)
        return result
Exemple #6
0
def __get_link_url(channel_uid: str, item_name: str) -> str:
    # rest/links/ endpoint needs the channel to be url encoded
    # (AAAA:BBBB:CCCC:0#NAME -> AAAA%3ABBBB%3ACCCC%3A0%23NAME)
    # otherwise the REST-api returns HTTP-Status 500 InternalServerError
    return 'links/' + quote_url(f"{item_name}/{channel_uid}")
Exemple #7
0
def build_stocklists(source_data: list, stocklist: list = None, style_summary: list = None) -> None:
    """
    Assemble JSON data from stock list export into lists for subsequent writing to selected file format

    Args:
        source_data: Source data unpacked from JSON
        stocklist:
        style_summary:

    Returns:

    """
    # pylint: disable=R0912,R0914
    thresholds = [
        {'description': 'Undated beers', 'ends': '0000-00-00'},
        {'description': 'Expired beers', 'ends': date.today().strftime('%Y-%m-%d')},
        {'description': 'Within one month', 'ends': (date.today() + relativedelta(months=+1)).strftime('%Y-%m-%d')},
        {'description': 'Within two months', 'ends': (date.today() + relativedelta(months=+2)).strftime('%Y-%m-%d')},
        {'description': 'More than two months away'}
    ]
    expiry_sets = [{} for _ in range(len(thresholds))]  # type: List[Dict]
    styles = {}  # type: Dict
    list_has_quantities = False

    for item in source_data:
        style = item['beer_type'].split(' -')[0].strip()

        if 'quantity' in item:
            if style not in styles:
                styles[style] = 0
            styles[style] += int(item['quantity'])
            list_has_quantities = True
        else:
            if style not in styles:
                styles[style] = None

        # Strictly only needed for full stocklist
        due = item.get('best_by_date_iso', '0000-00-00')
        for idx, threshold in enumerate(thresholds):
            if 'ends' in threshold and due <= threshold['ends']:
                if style not in expiry_sets[idx]:
                    expiry_sets[idx][style] = []
                expiry_sets[idx][style].append(item)
                break

            if 'ends' not in threshold:
                if style not in expiry_sets[idx]:
                    expiry_sets[idx][style] = []
                expiry_sets[idx][style].append(item)

    if stocklist is not None:

        stocklist.append(['Expiry', 'Type', '#', 'Brewery', 'Beverage', 'Subtype', 'ABV', 'Serving', 'BBE'])
        for k, expiry_set in enumerate(expiry_sets):
            if expiry_sets[k]:
                distinct_beer_count = sum([sum([1 for _ in expiry_set[style]]) for style in expiry_set])
                if list_has_quantities:
                    quantity = sum([sum([int(d['quantity']) for d in expiry_set[style]]) for style in expiry_set])
                    stocklist.append(
                        [
                            '%s: %d %s of %d %s' % (
                                thresholds[k]['description'],
                                quantity,
                                plural('item', quantity),
                                distinct_beer_count,
                                plural('beer', distinct_beer_count),
                            )
                        ]
                    )
                else:
                    stocklist.append(
                        [
                            '%s: %d %s' % (
                                thresholds[k]['description'],
                                distinct_beer_count,
                                plural('beer', distinct_beer_count),
                            )
                        ]
                    )

                # Sort styles by name
                for style in sorted(expiry_sets[k]):
                    first = True
                    drinks = expiry_sets[k][style]
                    drinks.sort(key=lambda d: (d['brewery_name'], d['beer_name']))
                    for item in drinks:
                        bbd = item.get('best_by_date_iso', '')
                        url = 'https://untappd.com/search?q='
                        url = url + quote_url(item["brewery_name"] + ' ' + item["beer_name"])
                        stocklist.append(
                            [
                                '',
                                style if first else '',
                                item.get('quantity', ''),
                                item['brewery_name'],
                                LinkedText(item['beer_name'], url),
                                item['beer_type'],
                                '%.1f%%' % float(item['beer_abv']),
                                item.get('container', ''),
                                bbd if bbd != '0000-00-00' else '',
                            ]
                        )
                        first = False

                if len(expiry_sets) > k + 1:
                    stocklist.append([''])  # space before next

        if list_has_quantities:
            stocklist.append(
                [
                    'TOTAL: %d items of %d beers' % (
                        sum([int(item['quantity']) for item in source_data]),
                        len(source_data)
                    )
                ]
            )

    if style_summary is not None:
        style_list = []
        for style, style_count in styles.items():
            style_list.append({'style': style, 'count': style_count})
        style_list.sort(key=lambda b: (0 if b['count'] is None else (0 - b['count']), b['style']))
        style_summary.append(['Styles'])
        for style_row in style_list:
            style_summary.append(
                [style_row['style']] if style_row['count'] is None else [style_row['style'], style_row['count']]
            )
Exemple #8
0
    def get_music_library_information(
        self,
        search_type,
        start=0,
        max_items=100,
        full_album_art_uri=False,
        search_term=None,
        subcategories=None,
        complete_result=False,
    ):
        """Retrieve music information objects from the music library.

        This method is the main method to get music information items, like
        e.g. tracks, albums etc., from the music library with. It can be used
        in a few different ways:

        The ``search_term`` argument performs a fuzzy search on that string in
        the results, so e.g calling::

            get_music_library_information('artists', search_term='Metallica')

        will perform a fuzzy search for the term 'Metallica' among all the
        artists.

        Using the ``subcategories`` argument, will jump directly into that
        subcategory of the search and return results from there. So. e.g
        knowing that among the artist is one called 'Metallica', calling::

            get_music_library_information('artists',
                                          subcategories=['Metallica'])

        will jump directly into the 'Metallica' sub category and return the
        albums associated with Metallica and::

            get_music_library_information('artists',
                                          subcategories=['Metallica', 'Black'])

        will return the tracks of the album 'Black' by the artist 'Metallica'.
        The order of sub category types is: Genres->Artists->Albums->Tracks.
        It is also possible to combine the two, to perform a fuzzy search in a
        sub category.

        The ``start``, ``max_items`` and ``complete_result`` arguments all
        have to do with paging of the results. By default the searches are
        always paged, because there is a limit to how many items we can get at
        a time. This paging is exposed to the user with the ``start`` and
        ``max_items`` arguments. So calling::

            get_music_library_information('artists', start=0, max_items=100)
            get_music_library_information('artists', start=100, max_items=100)

        will get the first and next 100 items, respectively. It is also
        possible to ask for all the elements at once::

            get_music_library_information('artists', complete_result=True)

        This will perform the paging internally and simply return all the
        items.

        Args:

            search_type (str):
                The kind of information to retrieve. Can be one of:
                ``'artists'``, ``'album_artists'``, ``'albums'``,
                ``'genres'``, ``'composers'``, ``'tracks'``, ``'share'``,
                ``'sonos_playlists'``, or ``'playlists'``, where playlists
                are the imported playlists from the music library.
            start (int, optional): starting number of returned matches
                (zero based). Default 0.
            max_items (int, optional): Maximum number of returned matches.
                Default 100.
            full_album_art_uri (bool):
                whether the album art URI should be absolute (i.e. including
                the IP address). Default `False`.
            search_term (str, optional):
                a string that will be used to perform a fuzzy search among the
                search results. If used in combination with subcategories,
                the fuzzy search will be performed in the subcategory.
            subcategories (str, optional):
                A list of strings that indicate one or more subcategories to
                dive into.
            complete_result (bool): if `True`, will disable
                paging (ignore ``start`` and ``max_items``) and return all
                results for the search.

        Warning:
            Getting e.g. all the tracks in a large collection might
            take some time.


        Returns:
             `SearchResult`: an instance of `SearchResult`.

        Note:
            * The maximum numer of results may be restricted by the unit,
              presumably due to transfer size consideration, so check the
              returned number against that requested.

            * The playlists that are returned with the ``'playlists'`` search,
              are the playlists imported from the music library, they
              are not the Sonos playlists.

        Raises:
             `SoCoException` upon errors.
        """
        search = self.SEARCH_TRANSLATION[search_type]

        # Add sub categories
        # sub categories are not allowed when searching shares
        if subcategories is not None and search_type != "share":
            for category in subcategories:
                search += "/" + url_escape_path(really_unicode(category))
        # Add fuzzy search
        if search_term is not None:
            if search_type == "share":
                # Don't insert ":" and don't escape "/" (so can't use url_escape_path)
                search += quote_url(
                    really_unicode(search_term).encode("utf-8"))
            else:
                search += ":" + url_escape_path(really_unicode(search_term))

        item_list = []
        metadata = {"total_matches": 100000}
        while len(item_list) < metadata["total_matches"]:
            # Change start and max for complete searches
            if complete_result:
                start, max_items = len(item_list), 100000

            # Try and get this batch of results
            try:
                response, metadata = self._music_lib_search(
                    search, start, max_items)
            except SoCoUPnPException as exception:
                # 'No such object' UPnP errors
                if exception.error_code == "701":
                    return SearchResult([], search_type, 0, 0, None)
                else:
                    raise exception

            # Parse the results
            items = from_didl_string(response["Result"])
            for item in items:
                # Check if the album art URI should be fully qualified
                if full_album_art_uri:
                    self._update_album_art_to_full_uri(item)
                # Append the item to the list
                item_list.append(item)

            # If we are not after the complete results, the stop after 1
            # iteration
            if not complete_result:
                break

        metadata["search_type"] = search_type
        if complete_result:
            metadata["number_returned"] = len(item_list)

        # pylint: disable=star-args
        return SearchResult(item_list, **metadata)