import urllib.request
-MAX_MEMORY_BYTES = 128 * 1024*1024
-USER_AGENT = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1"
+USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
MIMETYPES = {
"video/mp4": "mp4",
def find_func_name(script):
FUNC_NAME = R"([a-zA-Z0-9$]+)"
+ DECODE_URI_COMPONENT = R"(\(decodeURIComponent)?"
FUNC_PARAMS = R"(\([a-zA-Z,\.]+\.s\))"
TERMINATOR = R"[,;\)]"
- PATTERN = FUNC_NAME + FUNC_PARAMS + TERMINATOR
+ PATTERN = FUNC_NAME + DECODE_URI_COMPONENT + FUNC_PARAMS + TERMINATOR
match = re.search(PATTERN, script)
func_name = match.groups()[0]
sandbox.window = sandbox;
const code_string = %(code)s + ';';
- const exec_string = 'transformed_signature = %(func_name)s("", "MARKER", signature);';
+ const exec_string = 'transformed_signature = %(func_name)s(signature);';
vm.runInNewContext(code_string + exec_string, sandbox);
- function findSignature(obj) {
- if (typeof obj !== 'object') {
- return;
- }
- for (const [key, value] of Object.entries(obj)) {
- if (key === 'MARKER') {
- return value;
- }
- const result = findSignature(value);
- if (result) {
- return result;
- }
- }
- }
- console.log(findSignature(sandbox.transformed_signature));
+ console.log(sandbox.transformed_signature);
""" % params)
p.stdin.write(js_decode_script.encode("utf-8"))
p.stdin.close()
transformed_signature = p.stdout.read().decode("utf-8").strip()
+ transformed_signature = urllib.parse.unquote(transformed_signature)
if p.wait() != 0:
raise Exception("js failed to execute: %d" % p.returncode)
return transformed_signature
def get_best_video(player_config):
- url_data_list = player_config["args"]["url_encoded_fmt_stream_map"].split(",")
js_url = player_config["assets"]["js"]
+ player_args = player_config["args"]
+ player_response = json.loads(player_args["player_response"])
+ formats = player_response["streamingData"]["formats"]
+
best_url = None
best_quality = None
best_extension = None
- for url_data in url_data_list:
- url_data = urllib.parse.parse_qs(url_data)
- mimetype = url_data["type"][0].split(";")[0]
- quality = url_data["quality"][0]
+ for format_data in formats:
+ mimetype = format_data["mimeType"].split(";")[0]
+ quality = format_data["quality"]
- if "stereo3d" in url_data:
- continue
if quality not in QUALITIES:
continue
if mimetype not in MIMETYPES:
if best_quality is not None and quality < best_quality:
continue
- video_url = url_data["url"][0]
- if "sig" in url_data:
- signature = url_data["sig"][0]
- elif "s" in url_data:
- signature = decode_signature(js_url, url_data["s"][0])
+ if "signatureCipher" in format_data:
+ cipher = urllib.parse.parse_qs(format_data["signatureCipher"])
+ video_url = cipher["url"][0]
+ if "sig" in cipher:
+ signature = cipher["sig"][0]
+ elif "s" in cipher:
+ signature = decode_signature(js_url, cipher["s"][0])
+ sp = cipher.get("sp", ["signature"])[0]
+ video_url = append_to_qs(video_url, {sp: signature})
else:
- signature = None
-
- if signature:
- video_url = append_to_qs(video_url, {"signature": signature})
+ video_url = format_data["url"]
best_url = video_url
best_quality = quality
if not video_url:
return None, None
- filename = sanitize_filename(page.title)
- filename += "." + extension
+ title = player_config["args"].get("title", None)
+ if not title:
+ title = json.loads(player_config["args"]["player_response"])["videoDetails"]["title"]
+ if not title:
+ title = "Unknown title"
+
+ filename = sanitize_filename(title) + "." + extension
return video_url, filename
class YouTubeVideoPageParser(html.parser.HTMLParser):
def __init__(self):
super().__init__()
- self.title = None
self.unavailable_message = None
self.scripts = []
def handle_starttag(self, tag, attrs):
attrs = dict(attrs)
- self._handle_title(tag, attrs)
self._handle_unavailable_message(tag, attrs)
self._handle_script(tag, attrs)
def _ignore_data(self, _):
pass
- def _handle_title(self, tag, attrs):
- if tag == "title":
- self.handle_data = self._handle_title_data
-
- def _handle_title_data(self, data):
- self.title = data.strip()
-
def _handle_unavailable_message(self, tag, attrs):
if attrs.get("id", None) == "unavailable-message":
self.handle_data = self._handle_unavailable_message_data