def _parse(self): if self.parseResults is None: try: inst = YoutubeDL({ "outtmpl": "%(title)s-%(id)s.%(ext)s", "skip_download": True, "quiet": True, #"format": self.formats, "verbose": True }) #self.parseResults = inst.download([self.url])['entries'][0] inst.get_info_extractor("Youtube") inst.get_info_extractor("Vimeo") self.parseResults = inst.extract_info(self.url, False) except thirdparty_grabber.youtube_dl.utils.DownloadError as e: if "This video does not exist" in e: raise myexceptions.FetchingException("YouTube said: This video does not exist.", self.config.ERROR_404) if "GEMA" in e: raise myexceptions.FetchingException("BANNED BY GEMA", self.config.ERROR_GEMA) raise myexceptions.FetchingException(e.message, self.config.ERROR_CUSTOMMESSAGE)
class YoutubeMusicPlaybackProvider(backend.PlaybackProvider): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.last_id = None self.Youtube_Player_URL = None self.YoutubeDL = YoutubeDL({ "proxy": httpclient.format_proxy(self.backend.config["proxy"]), "nocheckcertificate": True, }) self.YoutubeIE = self.YoutubeDL.get_info_extractor('Youtube') 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 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
class YTMusicPlaybackProvider(backend.PlaybackProvider): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.last_id = None self.Youtube_Player_URL = None self.YoutubeDL = YoutubeDL({ "proxy": httpclient.format_proxy(self.backend.config["proxy"]), "nocheckcertificate": True, }) self.YoutubeIE = self.YoutubeDL.get_info_extractor("Youtube") 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 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