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
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)
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
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}")
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']] )
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)