feat: layed out the calculation of prefered format.
This commit is contained in:
parent
d93e536ffd
commit
c7279eb424
@ -128,22 +128,43 @@ class YouTubeMusicCredentials:
|
|||||||
return _extract_player_info(self.player_url)
|
return _extract_player_info(self.player_url)
|
||||||
|
|
||||||
|
|
||||||
class MusicKrakenYoutubeIE(YoutubeIE):
|
class YTDLLogger:
|
||||||
def __init__(self, *args, main_instance: YoutubeMusic, **kwargs):
|
def __init__(self, logger: logging.Logger):
|
||||||
self.main_instance = main_instance
|
self.logger = logger
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
def debug(self, msg):
|
||||||
|
self.logger.debug(msg)
|
||||||
|
|
||||||
|
def warning(self, msg):
|
||||||
|
self.logger.warning(msg)
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
self.logger.error(msg)
|
||||||
|
|
||||||
|
|
||||||
class MusicKrakenYoutubeDL(youtube_dl.YoutubeDL):
|
class MusicKrakenYoutubeDL(youtube_dl.YoutubeDL):
|
||||||
def __init__(self, main_instance: YoutubeMusic, ydl_opts: dict, **kwargs):
|
def __init__(self, main_instance: YoutubeMusic, ydl_opts: dict, **kwargs):
|
||||||
self.main_instance = main_instance
|
self.main_instance = main_instance
|
||||||
super().__init__(ydl_opts or {}, **kwargs)
|
ydl_opts = ydl_opts or {}
|
||||||
|
ydl_opts.update({
|
||||||
|
"logger": YTDLLogger(self.main_instance.LOGGER),
|
||||||
|
})
|
||||||
|
|
||||||
|
super().__init__(ydl_opts, **kwargs)
|
||||||
super().__enter__()
|
super().__enter__()
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
super().__exit__(None, None, None)
|
super().__exit__(None, None, None)
|
||||||
|
|
||||||
|
|
||||||
|
class MusicKrakenYoutubeIE(YoutubeIE):
|
||||||
|
def __init__(self, *args, main_instance: YoutubeMusic, **kwargs):
|
||||||
|
self.main_instance = main_instance
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _extract_player_url(self, *ytcfgs, **kw_webpage):
|
||||||
|
return youtube_settings["player_url"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class YoutubeMusic(SuperYouTube):
|
class YoutubeMusic(SuperYouTube):
|
||||||
@ -459,157 +480,39 @@ class YoutubeMusic(SuperYouTube):
|
|||||||
|
|
||||||
return album
|
return album
|
||||||
|
|
||||||
@lru_cache()
|
def _get_best_format(self, format_list: List[Dict]) -> str:
|
||||||
def _extract_signature_function(self, player_url):
|
def _calc_score(_f: dict):
|
||||||
r = self.connection.get(player_url)
|
s = 0
|
||||||
if r is None:
|
|
||||||
return lambda x: None
|
|
||||||
|
|
||||||
code = r.text
|
_url = _f.get("url", "")
|
||||||
|
if "mime=audio" in _url:
|
||||||
|
s += 100
|
||||||
|
|
||||||
funcname = self._search_regex((
|
return s
|
||||||
r'\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
||||||
r'\b[a-zA-Z0-9]+\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
||||||
r'\bm=(?P<sig>[a-zA-Z0-9$]{2,})\(decodeURIComponent\(h\.s\)\)',
|
|
||||||
r'\bc&&\(c=(?P<sig>[a-zA-Z0-9$]{2,})\(decodeURIComponent\(c\)\)',
|
|
||||||
r'(?:\b|[^a-zA-Z0-9$])(?P<sig>[a-zA-Z0-9$]{2,})\s*=\s*function\(\s*a\s*\)\s*{\s*a\s*=\s*a\.split\(\s*""\s*\)(?:;[a-zA-Z0-9$]{2}\.[a-zA-Z0-9$]{2}\(a,\d+\))?',
|
|
||||||
r'(?P<sig>[a-zA-Z0-9$]+)\s*=\s*function\(\s*a\s*\)\s*{\s*a\s*=\s*a\.split\(\s*""\s*\)',
|
|
||||||
# Obsolete patterns
|
|
||||||
r'("|\')signature\1\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
||||||
r'\.sig\|\|(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
||||||
r'yt\.akamaized\.net/\)\s*\|\|\s*.*?\s*[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?:encodeURIComponent\s*\()?\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
||||||
r'\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
||||||
r'\b[a-zA-Z0-9]+\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
||||||
r'\bc\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\('
|
|
||||||
),
|
|
||||||
code, group='sig')
|
|
||||||
|
|
||||||
jsi = JSInterpreter(code)
|
highest_score = 0
|
||||||
initial_function = jsi.extract_function(funcname)
|
best_format = {}
|
||||||
|
for _format in format_list:
|
||||||
|
print(_format)
|
||||||
|
|
||||||
return lambda s: initial_function([s])
|
_s = _calc_score(_format)
|
||||||
|
if _s >= highest_score:
|
||||||
|
highest_score = _s
|
||||||
|
best_format = _format
|
||||||
|
|
||||||
def _decrypt_signature(self, video_id, s):
|
return best_format.get("url")
|
||||||
print(youtube_settings["player_url"])
|
|
||||||
res = self._extract_signature_function(player_url=youtube_settings["player_url"])
|
|
||||||
test_string = ''.join(map(str, range(len(s))))
|
|
||||||
cache_spec = [ord(c) for c in res(test_string)]
|
|
||||||
signing_func = lambda _s: ''.join(_s[i] for i in cache_spec)
|
|
||||||
return signing_func(s)
|
|
||||||
|
|
||||||
def _parse_adaptive_formats(self, data: list, video_id) -> dict:
|
|
||||||
best_format = None
|
|
||||||
best_bitrate = 0
|
|
||||||
|
|
||||||
def parse_format(fmt: dict):
|
|
||||||
fmt_url = fmt.get('url')
|
|
||||||
|
|
||||||
if not fmt_url:
|
|
||||||
sc = parse_qs(possible_format["signatureCipher"])
|
|
||||||
print(sc["s"][0])
|
|
||||||
signature = self._decrypt_signature(video_id, sc['s'][0])
|
|
||||||
print(signature)
|
|
||||||
|
|
||||||
tmp = sc.get("sp", ["sig"])
|
|
||||||
sig_key = "signature" if len(tmp) <= 0 else tmp[-1]
|
|
||||||
|
|
||||||
fmt_url = sc.get("url", [None])[0]
|
|
||||||
|
|
||||||
ftm_parsed = urlparse(fmt_url)
|
|
||||||
q = parse_qs(ftm_parsed.query)
|
|
||||||
q[sig_key] = [signature]
|
|
||||||
print(json.dumps(q, indent=4))
|
|
||||||
print(sig_key)
|
|
||||||
query_str = urlencode(q)
|
|
||||||
print(query_str)
|
|
||||||
|
|
||||||
fmt_url = urlunparse((
|
|
||||||
ftm_parsed.scheme,
|
|
||||||
ftm_parsed.netloc,
|
|
||||||
ftm_parsed.path,
|
|
||||||
ftm_parsed.params,
|
|
||||||
query_str,
|
|
||||||
ftm_parsed.fragment
|
|
||||||
))
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not isinstance(url, tuple):
|
|
||||||
url = compat_urllib_parse.urlparse(url)
|
|
||||||
query = kwargs.pop('query_update', None)
|
|
||||||
if query:
|
|
||||||
qs = compat_parse_qs(url.query)
|
|
||||||
qs.update(query)
|
|
||||||
kwargs['query'] = compat_urllib_parse_urlencode(qs, True)
|
|
||||||
kwargs = compat_kwargs(kwargs)
|
|
||||||
return compat_urllib_parse.urlunparse(url._replace(**kwargs))
|
|
||||||
"""
|
|
||||||
|
|
||||||
return {
|
|
||||||
"bitrate": fmt.get("bitrate"),
|
|
||||||
"url": fmt_url
|
|
||||||
}
|
|
||||||
|
|
||||||
for possible_format in sorted(data, key=lambda x: x.get("bitrate", 0)):
|
|
||||||
if best_bitrate <= 0:
|
|
||||||
# no format has been found yet
|
|
||||||
best_format = possible_format
|
|
||||||
|
|
||||||
if possible_format.get('targetDurationSec') or possible_format.get('drmFamilies'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
mime_type: str = possible_format["mimeType"]
|
|
||||||
if not mime_type.startswith("audio"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
bitrate = int(possible_format.get("bitrate", 0))
|
|
||||||
|
|
||||||
if bitrate > best_bitrate:
|
|
||||||
best_bitrate = bitrate
|
|
||||||
best_format = possible_format
|
|
||||||
|
|
||||||
if bitrate >= main_settings["bitrate"]:
|
|
||||||
break
|
|
||||||
|
|
||||||
return parse_format(best_format)
|
|
||||||
|
|
||||||
def fetch_song(self, source: Source, stop_at_level: int = 1) -> Song:
|
def fetch_song(self, source: Source, stop_at_level: int = 1) -> Song:
|
||||||
# implement the functionality yt_dl provides
|
# implement the functionality yt_dl provides
|
||||||
ydl_res = self.yt_ie._real_extract(source.url)
|
ydl_res = self.yt_ie._real_extract(source.url)
|
||||||
print(ydl_res)
|
|
||||||
|
|
||||||
source.audio_url = ydl_res.get("formats")[0].get("url")
|
source.audio_url = self._get_best_format(ydl_res.get("formats", [{}]))
|
||||||
song = Song(
|
song = Song(
|
||||||
title=ydl_res.get("title"),
|
title=ydl_res.get("title"),
|
||||||
source_list=[source],
|
source_list=[source],
|
||||||
)
|
)
|
||||||
return song
|
return song
|
||||||
|
|
||||||
parsed_url = urlparse(source.url)
|
|
||||||
video_id = parse_qs(parsed_url.query)['v']
|
|
||||||
if len(video_id) <= 0:
|
|
||||||
return song
|
|
||||||
browse_id = video_id[0]
|
|
||||||
|
|
||||||
r = self.connection.post(
|
|
||||||
url=get_youtube_url(path="/youtubei/v1/player", query=f"key={self.credentials.api_key}&prettyPrint=false"),
|
|
||||||
json={
|
|
||||||
"videoId": browse_id,
|
|
||||||
"context": {**self.credentials.context, "adSignalsInfo": {"params": []}}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if r is None:
|
|
||||||
return song
|
|
||||||
|
|
||||||
data = r.json()
|
|
||||||
|
|
||||||
dump_to_file("yt_video_overview.json", data, exit_after_dump=False)
|
|
||||||
|
|
||||||
available_formats = data.get("streamingData", {}).get("adaptiveFormats", [])
|
|
||||||
|
|
||||||
if len(available_formats) > 0:
|
|
||||||
source.audio_url = self._parse_adaptive_formats(available_formats, video_id=browse_id).get("url")
|
|
||||||
|
|
||||||
return song
|
|
||||||
|
|
||||||
def download_song_to_target(self, source: Source, target: Target, desc: str = None) -> DownloadResult:
|
def download_song_to_target(self, source: Source, target: Target, desc: str = None) -> DownloadResult:
|
||||||
if source.audio_url is None:
|
if source.audio_url is None:
|
||||||
|
Loading…
Reference in New Issue
Block a user