def search(self, query=None, uris=None, exact=False):
     results = []
     logger.debug("YoutubeMusic searching for %s", query)
     if "any" in query:
         try:
             res = self.backend.api.search(" ".join(query["any"]),
                                           filter=None)
             results = self.parseSearch(res)
         except Exception:
             logger.exception(
                 "YoutubeMusic search failed for query \"any\"=\"%s\"",
                 " ".join(query["any"]))
     elif "track_name" in query:
         try:
             res = self.backend.api.search(" ".join(query["track_name"]),
                                           filter="songs")
             if exact:
                 results = self.parseSearch(res, "track",
                                            query["track_name"])
             else:
                 results = self.parseSearch(res)
         except Exception:
             logger.exception(
                 "YoutubeMusic search failed for query \"title\"=\"%s\"",
                 " ".join(query["track_name"]))
     elif "albumartist" in query or "artist" in query:
         q1 = ("albumartist" in query and query["albumartist"]) or []
         q2 = ("artist" in query and query["artist"]) or []
         try:
             res = self.backend.api.search(" ".join(q1 + q2),
                                           filter="artists")
             if exact:
                 results = self.parseSearch(res, "artist", q1 + q2)
             else:
                 results = self.parseSearch(res)
         except Exception:
             logger.exception(
                 "YoutubeMusic search failed for query \"artist\"=\"%s\"",
                 " ".join(q1 + q2))
     elif "album" in query:
         try:
             res = self.backend.api.search(" ".join(query["album"]),
                                           filter="albums")
             if exact:
                 results = self.parseSearch(res, "album", query["album"])
             else:
                 results = self.parseSearch(res)
         except Exception:
             logger.exception(
                 "YoutubeMusic search failed for query \"album\"=\"%s\"",
                 " ".join(query["album"]))
     else:
         logger.debug(
             "YoutubeMusic skipping search, unsupported field types \"%s\"",
             " ".join(query.keys()))
         return None
     return results
Beispiel #2
0
 def delete(self, uri):
     logger.debug("YoutubeMusic deleting playlist \"%s\"", uri)
     bId = parse_uri(uri)
     try:
         self.backend.api.delete_playlist(bId)
         return True
     except Exception:
         logger.exception("YoutubeMusic failed to delete playlist")
         return False
 def artistToTracks(self, artist):
     if "songs" in artist and "browseId" in artist["songs"] and artist[
             "songs"]["browseId"] is not None:
         res = self.backend.api.get_playlist(
             artist["songs"]["browseId"],
             limit=self.backend.playlist_item_limit)
         tracks = self.playlistToTracks(res)
         logger.debug('YoutubeMusic found %d tracks for %s', len(tracks),
                      artist['name'])
         return tracks
     return None
Beispiel #4
0
 def get_items(self, uri):
     bId = parse_uri(uri)
     logger.debug("YoutubeMusic getting playlist items for \"%s\"", bId)
     try:
         pls = self.backend.api.get_playlist(bId, limit=self.backend.playlist_item_limit)
     except Exception:
         logger.exception("YoutubeMusic failed getting playlist items")
         pls = None
     if pls:
         tracks = self.backend.library.playlistToTracks(pls)
         return [Ref.track(uri=t.uri, name=t.name) for t in tracks]
     return None
 def _get_youtube_player(self):
     # Refresh our js player URL so YDL can decode the signature correctly.
     response = requests.get('https://music.youtube.com',
                             headers=self.api.headers,
                             proxies=self.api.proxies)
     m = re.search(r'jsUrl"\s*:\s*"([^"]+)"', response.text)
     if m:
         url = m.group(1)
         logger.debug('YoutubeMusic updated player URL to %s', url)
         return (url)
     else:
         logger.error('YoutubeMusic unable to extract player URL.')
         return (None)
Beispiel #6
0
 def as_list(self):
     logger.debug("YoutubeMusic getting user playlists")
     refs = []
     try:
         playlists = self.backend.api.get_library_playlists(limit=100)
     except Exception:
         logger.exception("YoutubeMusic failed getting a list of playlists")
         playlists = []
     for pls in playlists:
         refs.append(Ref.playlist(
             uri=f"youtubemusic:playlist:{pls['playlistId']}", name=pls["title"],
         ))
     return refs
    def translate_uri(self, uri):
        logger.debug('YoutubeMusic PlaybackProvider.translate_uri "%s"', uri)

        if "youtubemusic:track:" not in uri:
            return None

        try:
            bId = uri.split(":")[2]
            self.last_id = bId
            return self._get_track(bId)
        except Exception as e:
            logger.error('translate_uri error "%s"', str(e))
            return None
Beispiel #8
0
 def save(self, playlist):
     bId = parse_uri(playlist.uri)
     logger.debug("YoutubeMusic saving playlist \"%s\" \"%s\"", playlist.name, bId)
     try:
         pls = self.backend.api.get_playlist(bId, limit=self.backend.playlist_item_limit)
     except Exception:
         logger.exception("YoutubeMusic saving playlist failed")
         return None
     oldIds = set([t["videoId"] for t in pls["tracks"]])
     newIds = set([parse_uri(p.uri)[0] for p in playlist.tracks])
     common = oldIds & newIds
     remove = oldIds ^ common
     add = newIds ^ common
     if len(remove):
         logger.debug("YoutubeMusic removing items \"%s\" from playlist", remove)
         try:
             videos = [t for t in pls["tracks"] if t["videoId"] in remove]
             self.backend.api.remove_playlist_items(bId, videos)
         except Exception:
             logger.exception("YoutubeMusic failed removing items from playlist")
     if len(add):
         logger.debug("YoutubeMusic adding items \"%s\" to playlist", add)
         try:
             self.backend.api.add_playlist_items(bId, list(add))
         except Exception:
             logger.exception("YoutubeMusic failed adding items to playlist")
     if pls["title"] != playlist.name:
         logger.debug("Renaming playlist to \"%s\"", playlist.name)
         try:
             self.backend.api.edit_playlist(bId, title=playlist.name)
         except Exception:
             logger.exception("YoutubeMusic failed renaming playlist")
     return playlist
 def scrobble_track(self, bId):
     # Called through YoutubeMusicScrobbleListener
     # Let YouTube Music know we're playing this track so it will be added to our history.
     endpoint = "https://www.youtube.com/get_video_info"
     params = {
         "video_id": bId,
         "hl": self.api.language,
         "el": "detailpage",
         "c": "WEB_REMIX",
         "cver": "0.1"
     }
     response = requests.get(endpoint,
                             params,
                             headers=self.api.headers,
                             proxies=self.api.proxies)
     text = parse_qs(response.text)
     player_response = json.loads(text['player_response'][0])
     trackurl = re.sub(
         r'plid=', 'list=', player_response['playbackTracking']
         ['videostatsPlaybackUrl']['baseUrl'])
     CPN_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_'
     params = {
         'cpn':
         ''.join((CPN_ALPHABET[random.randint(0, 256) & 63]
                  for _ in range(0, 16))),
         'referrer':
         "https://music.youtube.com",
         'cbr':
         text['cbr'][0],
         'cbrver':
         text['cbrver'][0],
         'c':
         text['c'][0],
         'cver':
         text['cver'][0],
         'cos':
         text['cos'][0],
         'cosver':
         text['cosver'][0],
         'cr':
         text['cr'][0],
         'ver':
         2,
     }
     tr = requests.get(trackurl,
                       params=params,
                       headers=self.api.headers,
                       proxies=self.api.proxies)
     logger.debug("%d code from '%s'", tr.status_code, tr.url)
Beispiel #10
0
 def lookup(self, uri):
     bId = parse_uri(uri)
     logger.debug("YoutubeMusic looking up playlist \"%s\"", bId)
     try:
         pls = self.backend.api.get_playlist(bId, limit=self.backend.playlist_item_limit)
     except Exception:
         logger.exception("YoutubeMusic playlist lookup failed")
         pls = None
     if pls:
         tracks = self.backend.library.playlistToTracks(pls)
         return Playlist(
             uri=f"youtubemusic:playlist:{pls['id']}",
             name=pls["title"],
             tracks=tracks,
             last_modified=None,
         )
Beispiel #11
0
 def create(self, name):
     logger.debug("YoutubeMusic creating playlist \"%s\"", name)
     try:
         bId = self.backend.api.create_playlist(name, "")
     except Exception:
         logger.exception("YoutubeMusic playlist creation failed")
         bId = None
     if bId:
         uri = f"youtubemusic:playlist:{bId}"
         logger.debug("YoutubeMusic created playlist \"%s\"", uri)
         return Playlist(
             uri=uri,
             name=name,
             tracks=[],
             last_modified=None,
         )
     return None
    def track_playback_ended(self, tl_track, time_position):
        if self.scrobbling:
            track = tl_track.track

            duration = track.length and track.length // 1000 or 0
            time_position = time_position // 1000

            if time_position < duration // 2 and time_position < 120:
                logger.debug(
                    "Track not played long enough too scrobble. (50% or 120s)"
                )
                return

            bId = track.uri.split(":")[2]
            logger.debug("Scrobbling: %s", bId)
            listener.send(
                YoutubeMusicScrobbleListener,
                "scrobble_track",
                bId=bId,
            )
 def _get_auto_playlists(self):
     try:
         logger.debug('YoutubeMusic loading auto playlists')
         response = self.api._send_request('browse', {})
         tab = nav(response, SINGLE_COLUMN_TAB)
         browse = parse_auto_playlists(nav(tab, SECTION_LIST))
         if 'continuations' in tab['sectionListRenderer']:
             request_func = lambda additionalParams: self.api._send_request(
                 'browse', {}, additionalParams)
             parse_func = lambda contents: parse_auto_playlists(contents)
             browse.extend(
                 get_continuations(tab['sectionListRenderer'],
                                   'sectionListContinuation', 100,
                                   request_func, parse_func))
         # Delete empty sections
         for i in range(len(browse) - 1, 0, -1):
             if len(browse[i]['items']) == 0:
                 browse.pop(i)
         logger.debug('YoutubeMusic loaded %d auto playlists sections',
                      len(browse))
         self.library.ytbrowse = browse
     except Exception:
         logger.exception('YoutubeMusic failed to load auto playlists')
     return (None)
 def parseSearch(self, results, field=None, queries=[]):
     tracks = set()
     salbums = set()
     sartists = set()
     for result in results:
         if result["resultType"] == "song":
             if field == "track" and not any(
                     q.casefold() == result["title"].casefold()
                     for q in queries):
                 continue
             if result['videoId'] in self.TRACKS:
                 tracks.add(self.TRACKS[result['videoId']])
             else:
                 try:
                     length = [
                         int(i) for i in result["duration"].split(":")
                     ]
                 except ValueError:
                     length = [0, 0]
                 if result['videoId'] is None:
                     continue
                 if result['videoId'] not in self.TRACKS:
                     artists = []
                     for a in result['artists']:
                         if a['id'] not in self.ARTISTS:
                             self.ARTISTS[a['id']] = Artist(
                                 uri=f"youtubemusic:artist:{a['id']}",
                                 name=a["name"],
                                 sortname=a["name"],
                                 musicbrainz_id="",
                             )
                         artists.append(self.ARTISTS[a['id']])
                     album = None
                     if 'album' in result:
                         if result['album']['id'] not in self.ALBUMS:
                             self.ALBUMS[result['album']['id']] = Album(
                                 uri=
                                 f"youtubemusic:album:{result['album']['id']}",
                                 name=result["album"]["name"],
                                 artists=artists,
                                 num_tracks=None,
                                 num_discs=None,
                                 date="0000",
                                 musicbrainz_id="",
                             )
                             album = self.ALBUMS[result['album']['id']]
                     self.TRACKS[result['videoId']] = Track(
                         uri=f"youtubemusic:track:{result['videoId']}",
                         name=result["title"],
                         artists=artists,
                         album=album,
                         composers=[],
                         performers=[],
                         genre="",
                         track_no=None,
                         disc_no=None,
                         date="0000",
                         length=(length[0] * 60 * 1000) +
                         (length[1] * 1000),
                         bitrate=0,
                         comment="",
                         musicbrainz_id="",
                         last_modified=None,
                     )
                 tracks.add(self.TRACKS[result['videoId']])
         elif result["resultType"] == "album":
             if field == "album" and not any(
                     q.casefold() == result["title"].casefold()
                     for q in queries):
                 continue
             try:
                 album = self.backend.api.get_album(result["browseId"])
                 if result["browseId"] not in self.ALBUMS:
                     date = result['year']
                     self.ALBUMS[result['browseId']] = Album(
                         uri=f"youtubemusic:album:{result['browseId']}",
                         name=album["title"],
                         artists=[
                             Artist(
                                 uri="",
                                 name=result["artist"],
                                 sortname=result["artist"],
                                 musicbrainz_id="",
                             )
                         ],
                         num_tracks=int(album["trackCount"])
                         if str(album["trackCount"]).isnumeric() else None,
                         num_discs=None,
                         date=date,
                         musicbrainz_id="",
                     )
                 salbums.add(self.ALBUMS[result['browseId']])
             except Exception:
                 logger.exception("YoutubeMusic failed parsing album %s",
                                  result["title"])
         elif result["resultType"] == "artist":
             if field == "artist" and not any(
                     q.casefold() == result["artist"].casefold()
                     for q in queries):
                 continue
             try:
                 artistq = self.backend.api.get_artist(result["browseId"])
                 if result['browseId'] not in self.ARTISTS:
                     self.ARTISTS[result['browseId']] = Artist(
                         uri=f"youtubemusic:artist:{result['browseId']}",
                         name=artistq["name"],
                         sortname=artistq["name"],
                         musicbrainz_id="",
                     )
                 sartists.add(self.ARTISTS[result['browseId']])
                 if 'albums' in artistq:
                     if 'params' in artistq['albums']:
                         albums = self.backend.api.get_artist_albums(
                             artistq["channelId"],
                             artistq["albums"]["params"])
                         for album in albums:
                             if album['browseId'] not in self.ALBUMS:
                                 self.ALBUMS[album['browseId']] = Album(
                                     uri=
                                     f"youtubemusic:album:{album['browseId']}",
                                     name=album["title"],
                                     artists=[
                                         self.ARTISTS[result['browseId']]
                                     ],
                                     date=album['year'],
                                     musicbrainz_id="",
                                 )
                             salbums.add(self.ALBUMS[album['browseId']])
                     elif 'results' in artistq['albums']:
                         for album in artistq["albums"]["results"]:
                             if album['browseId'] not in self.ALBUMS:
                                 self.ALBUMS[album['browseId']] = Album(
                                     uri=
                                     f"youtubemusic:album:{album['browseId']}",
                                     name=album["title"],
                                     artists=[
                                         self.ARTISTS[result['browseId']]
                                     ],
                                     date=album['year'],
                                     musicbrainz_id="",
                                 )
                             salbums.add(self.ALBUMS[album['browseId']])
                 if 'singles' in artistq and 'results' in artistq['singles']:
                     for single in artistq['singles']['results']:
                         if single['browseId'] not in self.ALBUMS:
                             self.ALBUMS[single['browseId']] = Album(
                                 uri=
                                 f"youtubemusic:album:{single['browseId']}",
                                 name=single['title'],
                                 artists=[self.ARTISTS[result['browseId']]],
                                 date=single['year'],
                                 musicbrainz_id="",
                             )
                         salbums.add(self.ALBUMS[single['browseId']])
                 if 'songs' in artistq:
                     if 'results' in artistq['songs']:
                         for song in artistq['songs']['results']:
                             if song['videoId'] in self.TRACKS:
                                 tracks.add(self.TRACKS[song['videoId']])
                             else:
                                 album = None
                                 if 'album' in song:
                                     if song['album'][
                                             'id'] not in self.ALBUMS:
                                         self.ALBUMS[
                                             song['album']['id']] = Album(
                                                 uri=
                                                 f"youtubemusic:album:{song['album']['id']}",
                                                 name=song['album']['name'],
                                                 artists=[
                                                     self.ARTISTS[
                                                         result['browseId']]
                                                 ],
                                                 date='1999',
                                                 musicbrainz_id="",
                                             )
                                     album = self.ALBUMS[song['album']
                                                         ['id']]
                                 if song['videoId'] not in self.TRACKS:
                                     self.TRACKS[song['videoId']] = Track(
                                         uri=
                                         f"youtubemusic:track:{song['videoId']}",
                                         name=song['title'],
                                         artists=[
                                             self.ARTISTS[
                                                 result['browseId']]
                                         ],
                                         album=album,
                                         composers=[],
                                         performers=[],
                                         genre="",
                                         track_no=None,
                                         disc_no=None,
                                         date="0000",
                                         length=None,
                                         bitrate=0,
                                         comment="",
                                         musicbrainz_id="",
                                         last_modified=None,
                                     )
                                 tracks.add(self.TRACKS[song['videoId']])
             except Exception:
                 logger.exception("YoutubeMusic failed parsing artist %s",
                                  result["artist"])
     tracks = list(tracks)
     for track in tracks:
         bId, _ = parse_uri(track.uri)
         self.TRACKS[bId] = track
     logger.debug("YoutubeMusic search returned %d results",
                  len(tracks) + len(sartists) + len(salbums))
     return SearchResult(
         uri="youtubemusic:search",
         tracks=tracks,
         artists=list(sartists),
         albums=list(salbums),
     )
 def browse(self, uri):
     if not uri:
         return []
     logger.debug("YoutubeMusic browsing uri \"%s\"", uri)
     if uri == "youtubemusic:root":
         dirs = []
         if self.backend.auth:
             dirs += [
                 Ref.directory(uri="youtubemusic:artist", name="Artists"),
                 Ref.directory(uri="youtubemusic:album", name="Albums"),
             ]
             if self.backend.liked_songs:
                 dirs.append(
                     Ref.directory(uri="youtubemusic:liked",
                                   name="Liked Songs"))
             if self.backend.history:
                 dirs.append(
                     Ref.directory(uri="youtubemusic:history",
                                   name="Recently Played"))
             if self.backend.subscribed_artist_limit:
                 dirs.append(
                     Ref.directory(uri="youtubemusic:subscriptions",
                                   name="Subscriptions"))
         dirs.append(
             Ref.directory(uri="youtubemusic:watch",
                           name="Similar to last played"))
         if self.backend.mood_genre:
             dirs.append(
                 Ref.directory(uri="youtubemusic:mood",
                               name="Mood and Genre Playlists"))
         if self.backend._auto_playlist_refresh_rate:
             dirs.append(
                 Ref.directory(uri="youtubemusic:auto",
                               name="Auto Playlists"))
         return (dirs)
     elif uri == "youtubemusic:subscriptions" and self.backend.subscribed_artist_limit:
         try:
             subs = self.backend.api.get_library_subscriptions(
                 limit=self.backend.subscribed_artist_limit)
             logger.debug("YoutubeMusic found %d artists in subscriptions",
                          len(subs))
             return [
                 Ref.artist(uri=f"youtubemusic:artist:{a['browseId']}",
                            name=a["artist"]) for a in subs
             ]
         except Exception:
             logger.exception(
                 "YoutubeMusic failed getting artists from subscriptions")
     elif uri == "youtubemusic:artist":
         try:
             library_artists = [
                 Ref.artist(uri=f"youtubemusic:artist:{a['browseId']}",
                            name=a["artist"])
                 for a in self.backend.api.get_library_artists(limit=100)
             ]
             logger.debug("YoutubeMusic found %d artists in library",
                          len(library_artists))
         except Exception:
             logger.exception(
                 "YoutubeMusic failed getting artists from library")
             library_artists = []
         if self.backend.auth:
             try:
                 upload_artists = [
                     Ref.artist(
                         uri=f"youtubemusic:artist:{a['browseId']}:upload",
                         name=a["artist"])
                     for a in self.backend.api.get_library_upload_artists(
                         limit=100)
                 ]
                 logger.debug("YoutubeMusic found %d uploaded artists",
                              len(upload_artists))
             except Exception:
                 logger.exception(
                     "YoutubeMusic failed getting uploaded artists")
                 upload_artists = []
         else:
             upload_artists = []
         return library_artists + upload_artists
     elif uri == "youtubemusic:album":
         try:
             library_albums = [
                 Ref.album(uri=f"youtubemusic:album:{a['browseId']}",
                           name=a["title"])
                 for a in self.backend.api.get_library_albums(limit=100)
             ]
             logger.debug("YoutubeMusic found %d albums in library",
                          len(library_albums))
         except Exception:
             logger.exception(
                 "YoutubeMusic failed getting albums from library")
             library_albums = []
         if self.backend.auth:
             try:
                 upload_albums = [
                     Ref.album(
                         uri=f"youtubemusic:album:{a['browseId']}:upload",
                         name=a["title"])
                     for a in self.backend.api.get_library_upload_albums(
                         limit=100)
                 ]
                 logger.debug("YoutubeMusic found %d uploaded albums",
                              len(upload_albums))
             except Exception:
                 logger.exception(
                     "YoutubeMusic failed getting uploaded albums")
                 upload_albums = []
         else:
             upload_albums = []
         return library_albums + upload_albums
     elif uri == "youtubemusic:liked":
         try:
             res = self.backend.api.get_liked_songs(
                 limit=self.backend.playlist_item_limit)
             tracks = self.playlistToTracks(res)
             logger.debug("YoutubeMusic found %d liked songs",
                          len(res["tracks"]))
             return [Ref.track(uri=t.uri, name=t.name) for t in tracks]
         except Exception:
             logger.exception("YoutubeMusic failed getting liked songs")
     elif uri == "youtubemusic:history":
         try:
             res = self.backend.api.get_history()
             tracks = self.playlistToTracks({'tracks': res})
             logger.debug("YoutubeMusic found %d songs from recent history",
                          len(res))
             return [Ref.track(uri=t.uri, name=t.name) for t in tracks]
         except Exception:
             logger.exception(
                 "YoutubeMusic failed getting listening history")
     elif uri == "youtubemusic:watch":
         try:
             playback = self.backend.playback
             if playback.last_id is not None:
                 track_id = playback.last_id
             elif self.backend.auth:
                 hist = self.backend.api.get_history()
                 track_id = hist[0]['videoId']
             if track_id:
                 res = self.backend.api.get_watch_playlist(
                     track_id, limit=self.backend.playlist_item_limit)
                 if 'tracks' in res:
                     logger.debug(
                         "YoutubeMusic found %d watch songs for \"%s\"",
                         len(res["tracks"]), track_id)
                     res['tracks'].pop(0)
                     tracks = self.playlistToTracks(res)
                     return [
                         Ref.track(uri=t.uri, name=t.name) for t in tracks
                     ]
         except Exception:
             logger.exception("YoutubeMusic failed getting watch songs")
     elif uri == "youtubemusic:mood":
         try:
             logger.debug('YoutubeMusic loading mood/genre playlists')
             moods = {}
             response = self.backend.api._send_request(
                 'browse', {"browseId": "FEmusic_moods_and_genres"})
             for sect in nav(response, SINGLE_COLUMN_TAB + SECTION_LIST):
                 for cat in nav(sect, ['gridRenderer', 'items']):
                     title = nav(cat, [
                         'musicNavigationButtonRenderer', 'buttonText',
                         'runs', 0, 'text'
                     ]).strip()
                     endpnt = nav(cat, [
                         'musicNavigationButtonRenderer', 'clickCommand',
                         'browseEndpoint', 'browseId'
                     ])
                     params = nav(cat, [
                         'musicNavigationButtonRenderer', 'clickCommand',
                         'browseEndpoint', 'params'
                     ])
                     moods[title] = {
                         'name': title,
                         'uri': 'youtubemusic:mood:' + params + ':' + endpnt
                     }
             return [
                 Ref.directory(uri=moods[a]['uri'], name=moods[a]['name'])
                 for a in sorted(moods.keys())
             ]
         except Exception:
             logger.exception(
                 'YoutubeMusic failed to load mood/genre playlists')
     elif uri.startswith("youtubemusic:mood:"):
         try:
             ret = []
             _, _, params, endpnt = uri.split(':')
             response = self.backend.api._send_request(
                 'browse', {
                     "browseId": endpnt,
                     "params": params
                 })
             for sect in nav(response, SINGLE_COLUMN_TAB + SECTION_LIST):
                 key = []
                 if 'gridRenderer' in sect:
                     key = ['gridRenderer', 'items']
                 elif 'musicCarouselShelfRenderer' in sect:
                     key = ['musicCarouselShelfRenderer', 'contents']
                 elif 'musicImmersiveCarouselShelfRenderer' in sect:
                     key = [
                         'musicImmersiveCarouselShelfRenderer', 'contents'
                     ]
                 if len(key):
                     for item in nav(sect, key):
                         title = nav(item, ['musicTwoRowItemRenderer'] +
                                     TITLE_TEXT).strip()
                         #                           if 'subtitle' in item['musicTwoRowItemRenderer']:
                         #                               title += ' ('
                         #                               for st in item['musicTwoRowItemRenderer']['subtitle']['runs']:
                         #                                   title += st['text']
                         #                               title += ')'
                         brId = nav(item, ['musicTwoRowItemRenderer'] +
                                    NAVIGATION_BROWSE_ID)
                         ret.append(
                             Ref.playlist(
                                 uri=f"youtubemusic:playlist:{brId}",
                                 name=title))
             return (ret)
         except Exception:
             logger.exception(
                 'YoutubeMusic failed getting mood/genre playlist "%s"',
                 uri)
     elif uri == "youtubemusic:auto" and self.backend._auto_playlist_refresh_rate:
         try:
             return [
                 Ref.directory(uri=a['uri'], name=a['name'])
                 for a in self.ytbrowse
             ]
         except Exception:
             logger.exception('YoutubeMusic failed getting auto playlists')
     elif uri.startswith("youtubemusic:auto:"
                         ) and self.backend._auto_playlist_refresh_rate:
         try:
             for a in self.ytbrowse:
                 if a['uri'] == uri:
                     ret = []
                     for i in a['items']:
                         if i['type'] == 'playlist':
                             ret.append(
                                 Ref.playlist(uri=i['uri'], name=i['name']))
                             logger.debug("playlist: %s - %s", i['name'],
                                          i['uri'])
                         elif i['type'] == 'artist':
                             ret.append(
                                 Ref.artist(uri=i['uri'], name=i['name']))
                             logger.debug("artist: %s - %s", i['name'],
                                          i['uri'])
                         elif i['type'] == 'album':
                             ret.append(
                                 Ref.album(uri=i['uri'], name=i['name']))
                             logger.debug("album: %s - %s", i['name'],
                                          i['uri'])
                     return (ret)
         except Exception:
             logger.exception(
                 'YoutubeMusic failed getting auto playlist "%s"', uri)
     elif uri.startswith("youtubemusic:artist:"):
         bId, upload = parse_uri(uri)
         if upload:
             try:
                 res = self.backend.api.get_library_upload_artist(bId)
                 tracks = self.uploadArtistToTracks(res)
                 logger.debug(
                     "YoutubeMusic found %d songs for uploaded artist \"%s\"",
                     len(res), res[0]["artist"]["name"])
                 return [Ref.track(uri=t.uri, name=t.name) for t in tracks]
             except Exception:
                 logger.exception(
                     "YoutubeMusic failed getting tracks for uploaded artist \"%s\"",
                     bId)
         else:
             try:
                 res = self.backend.api.get_artist(bId)
                 tracks = self.artistToTracks(res)
                 logger.debug(
                     "YoutubeMusic found %d songs for artist \"%s\" in library",
                     len(res["songs"]), res["name"])
                 return [Ref.track(uri=t.uri, name=t.name) for t in tracks]
             except Exception:
                 logger.exception(
                     "YoutubeMusic failed getting tracks for artist \"%s\"",
                     bId)
     elif uri.startswith("youtubemusic:album:"):
         bId, upload = parse_uri(uri)
         if upload:
             try:
                 res = self.backend.api.get_library_upload_album(bId)
                 tracks = self.uploadAlbumToTracks(res, bId)
                 logger.debug(
                     "YoutubeMusic found %d songs for uploaded album \"%s\"",
                     len(res["tracks"]), res["title"])
                 return [Ref.track(uri=t.uri, name=t.name) for t in tracks]
             except Exception:
                 logger.exception(
                     "YoutubeMusic failed getting tracks for uploaded album \"%s\"",
                     bId)
         else:
             try:
                 res = self.backend.api.get_album(bId)
                 tracks = self.albumToTracks(res, bId)
                 logger.debug(
                     "YoutubeMusic found %d songs for album \"%s\" in library",
                     len(res["tracks"]), res["title"])
                 return [Ref.track(uri=t.uri, name=t.name) for t in tracks]
             except Exception:
                 logger.exception(
                     "YoutubeMusic failed getting tracks for album \"%s\"",
                     bId)
     elif uri.startswith("youtubemusic:playlist:"):
         bId, upload = parse_uri(uri)
         try:
             res = self.backend.api.get_playlist(
                 bId, limit=self.backend.playlist_item_limit)
             tracks = self.playlistToTracks(res)
             return [Ref.track(uri=t.uri, name=t.name) for t in tracks]
         except Exception:
             logger.exception(
                 "YoutubeMusic failed to get tracks from playlist '%s'",
                 bId)
     return []
 def _refresh_auto_playlists(self):
     t0 = time.time()
     self._get_auto_playlists()
     t = time.time() - t0
     logger.debug("YoutubeMusic Auto Playlists refreshed in %.2fs", t)
 def _get_track(self, bId):
     streams = self.backend.api.get_streaming_data(bId)
     playstr = None
     url = None
     if self.backend.stream_preference:
         # Try to find stream by our preference order.
         tags = {}
         if 'adaptiveFormats' in streams:
             for stream in streams['adaptiveFormats']:
                 tags[str(stream['itag'])] = stream
         elif 'dashManifestUrl' in streams:
             # Grab the dashmanifest XML and parse out the streams from it
             dash = requests.get(streams['dashManifestUrl'])
             formats = re.findall(r'<Representation id="(\d+)" .*? bandwidth="(\d+)".*?BaseURL>(.*?)</BaseURL', dash.text)
             for stream in formats:
                 tags[stream[0]] = {
                     'url': stream[2],
                     'audioQuality': 'ITAG_' + stream[0],
                     'averageBitrate': int(stream[1]),
                 }
         for i, p in enumerate(self.backend.stream_preference, start=1):
             if str(p) in tags:
                 playstr = tags[str(p)]
                 logger.debug("Found #%d preference stream %s", i, str(p))
                 break
     if playstr is None:
         # Couldn't find our preference, let's try something else:
         if 'adaptiveFormats' in streams:
             # Try to find the highest quality stream.  We want "AUDIO_QUALITY_HIGH", barring
             # that we find the highest bitrate audio/mp4 stream, after that we sort through the
             # garbage.
             bitrate = 0
             crap = {}
             worse = {}
             for stream in streams['adaptiveFormats']:
                 if 'audioQuality' in stream and stream['audioQuality'] == 'AUDIO_QUALITY_HIGH':
                     playstr = stream
                     break
                 if stream['mimeType'].startswith('audio/mp4') and stream['averageBitrate'] > bitrate:
                     bitrate = stream['averageBitrate']
                     playstr = stream
                 elif stream['mimeType'].startswith('audio'):
                     crap[stream['averageBitrate']] = stream
                 else:
                     worse[stream['averageBitrate']] = stream
             if playstr is None:
                 # sigh.
                 if len(crap):
                     playstr = crap[sorted(list(crap.keys()))[-1]]
                     if 'audioQuality' not in playstr:
                         playstr['audioQuality'] = 'AUDIO_QUALITY_GARBAGE'
                 elif len(worse):
                     playstr = worse[sorted(list(worse.keys()))[-1]]
                     if 'audioQuality' not in playstr:
                         playstr['audioQuality'] = 'AUDIO_QUALITY_FECES'
         elif 'formats' in streams:
             # Great, we're really left with the dregs of quality.
             playstr = streams['formats'][0]
             if 'audioQuality' not in playstr:
                 playstr['audioQuality'] = 'AUDIO_QUALITY_404'
         else:
             logger.error('No streams found for %s. Falling back to youtube-dl.', bId)
     if playstr is not None:
         # Use Youtube-DL's Info Extractor to decode the signature.
         if 'signatureCipher' in playstr:
             sc = parse_qs(playstr['signatureCipher'])
             sig = self.YoutubeIE._decrypt_signature(
                 sc['s'][0],
                 bId,
                 self.Youtube_Player_URL,
             )
             url = sc['url'][0] + '&sig=' + sig + '&ratebypass=yes'
         elif 'url' in playstr:
             url = playstr['url']
         else:
             logger.error("Unable to get URL from stream for %s", bId)
             return(None)
         logger.debug('Found %s stream with %d ABR for %s', playstr['audioQuality'], playstr['averageBitrate'], bId)
     if url is not None:
         # Return the decoded youtube url to mopidy for playback.
         return(url)
     return None
 def _refresh_youtube_player(self):
     t0 = time.time()
     self.playback.Youtube_Player_URL = self._get_youtube_player()
     t = time.time() - t0
     logger.debug("Youtube Player URL refreshed in %.2fs", t)