Exemple #1
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
Exemple #2
0
    def translate_uri(self, uri):
        logger.debug('YTMusic PlaybackProvider.translate_uri "%s"', uri)

        if "ytmusic: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
Exemple #3
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
    def run(self, args, config):
        from ytmusicapi.ytmusic import YTMusic

        path = config["ytmusic"]["auth_json"]
        if not path:
            logger.error("auth_json path not defined in config")
            return 1
        print('Updating credentials in  "' + str(path) + '"')
        print(
            "Open Youtube Music, open developer tools (F12), go to Network tab,"
        )
        print(
            'right click on a POST request and choose "Copy request headers".')
        print("Then paste (CTRL+SHIFT+V) them here and press CTRL+D.")
        try:
            print(YTMusic.setup(filepath=str(path)))
        except Exception:
            logger.exception("YTMusic setup failed")
            return 1
        print("Authentication JSON data saved to {}".format(str(path)))
        return 0
Exemple #5
0
 def get_images(self, uris):
     ret = {}
     for uri in uris:
         images = []
         comp = uri.split(":")
         if len(comp) == 3:
             logger.debug("YTMusic getting images for %s", uri)
             func, bId = comp[1:3]
             if bId not in self.IMAGES:
                 try:
                     if func == "artist":
                         data = self.backend.api.get_artist(bId)
                         images = self.addThumbnails(bId, data, False)
                     elif func == "album":
                         data = self.backend.api.get_album(bId)
                         images = self.addThumbnails(bId, data)
                     elif func == "playlist":
                         data = self.backend.api.get_playlist(bId)
                         images = self.addThumbnails(bId, data, False)
                     elif func == "track":
                         if (bId in self.TRACKS
                                 and self.TRACKS[bId].album is not None and
                                 self.TRACKS[bId].album.uri is not None):
                             album, upload = parse_uri(
                                 self.TRACKS[bId].album.uri)
                             if upload:
                                 data = self.backend.api.get_library_upload_album(
                                     album)
                             else:
                                 data = self.backend.api.get_album(album)
                             images = self.addThumbnails(bId, data)
                 except Exception:
                     logger.error("YTMusic unable to get image url for %s",
                                  uri)
             else:
                 images = self.IMAGES[bId]
         elif len(comp) == 4 and comp[3] == "upload":
             logger.debug("YTMusic getting images for %s", uri)
             func, bId = comp[1:3]
             if bId not in self.IMAGES:
                 try:
                     if func == "artist":
                         data = self.backend.api.get_library_upload_artist(
                             bId)
                         images = self.addThumbnails(bId, data, False)
                     elif func == "album":
                         data = self.backend.api.get_library_upload_album(
                             bId)
                         images = self.addThumbnails(bId, data)
                     elif func == "track":
                         if (bId in self.TRACKS
                                 and self.TRACKS[bId].album is not None and
                                 self.TRACKS[bId].album.uri is not None):
                             album, upload = parse_uri(
                                 self.TRACKS[bId].album.uri)
                             if upload:
                                 data = self.backend.api.get_library_upload_album(
                                     album)
                             else:
                                 data = self.backend.api.get_album(album)
                             images = self.addThumbnails(bId, data)
                 except Exception:
                     logger.error(
                         "YTMusic unable to get image url for uploaded %s",
                         uri,
                     )
             else:
                 images = self.IMAGES[bId]
         ret[uri] = images
         logger.debug("YTMusic found %d image urls for %s", len(images),
                      uri)
     return ret
Exemple #6
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