def _get_auto_playlists(self): try: logger.debug("YTMusic 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.info("YTMusic loaded %d auto playlists sections", len(browse)) self.library.ytbrowse = browse except Exception: logger.exception("YTMusic failed to load auto playlists") return None
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 parse_auto_playlists(res): browse = [] for sect in res: car = [] if "musicImmersiveCarouselShelfRenderer" in sect: car = nav(sect, ["musicImmersiveCarouselShelfRenderer"]) elif "musicCarouselShelfRenderer" in sect: car = nav(sect, ["musicCarouselShelfRenderer"]) else: continue stitle = nav(car, CAROUSEL_TITLE + ["text"]).strip() browse.append({ "name": stitle, "uri": "ytmusic:auto:" + hashlib.md5(stitle.encode("utf-8")).hexdigest(), "items": [], }) for item in nav(car, ["contents"]): brId = nav( item, ["musicTwoRowItemRenderer"] + TITLE + NAVIGATION_BROWSE_ID, True, ) if brId is None or brId == "VLLM": continue pagetype = nav( item, [ "musicTwoRowItemRenderer", "navigationEndpoint", "browseEndpoint", "browseEndpointContextSupportedConfigs", "browseEndpointContextMusicConfig", "pageType", ], True, ) ititle = nav(item, ["musicTwoRowItemRenderer"] + TITLE_TEXT).strip() if pagetype == "MUSIC_PAGE_TYPE_PLAYLIST": if "subtitle" in item["musicTwoRowItemRenderer"]: ititle += " (" for st in item["musicTwoRowItemRenderer"]["subtitle"][ "runs"]: ititle += st["text"] ititle += ")" browse[-1]["items"].append({ "type": "playlist", "uri": f"ytmusic:playlist:{brId}", "name": ititle, }) elif pagetype == "MUSIC_PAGE_TYPE_ARTIST": browse[-1]["items"].append({ "type": "artist", "uri": f"ytmusic:artist:{brId}", "name": ititle + " (Artist)", }) elif pagetype == "MUSIC_PAGE_TYPE_ALBUM": artist = nav( item, [ "musicTwoRowItemRenderer", "subtitle", "runs", -1, "text" ], True, ) ctype = nav( item, ["musicTwoRowItemRenderer", "subtitle", "runs", 0, "text"], True, ) if artist is not None: browse[-1]["items"].append({ "type": "album", "uri": f"ytmusic:album:{brId}", "name": artist + " - " + ititle + " (" + ctype + ")", }) else: browse[-1]["items"].append({ "type": "album", "uri": f"ytmusic:album:{brId}", "name": ititle + " (" + ctype + ")", }) return browse
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 parse_auto_playlists(res): browse = [] for sect in res: car = [] if 'musicImmersiveCarouselShelfRenderer' in sect: car = nav(sect, ['musicImmersiveCarouselShelfRenderer']) elif 'musicCarouselShelfRenderer' in sect: car = nav(sect, ['musicCarouselShelfRenderer']) else: continue stitle = nav(car, CAROUSEL_TITLE + ['text']).strip() browse.append({ 'name': stitle, 'uri': 'youtubemusic: auto: ' + hashlib.md5(stitle.encode('utf-8')).hexdigest(), 'items': [] }) for item in nav(car, ['contents']): brId = nav(item, ['musicTwoRowItemRenderer'] + TITLE + NAVIGATION_BROWSE_ID, True) if brId is None or brId == 'VLLM': continue pagetype = nav(item, [ 'musicTwoRowItemRenderer', 'navigationEndpoint', 'browseEndpoint', 'browseEndpointContextSupportedConfigs', 'browseEndpointContextMusicConfig', 'pageType' ], True) ititle = nav(item, ['musicTwoRowItemRenderer'] + TITLE_TEXT).strip() if pagetype == 'MUSIC_PAGE_TYPE_PLAYLIST': if 'subtitle' in item['musicTwoRowItemRenderer']: ititle += ' (' for st in item['musicTwoRowItemRenderer']['subtitle'][ 'runs']: ititle += st['text'] ititle += ')' browse[-1]['items'].append({ 'type': 'playlist', 'uri': f"youtubemusic: playlist: {brId}", 'name': ititle }) elif pagetype == 'MUSIC_PAGE_TYPE_ARTIST': browse[-1]['items'].append({ 'type': 'artist', 'uri': f"youtubemusic: artist: {brId}", 'name': ititle + ' (Artist)' }) elif pagetype == 'MUSIC_PAGE_TYPE_ALBUM': artist = nav(item, [ 'musicTwoRowItemRenderer', 'subtitle', 'runs', -1, 'text' ], True) ctype = nav( item, ['musicTwoRowItemRenderer', 'subtitle', 'runs', 0, 'text'], True) if artist is not None: browse[-1]['items'].append({ 'type': 'album', 'uri': f"youtubemusic: album: {brId}", 'name': artist + ' - ' + ititle + ' (' + ctype + ')' }) else: browse[-1]['items'].append({ 'type': 'album', 'uri': f"youtubemusic: album: {brId}", 'name': ititle + ' (' + ctype + ')' }) return (browse)