parent
903d188823
commit
1f617717a6
@ -1,251 +1,428 @@ |
||||
import os |
||||
import re, requests, time, json |
||||
from hashlib import md5 |
||||
from urllib.parse import parse_qsl, urlsplit |
||||
import base64 |
||||
from Crypto.Cipher import AES |
||||
from tabulate import tabulate |
||||
from pywidevineb.L3.cdm import deviceconfig |
||||
from pywidevineb.L3.decrypt.wvdecryptcustom import WvDecrypt |
||||
from tools import get_pssh, dealck |
||||
|
||||
requests = requests.Session() |
||||
|
||||
|
||||
class YouKu: |
||||
def __init__(self, cookie): |
||||
self.cookie = dealck(cookie) |
||||
self.r = "xWrtQpP4Z4RsrRCY" |
||||
self.R = "aq1mVooivzaolmJY5NrQ3A==" |
||||
self.key = "" |
||||
self.headers = { |
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", |
||||
} |
||||
requests.headers.update(self.headers) |
||||
requests.cookies.update(self.cookie) |
||||
|
||||
def youku_sign(self, t, data, token): |
||||
appKey = '24679788' # 固定值 |
||||
'''token值在cookie中''' |
||||
sign = token + '&' + t + '&' + appKey + '&' + data |
||||
md = md5() |
||||
md.update(sign.encode('UTF-8')) |
||||
sign = md.hexdigest() |
||||
return sign |
||||
|
||||
def utid(self): |
||||
json_cookie = requests.cookies.get_dict() |
||||
requests.cookies.clear() |
||||
requests.cookies.update(json_cookie) |
||||
utid = json_cookie.get("cna") |
||||
token = json_cookie.get("_m_h5_tk").split("_")[0] |
||||
return {"utid": utid, "token": token} |
||||
|
||||
# 若直接在首页小窗口上复制的视频网址,是重定向的网址。 |
||||
def redirect(self, url): |
||||
headers = { |
||||
"referer": "https://www.youku.com/", |
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", |
||||
} |
||||
resp = requests.get(url=url) |
||||
return resp.url |
||||
|
||||
def page_parser(self, url): |
||||
vid = re.findall(r"id_(.*?)\.html", url)[0] |
||||
url = "https://openapi.youku.com/v2/videos/show.json" |
||||
params = { |
||||
"client_id": "53e6cc67237fc59a", |
||||
"package": "com.huawei.hwvplayer.youku", |
||||
"ext": "show", |
||||
"video_id": vid |
||||
} |
||||
try: |
||||
response = requests.get(url, params=params).json() |
||||
showid = response["show"]["id"] |
||||
return {"current_showid": showid, "videoId": 0, "vid": vid} |
||||
except Exception as e: |
||||
print(f"获取showid失败:{e}") |
||||
print(f"[red]获取showid失败[/red]") |
||||
|
||||
def get_emb(self, videoId): |
||||
emb = base64.b64encode(("%swww.youku.com/" % videoId).encode('utf-8')).decode('utf-8') |
||||
return emb |
||||
|
||||
# 这个函数用来获取元素的第一个值 |
||||
def takeOne(self, elem): |
||||
return float(elem[0]) |
||||
|
||||
def m3u8_url(self, t, params_data, sign): |
||||
url = "https://acs.youku.com/h5/mtop.youku.play.ups.appinfo.get/1.1/" |
||||
|
||||
params = { |
||||
"jsv": "2.5.8", |
||||
"appKey": "24679788", |
||||
"t": t, |
||||
"sign": sign, |
||||
"api": "mtop.youku.play.ups.appinfo.get", |
||||
"v": "1.1", |
||||
"timeout": "20000", |
||||
"YKPid": "20160317PLF000211", |
||||
"YKLoginRequest": "true", |
||||
"AntiFlood": "true", |
||||
"AntiCreep": "true", |
||||
"type": "jsonp", |
||||
"dataType": "jsonp", |
||||
"callback": "mtopjsonp1", |
||||
"data": params_data, |
||||
} |
||||
resp = requests.get(url=url, params=params) |
||||
result = resp.text |
||||
# print(result) |
||||
data = json.loads(result[12:-1]) |
||||
# print(data) |
||||
ret = data["ret"] |
||||
video_lists = [] |
||||
if ret == ["SUCCESS::调用成功"]: |
||||
stream = data["data"]["data"]["stream"] |
||||
title = data["data"]["data"]["video"]["title"] |
||||
print("解析成功:") |
||||
for video in stream: |
||||
m3u8_url = video["m3u8_url"] |
||||
width = video["width"] |
||||
height = video["height"] |
||||
size = video.get("size", 0) |
||||
size = '{:.1f}'.format(float(size) / 1048576) |
||||
drm_type = video["drm_type"] |
||||
if video['drm_type'] == "default": |
||||
key = "" |
||||
elif drm_type == "cbcs": |
||||
license_url = video["stream_ext"]["uri"] |
||||
key = self.get_cbcs_key(license_url, m3u8_url) |
||||
if key[0]: |
||||
key = key[1][0] |
||||
else: |
||||
encryptR_server = video['encryptR_server'] |
||||
copyright_key = video['stream_ext']['copyright_key'] |
||||
key = self.copyrightDRM(self.r, encryptR_server, copyright_key) |
||||
video_lists.append([title, size + "M", f"{width}x{height}", drm_type, key, m3u8_url]) |
||||
tb = tabulate([[*video_lists[i][:5]] for i in range(len(video_lists))], |
||||
headers=["标题", "分辨率", "视频大小", "drm_type", "base64key"], tablefmt="pretty", |
||||
showindex=range(1, len(video_lists) + 1)) |
||||
ch = input(f"{tb}\n请输入要下载的视频序号:") |
||||
ch = ch.split(",") |
||||
for i in ch: |
||||
title, size, resolution, drm_type, key, m3u8_url = video_lists[int(i) - 1] |
||||
savename = f"{title}_{resolution}_{size}" |
||||
savepath = os.path.join(os.getcwd(), "/download/yk") |
||||
rm3u8_url = m3u8_url.replace("%", "%%") |
||||
common_args = f"N_m3u8DL-RE.exe \"{rm3u8_url}\" --tmp-dir ./cache --save-name \"{title}\" --save-dir \"{savepath}\" --thread-count 16 --download-retry-count 30 --auto-select --check-segments-count" |
||||
if drm_type == "default": |
||||
cmd = common_args |
||||
elif drm_type == "cbcs": |
||||
cmd = f"{common_args} --key {key} -M format=mp4" |
||||
else: |
||||
txt = f''' |
||||
#OUT,{savepath} |
||||
#DECMETHOD,ECB |
||||
#KEY,{key} |
||||
{title}_{resolution}_{size},{m3u8_url} |
||||
''' |
||||
with open("{}.txt".format(title), "a", encoding="gbk") as f: |
||||
f.write(txt) |
||||
print("下载链接已生成") |
||||
continue |
||||
with open("{}.bat".format(title), "a", encoding="gbk") as f: |
||||
f.write(cmd) |
||||
f.write("\n") |
||||
print("下载链接已生成") |
||||
elif ret == ["FAIL_SYS_ILLEGAL_ACCESS::非法请求"]: |
||||
print("请求参数错误") |
||||
elif ret == ["FAIL_SYS_TOKEN_EXOIRED::令牌过期"]: |
||||
print("Cookie过期") |
||||
return 10086 |
||||
else: |
||||
print(ret[0]) |
||||
return 0 |
||||
|
||||
def copyrightDRM(self, r, encryptR_server, copyright_key): |
||||
crypto_1 = AES.new(r.encode(), AES.MODE_ECB) |
||||
key_2 = crypto_1.decrypt(base64.b64decode(encryptR_server)) |
||||
crypto_2 = AES.new(key_2, AES.MODE_ECB) |
||||
return base64.b64encode(base64.b64decode(crypto_2.decrypt(base64.b64decode(copyright_key)))).decode() |
||||
|
||||
def get_cbcs_key(self, license_url, m3u8_url): |
||||
headers = { |
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.82" |
||||
} |
||||
m3u8data = requests.get(m3u8_url, headers=headers).text |
||||
key_url = re.findall(r"URI=\"(.*?)\"", m3u8data)[0] |
||||
response = requests.get(key_url, headers=headers).text |
||||
pssh = response.split("data:text/plain;base64,").pop().split('",')[0] |
||||
wvdecrypt = WvDecrypt(init_data_b64=pssh, cert_data_b64="", device=deviceconfig.device_android_generic) |
||||
headers = { |
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.82", |
||||
} |
||||
dic = dict(parse_qsl(urlsplit(license_url).query)) |
||||
url = license_url.split("?")[0] |
||||
dic["licenseRequest"] = base64.b64encode(wvdecrypt.get_challenge()).decode() |
||||
dic["drmType"] = "widevine" |
||||
response = requests.post(url, data=dic, headers=headers) |
||||
license_b64 = response.json()["data"] |
||||
wvdecrypt.update_license(license_b64) |
||||
Correct, keyswvdecrypt = wvdecrypt.start_process() |
||||
if Correct: |
||||
return Correct, keyswvdecrypt |
||||
|
||||
def get(self, url): |
||||
t = str(int(time.time() * 1000)) |
||||
user_info = self.utid() |
||||
userid = user_info["utid"] |
||||
page_info = self.page_parser(url) |
||||
emb = self.get_emb(page_info["videoId"]) |
||||
steal_params = { |
||||
"ccode": "0502", |
||||
"utid": userid, |
||||
"version": "9.4.39", |
||||
"ckey": "DIl58SLFxFNndSV1GFNnMQVYkx1PP5tKe1siZu/86PR1u/Wh1Ptd+WOZsHHWxysSfAOhNJpdVWsdVJNsfJ8Sxd8WKVvNfAS8aS8fAOzYARzPyPc3JvtnPHjTdKfESTdnuTW6ZPvk2pNDh4uFzotgdMEFkzQ5wZVXl2Pf1/Y6hLK0OnCNxBj3+nb0v72gZ6b0td+WOZsHHWxysSo/0y9D2K42SaB8Y/+aD2K42SaB8Y/+ahU+WOZsHcrxysooUeND", |
||||
"client_ip": "192.168.1.1", |
||||
"client_ts": 1698373135 |
||||
} |
||||
biz_params = { |
||||
"vid": page_info["vid"], |
||||
"h265": 1, |
||||
"preferClarity": 4, |
||||
"media_type": "standard,subtitle", |
||||
"app_ver": "9.4.39", |
||||
"extag": "EXT-X-PRIVINF", |
||||
"play_ability": 16782592, |
||||
"master_m3u8": 1, |
||||
"drm_type": 19, |
||||
"key_index": "web01", |
||||
"encryptR_client": self.R, |
||||
"skh": 1, |
||||
"last_clarity": 5, |
||||
"clarity_chg_ts": 1689341442 |
||||
} |
||||
ad_params = { |
||||
"vip": 1, |
||||
} |
||||
params_data = { |
||||
"steal_params": json.dumps(steal_params), |
||||
"biz_params": json.dumps(biz_params), |
||||
"ad_params": json.dumps(ad_params), |
||||
} |
||||
params_data = json.dumps(params_data) |
||||
sign = self.youku_sign(t, params_data, user_info["token"]) |
||||
return self.m3u8_url(t, params_data, sign) |
||||
|
||||
def start(self, url=None): |
||||
url = input("请输入视频链接:") if url is None else url |
||||
url = self.redirect(url) |
||||
for i in range(3): |
||||
ret = self.get(url) |
||||
if ret: |
||||
continue |
||||
break |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
cookie ="" |
||||
youku = YouKu(cookie) |
||||
youku.start() |
||||
import os |
||||
import re, requests, time, json |
||||
from hashlib import md5 |
||||
from urllib.parse import parse_qsl, urlsplit |
||||
import base64 |
||||
from Crypto.Cipher import AES |
||||
from tabulate import tabulate |
||||
from pywidevine.L3.cdm import deviceconfig |
||||
from pywidevine.L3.decrypt.wvdecryptcustom import WvDecrypt |
||||
from tools import get_pssh, dealck |
||||
|
||||
requests = requests.Session() |
||||
|
||||
|
||||
class YouKu: |
||||
def __init__(self, cookie): |
||||
self.cookie = dealck(cookie) |
||||
self.r = "xWrtQpP4Z4RsrRCY" |
||||
self.R = "aq1mVooivzaolmJY5NrQ3A==" |
||||
self.key = "" |
||||
self.headers = { |
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", |
||||
} |
||||
requests.headers.update(self.headers) |
||||
requests.cookies.update(self.cookie) |
||||
self.ptoken = self.cookie.get("P_pck_rm") |
||||
self.utida = "ZIH81OVlRSMDAOQQiG52i4cO" |
||||
|
||||
def youku_sign(self, t, data, token): |
||||
appKey = '24679788' # 固定值 |
||||
'''token值在cookie中''' |
||||
sign = token + '&' + t + '&' + appKey + '&' + data |
||||
md = md5() |
||||
md.update(sign.encode('UTF-8')) |
||||
sign = md.hexdigest() |
||||
return sign |
||||
|
||||
def utid(self): |
||||
json_cookie = requests.cookies.get_dict() |
||||
requests.cookies.clear() |
||||
requests.cookies.update(json_cookie) |
||||
utid = json_cookie.get("cna") |
||||
token = json_cookie.get("_m_h5_tk").split("_")[0] |
||||
return {"utid": utid, "token": token} |
||||
|
||||
# 若直接在首页小窗口上复制的视频网址,是重定向的网址。 |
||||
def redirect(self, url): |
||||
headers = { |
||||
"referer": "https://www.youku.com/", |
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", |
||||
} |
||||
resp = requests.get(url=url) |
||||
return resp.url |
||||
|
||||
def page_parser(self, url): |
||||
vid = re.findall(r"id_(.*?)\.html", url)[0] |
||||
url = "https://openapi.youku.com/v2/videos/show.json" |
||||
params = { |
||||
"client_id": "53e6cc67237fc59a", |
||||
"package": "com.huawei.hwvplayer.youku", |
||||
"ext": "show", |
||||
"video_id": vid |
||||
} |
||||
try: |
||||
response = requests.get(url, params=params).json() |
||||
showid = response["show"]["id"] |
||||
return {"current_showid": showid, "videoId": 0, "vid": vid} |
||||
except Exception as e: |
||||
print(f"获取showid失败:{e}") |
||||
print(f"[red]获取showid失败[/red]") |
||||
|
||||
def get_emb(self, videoId): |
||||
emb = base64.b64encode(("%swww.youku.com/" % videoId).encode('utf-8')).decode('utf-8') |
||||
return emb |
||||
|
||||
# 这个函数用来获取元素的第一个值 |
||||
def takeOne(self, elem): |
||||
return float(elem[0]) |
||||
|
||||
def save_m3u8(self, video, auto=1): |
||||
title, size, resolution, drm_type, key, stream_type, _, m3u8_url, _ = video |
||||
title = f"{title}_{resolution}_{size}_{stream_type}" |
||||
savepath = os.path.join(os.getcwd(), "/download/yk") |
||||
rm3u8_url = m3u8_url.replace("%", "%%") |
||||
if rm3u8_url.startswith("http"): |
||||
common_args = f"N_m3u8DL-RE.exe \"{rm3u8_url}\" --tmp-dir ./cache --save-name \"{title}\" --save-dir \"{savepath}\" --thread-count 16 --download-retry-count 30 --check-segments-count" |
||||
if auto: |
||||
common_args += " --auto-select" |
||||
if drm_type == "default": |
||||
cmd = common_args |
||||
elif drm_type == "cbcs": |
||||
cmd = f"{common_args} --key {key} -M format=mp4" |
||||
else: |
||||
key = key if ":" not in key else base64.b64encode(bytes.fromhex(key.split(":")[1])).decode() |
||||
txt = f''' |
||||
#OUT,{savepath} |
||||
#DECMETHOD,ECB |
||||
#KEY,{key} |
||||
{title},{m3u8_url} |
||||
''' |
||||
with open("{}.txt".format(title), "a", encoding="gbk") as f: |
||||
f.write(txt) |
||||
print("下载链接已生成") |
||||
return |
||||
else: |
||||
m3u8_path = "{}.m3u8".format(title) |
||||
with open(m3u8_path, "w", encoding="utf-8") as f: |
||||
f.write(m3u8_url) |
||||
common_args = f"N_m3u8DL-RE.exe \"{m3u8_path}\" --tmp-dir ./cache --save-name \"{title}\" --save-dir \"{savepath}\" --thread-count 16 --download-retry-count 30 --check-segments-count" |
||||
if ":" not in key: |
||||
uri = re.findall(r'(http.*)\n', m3u8_url)[0] |
||||
m3u8_text = requests.get(uri).text |
||||
keyid = re.findall(r'KEYID=0x(.*),IV', m3u8_text)[0].lower() |
||||
key = "--key {}:{}".format(keyid, base64.b64decode(key).hex()) |
||||
cmd = f"{common_args} {key} -M format=mp4" |
||||
with open("{}.bat".format(title), "a", encoding="gbk") as f: |
||||
f.write(cmd) |
||||
f.write("\n") |
||||
f.close() |
||||
print("下载链接已生成") |
||||
|
||||
def m3u8_url(self, t, params_data, sign, vid): |
||||
url = "https://acs.youku.com/h5/mtop.youku.play.ups.appinfo.get/1.1/" |
||||
|
||||
params = { |
||||
"jsv": "2.5.8", |
||||
"appKey": "24679788", |
||||
"t": t, |
||||
"sign": sign, |
||||
"api": "mtop.youku.play.ups.appinfo.get", |
||||
"v": "1.1", |
||||
"timeout": "20000", |
||||
"YKPid": "20160317PLF000211", |
||||
"YKLoginRequest": "true", |
||||
"AntiFlood": "true", |
||||
"AntiCreep": "true", |
||||
"type": "jsonp", |
||||
"dataType": "jsonp", |
||||
"callback": "mtopjsonp1", |
||||
"data": params_data, |
||||
} |
||||
resp = requests.get(url=url, params=params) |
||||
result = resp.text |
||||
# print(result) |
||||
data = json.loads(result[12:-1]) |
||||
# print(data) |
||||
ret = data["ret"] |
||||
video_lists = [] |
||||
if ret == ["SUCCESS::调用成功"]: |
||||
stream = data["data"]["data"].get("stream", []) |
||||
title = data["data"]["data"].get("video", {}).get("title", "") |
||||
print("解析成功:") |
||||
keys = {} |
||||
tv_stream = self.get_TV_stream(vid) |
||||
stream.extend(tv_stream) |
||||
for video in stream: |
||||
m3u8_url = video["m3u8_url"] |
||||
width = video["width"] |
||||
height = video["height"] |
||||
size = video.get("size", 0) |
||||
size = '{:.1f}'.format(float(size) / 1048576) |
||||
drm_type = video["drm_type"] |
||||
audio_lang = video["audio_lang"] |
||||
audio = video['stream_ext'].get("audioGroupId", "") or "default" |
||||
if audio_lang == "default": |
||||
audio_lang = "guoyu" |
||||
language = [] |
||||
language = re.findall(r'LANGUAGE="([\w\s]+)"', m3u8_url) |
||||
# print("language是------------------>>>>>", language) |
||||
if 'en' in language: |
||||
audio_lang = "en" |
||||
if video['drm_type'] == "default": |
||||
key = "" |
||||
elif audio_lang not in keys.keys(): |
||||
if drm_type == "cbcs": |
||||
license_url = video["stream_ext"]["uri"] |
||||
key = self.get_cbcs_key(license_url, m3u8_url) |
||||
if key[0]: |
||||
key = key[1][0] |
||||
else: |
||||
encryptR_server = video['encryptR_server'] |
||||
copyright_key = video['stream_ext']['copyright_key'] |
||||
key = self.copyrightDRM(self.r, encryptR_server, copyright_key) |
||||
keys[audio_lang] = key |
||||
else: |
||||
key = keys[audio_lang] |
||||
video_lists.append( |
||||
[title, size + "M", f"{width}x{height}", drm_type, key, video["stream_type"], |
||||
audio + "_" + audio_lang, m3u8_url, |
||||
video.get("size", 0)]) |
||||
video_lists = sorted(video_lists, key=lambda x: x[-1], reverse=True) |
||||
tb = tabulate([[*video_lists[i][:7]] for i in range(len(video_lists))], |
||||
headers=["标题", "视频大小", "分辨率", "drm_type", "base64key", "stream_type", "audio"], |
||||
tablefmt="pretty", |
||||
showindex=range(1, len(video_lists) + 1)) |
||||
ch = input(f"{tb}\n请输入要下载的视频序号,输入0尝试自动选择最高清晰度视频:") |
||||
if ch == "0": |
||||
self.save_m3u82(video_lists) |
||||
return 0 |
||||
ch = ch.split(",") |
||||
for i in ch: |
||||
video = video_lists[int(i) - 1] |
||||
self.save_m3u8(video) |
||||
elif ret == ["FAIL_SYS_ILLEGAL_ACCESS::非法请求"]: |
||||
print("请求参数错误") |
||||
elif ret == ["FAIL_SYS_TOKEN_EXOIRED::令牌过期"]: |
||||
print("Cookie过期") |
||||
return 10086 |
||||
else: |
||||
print(ret[0]) |
||||
return 0 |
||||
|
||||
def copyrightDRM(self, r, encryptR_server, copyright_key): |
||||
try: |
||||
crypto_1 = AES.new(r.encode(), AES.MODE_ECB) |
||||
key_2 = crypto_1.decrypt(base64.b64decode(encryptR_server)) |
||||
crypto_2 = AES.new(key_2, AES.MODE_ECB) |
||||
return base64.b64encode(base64.b64decode(crypto_2.decrypt(base64.b64decode(copyright_key)))).decode() |
||||
except: |
||||
return "" |
||||
|
||||
def get_cbcs_key(self, license_url, m3u8_url): |
||||
headers = { |
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.82" |
||||
} |
||||
m3u8data = requests.get(m3u8_url, headers=headers).text |
||||
key_url = re.findall(r"URI=\"(.*?)\"", m3u8data)[0] |
||||
response = requests.get(key_url, headers=headers).text |
||||
pssh = response.split("data:text/plain;base64,").pop().split('",')[0] |
||||
wvdecrypt = WvDecrypt(init_data_b64=pssh, cert_data_b64="", device=deviceconfig.device_android_generic) |
||||
headers = { |
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.82", |
||||
} |
||||
dic = dict(parse_qsl(urlsplit(license_url).query)) |
||||
url = license_url.split("?")[0] |
||||
dic["licenseRequest"] = base64.b64encode(wvdecrypt.get_challenge()).decode() |
||||
dic["drmType"] = "widevine" |
||||
response = requests.post(url, data=dic, headers=headers) |
||||
license_b64 = response.json()["data"] |
||||
wvdecrypt.update_license(license_b64) |
||||
Correct, keyswvdecrypt = wvdecrypt.start_process() |
||||
if Correct: |
||||
return Correct, keyswvdecrypt |
||||
|
||||
def get_TV_stream(self, vid): |
||||
headers = { |
||||
"user-agent": "OTTSDK;1.0.8.6;Android;9;2203121C" |
||||
} |
||||
|
||||
def getdata(): |
||||
response = requests.get(url, headers=headers, params=params) |
||||
try: |
||||
data = response.json()["data"] |
||||
title = data['show']["title"] |
||||
streams = data["stream"] |
||||
streamss.extend(streams) |
||||
return title, streams |
||||
except Exception as e: |
||||
return None, [] |
||||
|
||||
url = "https://ups.youku.com/ups/get.json" # light_get.json |
||||
params = { |
||||
"ckey": "7B19C0AB12633B22E7FE81271162026020570708D6CC189E4924503C49D243A0DE6CD84A766832C2C99898FC5ED31F3709BB3CDD82C96492E721BDD381735026", |
||||
"client_ip": "192.168.3.1", |
||||
"client_ts": "1697343919", |
||||
"utid": self.utida, |
||||
"pid": "b777e6ae3c99e26", |
||||
# HAIER_PID = "36214723575196"; JIMI_PID = "3b777e6ae3c99e26";SONY_PID = "36281532078091"; |
||||
"player_type": "dnahard", # system:hls,dnahard: cmfv |
||||
"app_ver": "11.4.6.4", # 2121104604,2121100600,11.0.6.0,11.4.6.4 |
||||
"ccode": "0103010261", # sony :0103010261, jimi:010301025C,haier:0103010275 280 |
||||
"player_source": "21", # 20 sdr 21hfr 22dolby 23bit10 |
||||
"encryptR_client": "fTWuKHLOVUoOide+VH/h8w==", |
||||
"key_index": "key01", |
||||
"vid": vid, |
||||
"h265": "1", |
||||
"media_type": "standard,sei", |
||||
"client_id": "", |
||||
"ptoken": self.ptoken, |
||||
"drm_type": "7", |
||||
"extag": "EXT-X-PRIVINF", |
||||
"extag_fields": "STREAMTYPE", |
||||
"device_name": "XR-98X90L", |
||||
"play_ability": "405929984", |
||||
"preferClarity": "23", |
||||
"master_m3u8": "0", |
||||
"play_ability_v2": "2222222", |
||||
"site": "1", |
||||
"fu": "1", |
||||
"vs": "1.0", |
||||
"os": "android", |
||||
"osv": "12.1.1", |
||||
"bt": "tv", |
||||
"aw": "a", |
||||
"p": "27", |
||||
"mdl": "XR-98X90L", |
||||
"device_model": "XR-98X90L", |
||||
"": "" |
||||
} |
||||
streamss = [] |
||||
player_source = [20, 23, 22, 21] |
||||
player_type = ["system", "dnahard"] |
||||
for i in player_source: |
||||
params["player_source"] = str(i) |
||||
for j in player_type: |
||||
params["player_type"] = j |
||||
getdata() |
||||
params["ccode"] = "0103010275" |
||||
params["player_source"] = "21" |
||||
params["player_type"] = "system" |
||||
getdata() |
||||
params.update({"app_ver": "11.4.7.0", |
||||
"play_ability": "274877906943", |
||||
"play_ability_v2": "1111111", |
||||
"pid": "52f8ca2b4982124b", }) |
||||
play_ability_v2 = ["1111111111", "0111111111", 1111000000, 1101110000, 1101101000, 11101100100, 1101100010] |
||||
# "play_ability_v2": "1111111111", # 1:dolby_vision 2:hdr10 3:dtsc -5:dolby_atmos -4:dolby_around -3:dts -2:aac_hd3_51 |
||||
#仅供测试,建议自行修改,减少请求次数,否则容易封,提示客户端无权播放 |
||||
for v2 in play_ability_v2: |
||||
params["play_ability_v2"] = v2 |
||||
getdata() |
||||
url = "https://ups.youku.com/ups/light_get.json" |
||||
params["ccode"] = "0103010280" |
||||
params["drm_type"] = 0 |
||||
getdata() |
||||
streamss = sorted(streamss, key=lambda x: x["size"], reverse=True) |
||||
return streamss |
||||
|
||||
def get(self, url): |
||||
t = str(int(time.time() * 1000)) |
||||
user_info = self.utid() |
||||
userid = user_info["utid"] |
||||
page_info = self.page_parser(url) |
||||
emb = self.get_emb(page_info["videoId"]) |
||||
steal_params = { |
||||
"ccode": "0502", |
||||
"utid": userid, |
||||
"version": "9.4.39", |
||||
"ckey": "DIl58SLFxFNndSV1GFNnMQVYkx1PP5tKe1siZu/86PR1u/Wh1Ptd+WOZsHHWxysSfAOhNJpdVWsdVJNsfJ8Sxd8WKVvNfAS8aS8fAOzYARzPyPc3JvtnPHjTdKfESTdnuTW6ZPvk2pNDh4uFzotgdMEFkzQ5wZVXl2Pf1/Y6hLK0OnCNxBj3+nb0v72gZ6b0td+WOZsHHWxysSo/0y9D2K42SaB8Y/+aD2K42SaB8Y/+ahU+WOZsHcrxysooUeND", |
||||
"client_ip": "192.168.1.1", |
||||
"client_ts": 1698373135 |
||||
} |
||||
biz_params = { |
||||
"vid": page_info["vid"], |
||||
"h265": 1, |
||||
"preferClarity": 4, |
||||
"media_type": "standard,subtitle", |
||||
"app_ver": "9.4.39", |
||||
"extag": "EXT-X-PRIVINF", |
||||
"play_ability": 16782592, |
||||
"master_m3u8": 1, |
||||
"drm_type": 19, |
||||
"key_index": "web01", |
||||
"encryptR_client": self.R, |
||||
"skh": 1, |
||||
"last_clarity": 5, |
||||
"clarity_chg_ts": 1689341442, |
||||
"needad": 0, |
||||
} |
||||
ad_params = { |
||||
"vip": 1, |
||||
"needad":0, |
||||
} |
||||
params_data = { |
||||
"steal_params": json.dumps(steal_params), |
||||
"biz_params": json.dumps(biz_params), |
||||
"ad_params": json.dumps(ad_params), |
||||
} |
||||
params_data = json.dumps(params_data) |
||||
sign = self.youku_sign(t, params_data, user_info["token"]) |
||||
return self.m3u8_url(t, params_data, sign, page_info["vid"]) |
||||
|
||||
def start(self, url=None): |
||||
url = input("请输入视频链接:") if url is None else url |
||||
url = self.redirect(url) if url.startswith("https://") else f"https://v.youku.com/v_show/id_{url}.html" |
||||
for i in range(3): |
||||
ret = self.get(url) |
||||
if ret: |
||||
continue |
||||
break |
||||
|
||||
def save_m3u82(self, video_lists): |
||||
video_lists_a = [x for x in video_lists if "cmfv5hd" in x[5]] |
||||
if video_lists_a: |
||||
videoTypes = set() |
||||
audioTypes = [] |
||||
langs = [] |
||||
keys="" |
||||
m3u8data = "#EXTM3U\n" |
||||
for video in video_lists_a: |
||||
audioType = video[6] |
||||
lang = audioType.split("_")[-1] |
||||
m3u8_url = video[-2] |
||||
key=video[4] |
||||
if lang not in langs: |
||||
if ":" not in key: |
||||
uri = re.findall(r'(http.*)\n', m3u8_url)[0] |
||||
m3u8_text = requests.get(uri).text |
||||
keyid = re.findall(r'KEYID=0x(.*),IV', m3u8_text)[0].lower() |
||||
keys += " --key {}:{}".format(keyid, base64.b64decode(key).hex()) |
||||
else: |
||||
keys += f" --key {key}" |
||||
langs.append(lang) |
||||
videoType = video[5].split("_")[1] |
||||
''' |
||||
if videoType not in videoTypes: |
||||
m3u8data += "\n".join(m3u8_url.split("\n")[2:4]) |
||||
videoTypes.append(videoType) |
||||
elif audioType not in audioTypes: |
||||
m3u8data += m3u8_url.split("\n")[1] |
||||
audioTypes.append(audioType) |
||||
部分视频存在多个音频流,会出错 |
||||
所以直接把所有的m3u8链接都写入m3u8文件,手动在下载时选择 |
||||
''' |
||||
if videoType not in videoTypes or audioType not in audioTypes: |
||||
m3u8data += "\n".join(m3u8_url.split("\n")[1:-2]) |
||||
videoTypes.add(videoType) |
||||
audioTypes.append(audioType) |
||||
m3u8data += "\n#EXT-X-ENDLIST" |
||||
video_lists_a[0][7] = m3u8data |
||||
video_lists_a[0][4] = keys |
||||
self.save_m3u8(video_lists_a[0]) |
||||
else: |
||||
self.save_m3u8(video_lists[0]) |
||||
if __name__ == '__main__': |
||||
cookie = '' |
||||
youku = YouKu(cookie) |
||||
youku.start("XNjE0OTU0NDU0NA==") |
||||
|
Loading…
Reference in new issue