Example #1
0
 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
Example #2
0
 def save(self, playlist):
     id_, upload = parse_uri(playlist.uri)
     logger.info("YTMusic saving playlist \"%s\" \"%s\"", playlist.name,
                 id_)
     try:
         pls = API.get_playlist(id_, limit=100)
     except Exception:
         logger.exception("YTMusic 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("YTMusic removing items \"%s\" from playlist", remove)
         try:
             videos = [t for t in pls["tracks"] if t["videoId"] in remove]
             API.remove_playlist_items(id_, videos)
         except Exception:
             logger.exception("YTMusic failed removing items from playlist")
     if len(add):
         logger.debug("YTMusic adding items \"%s\" to playlist", add)
         try:
             API.add_playlist_items(id_, list(add))
         except Exception:
             logger.exception("YTMusic failed adding items to playlist")
     if pls["title"] != playlist.name:
         logger.debug("Renaming playlist to \"%s\"", playlist.name)
         try:
             API.edit_playlist(id_, title=playlist.name)
         except Exception:
             logger.exception("YTMusic failed renaming playlist")
     return playlist
Example #3
0
 def lookup(self, uri):
     id_, upload = parse_uri(uri)
     logger.info("YTMusic looking up playlist \"%s\"", id_)
     try:
         pls = API.get_playlist(id_, limit=100)
     except Exception:
         logger.exception("YTMusic playlist lookup failed")
         pls = None
     if pls:
         tracks = []
         if "tracks" in pls:
             for track in pls["tracks"]:
                 duration = track["duration"].split(":")
                 artists = [Artist(
                     uri=f"ytm:artist?id={a['id']}&upload=false",
                     name=a["name"],
                     sortname=a["name"],
                     musicbrainz_id="",
                 ) for a in track["artists"]]
                 if track["album"]:
                     album = Album(
                         uri=f"ytm:album?id={track['album']['id']}&upload=false",
                         name=track["album"]["name"],
                         artists=artists,
                         num_tracks=None,
                         num_discs=None,
                         date="1999",
                         musicbrainz_id="",
                     )
                 else:
                     album = None
                 tracks.append(Track(
                     uri=f"ytm:video?id={track['videoId']}",
                     name=track["title"],
                     artists=artists,
                     album=album,
                     composers=[],
                     performers=[],
                     genre="",
                     track_no=None,
                     disc_no=None,
                     date="1999",
                     length=(int(duration[0]) * 60 * 1000) + (int(duration[1]) * 1000),
                     bitrate=0,
                     comment="",
                     musicbrainz_id="",
                     last_modified=None,
                 ))
         for track in tracks:
             tid, tupload = parse_uri(track.uri)
             TRACKS[tid] = track
         return Playlist(
             uri=f"ytm:playlist?id={pls['id']}",
             name=pls["title"],
             tracks=tracks,
             last_modified=None,
         )
Example #4
0
 def delete(self, uri):
     logger.info("YTMusic deleting playlist \"%s\"", uri)
     id_, upload = parse_uri(uri)
     try:
         API.delete_playlist(id_)
         return True
     except Exception:
         logger.exception("YTMusic failed to delete playlist")
         return False
Example #5
0
    def translate_uri(self, uri):
        logger.info('YTMusic PlaybackProvider.translate_uri "%s"', uri)

        if "ytm:video?" not in uri:
            return None

        try:
            id_ = parse_qs(urlparse(uri).query)["id"][0]
            return get_video(id_)
        except Exception as e:
            logger.error('translate_uri error "%s"', e)
            return None
Example #6
0
 def as_list(self):
     logger.info("YTMusic getting user playlists")
     refs = []
     try:
         playlists = API.get_library_playlists(limit=100)
     except Exception:
         logger.exception("YTMusic failed getting a list of playlists")
         playlists = []
     for pls in playlists:
         refs.append(Ref.playlist(
             uri=f"ytm:playlist?id={pls['playlistId']}", name=pls["title"],
         ))
     return refs
Example #7
0
 def get_items(self, uri):
     id_, upload = parse_uri(uri)
     logger.info("YTMusic getting playlist items for \"%s\"", id_)
     try:
         pls = API.get_playlist(id_, limit=100)
     except Exception:
         logger.exception("YTMusic failed getting playlist items")
         pls = None
     if pls:
         refs = []
         if "tracks" in pls:
             for track in pls["tracks"]:
                 refs.append(Ref.track(uri=f"ytm:video?id={track['videoId']}", name=track["title"]))
                 duration = track["duration"].split(":")
                 artists = [Artist(
                     uri=f"ytm:artist?id={a['id']}&upload=false",
                     name=a["name"],
                     sortname=a["name"],
                     musicbrainz_id="",
                 ) for a in track["artists"]]
                 if track["album"]:
                     album = Album(
                         uri=f"ytm:album?id={track['album']['id']}&upload=false",
                         name=track["album"]["name"],
                         artists=artists,
                         num_tracks=None,
                         num_discs=None,
                         date="1999",
                         musicbrainz_id="",
                     )
                 else:
                     album = None
                 TRACKS[track["videoId"]] = Track(
                     uri=f"ytm:video?id={track['videoId']}",
                     name=track["title"],
                     artists=artists,
                     album=album,
                     composers=[],
                     performers=[],
                     genre="",
                     track_no=None,
                     disc_no=None,
                     date="1999",
                     length=(int(duration[0]) * 60 * 1000) + (int(duration[1]) * 1000),
                     bitrate=0,
                     comment="",
                     musicbrainz_id="",
                     last_modified=None,
                 )
         return refs
     return None
Example #8
0
 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.info("YTMusic updated player URL to %s", url)
         return url
     else:
         logger.error("YTMusic unable to extract player URL.")
         return None
Example #9
0
 def create(self, name):
     logger.info("YTMusic creating playlist \"%s\"", name)
     try:
         id_ = API.create_playlist(name, "")
     except Exception:
         logger.exception("YTMusic playlist creation failed")
         id_ = None
     if id_:
         uri = f"ytm:playlist?id={id_}"
         logger.info("YTMusic created playlist \"%s\"", uri)
         return Playlist(
             uri=uri,
             name=name,
             tracks=[],
             last_modified=None,
         )
     return None
Example #10
0
 def search(self, query=None, uris=None, exact=False):
     logger.info("YTMusic searching for %s", query)
     tracks = []
     if "any" in query:
         try:
             res = API.search(" ".join(query["any"]), filter=None)
             tracks.extend(parseSearch(res))
             if (exact):
                 for track in tracks:
                     for q in query["any"]:
                         q = q.casefold()
                         if q != track.name.casefold():
                             tracks.remove(track)
                         if q == track.album.name.casefold():
                             tracks.remove(track)
                         for artist in track.artists:
                             if q == artist.name.casefold():
                                 tracks.remove(track)
         except Exception:
             logger.exception("YTMusic search failed for query \"%s\"", " ".join(query["any"]))
     elif "track_name" in query:
         try:
             res = API.search(" ".join(query["track_name"]), filter="songs")
             if exact:
                 tracks.extend(parseSearch(res, "track", query["track_name"]))
             else:
                 tracks.extend(parseSearch(res))
         except Exception:
             logger.exception("YTMusic 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 = API.search(" ".join(q1 + q2), filter="artists")
             if exact:
                 tracks.extend(parseSearch(res, "artist", q1 + q2))
             else:
                 tracks.extend(parseSearch(res))
         except Exception:
             logger.exception("YTMusic search failed for query \"artist\"=\"%s\"", " ".join(q1 + q2))
     elif "album" in query:
         try:
             res = API.search(" ".join(query["album"]), filter="albums")
             if exact:
                 tracks.extend(parseSearch(res, "album", query["album"]))
             else:
                 tracks.extend(parseSearch(res))
         except Exception:
             logger.exception("YTMusic search failed for query \"album\"=\"%s\"", " ".join(query["album"]))
     else:
         logger.info("YTMusic skipping search, unsupported field types \"%s\"", " ".join(query.keys()))
         return None
     logger.info("YTMusic search returned %d results", len(tracks))
     return SearchResult(
         uri="",
         tracks=tracks,
         artists=None,
         albums=None,
     )
Example #11
0
    def as_list(self):
        logger.info("YTMusic getting user playlists")
        refs = []

        # playlist with songs similar to the last played
        refs.append(
            Ref.playlist(
                uri=f"ytm:playlist?id=watch",
                name="Similar to last played",
            ))

        # system playlists
        try:
            playlists = API.get_library_playlists(limit=100)
        except Exception:
            logger.exception("YTMusic failed getting a list of playlists")
            playlists = []
        for pls in playlists:
            refs.append(
                Ref.playlist(
                    uri=f"ytm:playlist?id={pls['playlistId']}",
                    name=pls["title"],
                ))
        return refs
Example #12
0
 def _refresh_auto_playlists(self):
     t0 = time.time()
     self._get_auto_playlists()
     t = time.time() - t0
     logger.info("YTMusic Auto Playlists refreshed in %.2fs", t)
Example #13
0
    def get_items(self, uri):
        id_, upload = parse_uri(uri)
        tracks = None

        try:
            if id_ == 'watch':
                playback = self.backend.playback
                if playback.last_id is not None:
                    track_id = playback.last_id
                    logger.info(
                        "YTMusic getting watch playlist items for \"%s\"",
                        track_id)
                    tracks = API.get_watch_playlist(track_id, limit=100)
            else:
                logger.info("YTMusic getting playlist items for \"%s\"", id_)
                pls = API.get_playlist(id_, limit=100)
                if "tracks" in pls:
                    tracks = pls["tracks"]
        except Exception:
            logger.exception("YTMusic failed getting playlist items")

        if tracks:
            refs = []
            for track in tracks:
                refs.append(
                    Ref.track(uri=f"ytm:video?id={track['videoId']}",
                              name=track["title"]))
                duration = (track['duration'] if 'duration' in track else
                            track['length']).split(":")
                if 'artists' in track:
                    artists = [
                        Artist(
                            uri=f"ytm:artist?id={a['id']}&upload=false",
                            name=a["name"],
                            sortname=a["name"],
                            musicbrainz_id="",
                        ) for a in track["artists"]
                    ]
                elif 'byline' in track:
                    artists = [
                        Artist(
                            name=track["byline"],
                            sortname=track["byline"],
                            musicbrainz_id="",
                        )
                    ]
                else:
                    artists = None

                if 'album' in track:
                    album = Album(
                        uri=f"ytm:album?id={track['album']['id']}&upload=false",
                        name=track["album"]["name"],
                        artists=artists,
                        num_tracks=None,
                        num_discs=None,
                        date="1999",
                        musicbrainz_id="",
                    )
                else:
                    album = None

                TRACKS[track["videoId"]] = Track(
                    uri=f"ytm:video?id={track['videoId']}",
                    name=track["title"],
                    artists=artists,
                    album=album,
                    composers=[],
                    performers=[],
                    genre="",
                    track_no=None,
                    disc_no=None,
                    date="1999",
                    length=(int(duration[0]) * 60 * 1000) +
                    (int(duration[1]) * 1000),
                    bitrate=0,
                    comment="",
                    musicbrainz_id="",
                    last_modified=None,
                )
            return refs
        return None
Example #14
0
 def browse(self, uri):
     logger.info("YTMusic browsing uri \"%s\"", uri)
     if uri == "ytm:root":
         return [
             Ref.directory(uri="ytm:artist", name="Artists"),
             Ref.directory(uri="ytm:album", name="Albums"),
             Ref.directory(uri="ytm:liked", name="Liked Songs"),
         ]
     elif uri == "ytm:artist":
         try:
             library_artists = [
                 Ref.artist(
                     uri=f"ytm:artist?id={a['browseId']}&upload=false",
                     name=a["artist"])
                 for a in API.get_library_artists(limit=100)
             ]
             logger.info("YTMusic found %d artists in library",
                         len(library_artists))
         except Exception:
             logger.exception("YTMusic failed getting artists from library")
             library_artists = []
         # try:
         #     upload_artists = [
         #         Ref.artist(uri=f"ytm:artist?id={a['browseId']}&upload=true", name=a["artist"])
         #         for a in API.get_library_upload_artists(limit=100)
         #     ]
         #     logger.info("YTMusic found %d uploaded artists", len(upload_artists))
         # except Exception:
         #     logger.exception("YTMusic failed getting uploaded artists")
         #     upload_artists = []
         return library_artists  # + upload_artists
     elif uri == "ytm:album":
         try:
             library_albums = [
                 Ref.album(uri=f"ytm:album?id={a['browseId']}&upload=false",
                           name=a["title"])
                 for a in API.get_library_albums(limit=100)
             ]
             logger.info("YTMusic found %d albums in library",
                         len(library_albums))
         except Exception:
             logger.exception("YTMusic failed getting albums from library")
             library_albums = []
         # try:
         #     upload_albums = [
         #         Ref.album(uri=f"ytm:album?id={a['browseId']}&upload=true", name=a["title"])
         #         for a in API.get_library_upload_albums(limit=100)
         #     ]
         #     logger.info("YTMusic found %d uploaded albums", len(upload_albums))
         # except Exception:
         #     logger.exception("YTMusic failed getting uploaded albums")
         #     upload_albums = []
         return library_albums  # + upload_albums
     elif uri == "ytm:liked":
         try:
             res = API.get_liked_songs(limit=100)
             logger.info("YTMusic found %d liked songs", len(res["tracks"]))
             playlistToTracks(res)
             return [
                 Ref.track(uri=f"ytm:video?id={t['videoId']}",
                           name=t["title"])
                 for t in ("tracks" in res and res["tracks"]) or []
             ]
         except Exception:
             logger.exception("YTMusic failed getting liked songs")
     elif uri.startswith("ytm:artist?"):
         id_, upload = parse_uri(uri)
         # if upload:
         #     try:
         #         res = API.get_library_upload_artist(id_)
         #         uploadArtistToTracks(res)
         #         return [
         #             Ref.track(uri=f"ytm:album?id={t['videoId']}", name=t["title"])
         #             for t in res
         #         ]
         #         logger.info("YTMusic found %d songs for uploaded artist \"%s\"", len(res), res[0]["artist"]["name"])
         #     except Exception:
         #         logger.exception("YTMusic failed getting tracks for uploaded artist \"%s\"", id_)
         # else:
         try:
             res = API.get_artist(id_)
             logger.info(
                 "YTMusic found %d songs for artist \"%s\" in library",
                 len(res["songs"]), res["name"])
             artistToTracks(res)
             return [
                 Ref.track(uri=f"ytm:video?id={t['videoId']}",
                           name=t["title"])
                 for t in ("songs" in res and "results" in res["songs"]
                           and res["songs"]["results"]) or []
             ]
         except Exception:
             logger.exception(
                 "YTMusic failed getting tracks for artist \"%s\"", id_)
     elif uri.startswith("ytm:album?"):
         id_, upload = parse_uri(uri)
         # if upload:
         #     try:
         #         res = API.get_library_upload_album(id_)
         #         uploadAlbumToTracks(res, id_)
         #         return [
         #             Ref.track(uri=f"ytm:video?id={t['videoId']}", name=t["title"])
         #             for t in ("tracks" in res and res["tracks"]) or []
         #         ]
         #         logger.info("YTMusic found %d songs for uploaded album \"%s\"", len(res["tracks"]), res["title"])
         #     except Exception:
         #         logger.exception("YTMusic failed getting tracks for uploaded album \"%s\"", id_)
         # else:
         try:
             res = API.get_album(id_)
             logger.info(
                 "YTMusic found %d songs for album \"%s\" in library",
                 len(res["tracks"]), res["title"])
             albumToTracks(res, id_)
             return [
                 Ref.track(uri=f"ytm:video?id={t['videoId']}",
                           name=t["title"])
                 for t in ("tracks" in res and res["tracks"]) or []
             ]
         except Exception:
             logger.exception(
                 "YTMusic failed getting tracks for album \"%s\"", id_)
     return []
Example #15
0
 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],
                     "bitrate": 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["bitrate"] > bitrate):
                     bitrate = stream["bitrate"]
                     playstr = stream
                 elif stream["mimeType"].startswith("audio"):
                     crap[stream["bitrate"]] = stream
                 else:
                     worse[stream["bitrate"]] = 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.info(
             "YTMusic Found %s stream with %d bitrate for %s",
             playstr["audioQuality"],
             playstr["bitrate"],
             bId,
         )
     if url is not None:
         if (self.backend.verify_track_url
                 and requests.head(url).status_code == 403):
             # It's forbidden. Likely because the player url changed and we
             # decoded the signature incorrectly.
             # Refresh the player, log an error, and send back none.
             logger.error(
                 "YTMusic found forbidden URL. Updating player URL now.")
             self.backend._youtube_player_refresh_timer.now()
         else:
             # Return the decoded youtube url to mopidy for playback.
             logger.debug("YTMusic found %s", url)
             return url
     return None