Compare commits

..

6 Commits
old ... main

  1. 32
      README.md
  2. 2
      iqy.py
  3. 10
      main.py
  4. 49
      pywidevine/L3/cdm/cdm.py
  5. 22
      tx.py
  6. 253
      yk.py

@ -6,18 +6,12 @@
1.爱奇艺普通视频以及wvdrm内容
2.腾讯普通视频
2.腾讯普通视频(不再支持)
3.优酷普通视频,wv加密内容以及自研加密内容
#### 暂不支持
腾讯wv(我网页都播放不了),chacha20(超前买不起)
### 使用说明
只提供win10文件,其余系统自行编译,下载相关依赖
自行编译,下载相关依赖
本软件支持解析,不支持破解
1.下载链接中全部文件,放到同一个文件夹
@ -57,3 +51,25 @@ txt需要下载https://www.52pojie.cn/thread-1631141-1-1.html,直接拖进下
2.增加爱奇艺4k
3.下载器修复,优酷存在多个map导致下载只有6s,修复爱奇艺wv加密,读取不到init.mp4
## 2023-11-20更新
1.删除腾讯支持
2.增加优酷帧享
![image](https://github.com/Tontonnow/ttt/assets/122251963/199b9f19-399a-48b7-af0d-37cc44d4c4ed)
## 2023-11-25更新
1.修复优酷key出错
## 2023-12-8更新
1.增加优酷dts等音效
![QQ截图20231208185853](https://github.com/Tontonnow/ttt/assets/122251963/34cf85a4-18e4-4177-a07a-c57e9dc6303a)
下载选项选择0,自动生成m3u8大师列表,暂时支持单语种
![image](https://github.com/Tontonnow/ttt/assets/122251963/efb5746c-33df-48cc-9077-fb93f45e8975)
## 2024-1-25更新
1.增加音效和杜比
2.测试支持多音轨
![image](https://github.com/Tontonnow/ttt/assets/122251963/770479d9-ce45-4083-8766-801cb164c6f3)
![image](https://github.com/Tontonnow/ttt/assets/122251963/d8200b25-7f95-4eed-9df0-82a94d3bbaa4)

@ -176,7 +176,7 @@ class iqy:
dash = f'/dash?'
for a, b in params.items():
dash += f"{a}={b}&"
dash = dash[:-1] + "&bop=" + parse.quote(self.bop) + "&ut=14"
dash = dash[:-1] + "&bop=" + parse.quote(self.bop) + "&ut=13&ut=4&ut=5&ut=14&ut=57"
vf = md5(dash + "tle8orw4vetejc62int3uewiniecr18i")
dash += f"&vf={vf}"
return dash

@ -28,18 +28,18 @@ import re, requests, time, json
from hashlib import md5
import base64
from tools import rsa_dec, aes_decrypt, djb2Hash, b64decode, sha1withrsa, check_file, get_config
from tx import TX
from iqy import iqy
from yk import YouKu
if __name__ == '__main__':
check_file()
config = get_config()
txck = config["txck"]
yk = config["yk"]
aqy = config["aqy"]
try:
tx = TX(txck)
iq = iqy(aqy)
youku = YouKu(yk)
except Exception as e:
@ -47,9 +47,7 @@ if __name__ == '__main__':
print(e)
while True:
url = input("请输入视频链接:")
if "v.qq.com" in url:
tx.run(url)
elif "iqiyi.com" in url:
if "iqiyi.com" in url:
iq.run(url)
elif "youku.com" in url:
youku.start(url)

@ -7,9 +7,9 @@ import binascii
from google.protobuf.message import DecodeError
from google.protobuf import text_format
from pywidevineb.L3.cdm.formats import wv_proto2_pb2 as wv_proto2
from pywidevineb.L3.cdm.session import Session
from pywidevineb.L3.cdm.key import Key
from pywidevine.L3.cdm.formats import wv_proto2_pb2 as wv_proto2
from pywidevine.L3.cdm.session import Session
from pywidevine.L3.cdm.key import Key
from Cryptodome.Random import get_random_bytes
from Cryptodome.Random import random
from Cryptodome.Cipher import PKCS1_OAEP, AES
@ -19,7 +19,6 @@ from Cryptodome.Signature import pss
from Cryptodome.Util import Padding
import logging
class Cdm:
def __init__(self):
self.logger = logging.getLogger(__name__)
@ -146,9 +145,14 @@ class Cdm:
else:
license_request = wv_proto2.SignedLicenseRequest()
client_id = wv_proto2.ClientIdentification()
if not os.path.exists(session.device_config.device_client_id_blob_filename):
self.logger.error("no client ID blob available for this device")
return 1
with open(session.device_config.device_client_id_blob_filename, "rb") as f:
try:
cid_bytes = client_id.ParseFromString(base64.b64decode(
"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"))
cid_bytes = client_id.ParseFromString(f.read())
except DecodeError:
self.logger.error("client id failed to parse as protobuf")
return 1
@ -214,36 +218,8 @@ class Cdm:
else:
license_request.Msg.ClientId.CopyFrom(client_id)
kes="""-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAosIRKPWQ6+vptCMzSpZ4b4v5g8kp+qs2BgOepFOJG6Tuj1Ph
MzsK2ykmygErTW2X9PF610gjmCREslYzbcVdJ99+BAI1Yp18fFJCoOqYRUrs0mcb
1GaVuhU+hlml2AE8jOvHl/33fJir4Cws14/y680MGOJYPtHtYnvFrMUmMAReqlLs
HMvyMdGLR3oSOS35+xnh1Kuztrlw/KVlGSANFQ0bYMQEy6C4jC4aFu9IUMj+UPcG
pJ07t8cCPXh7eE1zb3SJuYvFqZKMT7OnethnaiDAE4xqQws5z6Wjrxmgop/Oz8hO
048G/jrNoDnfBK2nsWT+4EbuwFZTWsK28WyEdwIDAQABAoIBACkwyDr/ev/aIrlO
N0rnLe+9ExbBMHiaIAABpoKcCZUPdribV+EpTiQNFB4Hkbf0xoJdIuOdBDUa6K/h
lP5w9pSCwVeuX2hnxvuHrgkflg3jWnAdXDOzCq2fdsV1pr02Aub/PPJAegP0d3sy
ct7TNX1r1WXu0rqDUnqcLHj/JBz+drkyfcLOLbKffd98t1Sxsjy/aFMiUngHk/uj
imNamAMNhmob2xyah8pqg7Y7XFuZn3Wu+i+tL2HoAZRUaRWXiBPV1SST0F/4pQg5
we9xaMfuxuwBIdRPkiiyagK1IWqT2XsVG2byMEvcq3iIVyAS1dzb85tZbdv+ufR4
VoZ70lECgYEA1CvxfdmuqBG45GKQzin5+jnkGLnj6LjEH5EtUQPy2Is3N6WJIp6B
SHxgddoJZh3Pc9D62nKLTrAkkk1UpCrrFXpjy7VkIUBnEzVj0Nbh0xoV9brRPQOD
lqtrfj1NQNNY8ZgWpILnJ9n26Gqjr2nkUlAsu3bPaz/VzffA/waP4pUCgYEAxGEJ
MO99eIpkZdZU7PxjRs8rJmIzx77MekWpUJKtKzDA6BbwWI2oLuG9zbcANMKMdonD
j2ZXdVUQfqBvcwHuDmK+7FhKQ1Rw0jWWlrEADYQgK3MfqMPoOGv9Wn3hrBetSbWK
HTXOQQccDaEzSSCTOG3RPrMi2eIp7uFCENbqM9sCgYB4mdHW+1kv54L1LqGozmtt
NGLXOzK1IfE5EEh1+IydUeS9GLbumrJaBXi/BIS7Ks60wmEUsm9E9xKSpqop9stR
lhQLwrt7uyPb40kteDc8y2MYHmy5BbpSdnXPeADljDzOdujH8jB6koaqbZNFLie+
Mhx7InmcONjLDr0BOTWoUQKBgQCAzkzjBhK8P7m+eijWEG1lgnkBAiSIfYNNJ+f4
a1yeGapOEM2wp6mKppKCHehKstjC33Wf1zbCRPs+syimvLtSQD6OcxKyuu4NUwzk
5k/sjZ80IJzBa04jw+E3u52L7TPCRwrCQgp46Jrj7bnf2zf1KUK353OSih+LCcD1
nqGbRQKBgQDC5ns0X8TnJCgf1BD3cGvc3o9zo3gw/NuZ6cqm8q45u0kiw5pRs+7j
9CXENirhHL5JXighOFB78Q3WWMuppTDxj7S1rpYdgp6+ITGSOmY5Xs6uaimilt2H
JPmXCYQt2Qu51bJ+MqZRWYeyN01O6rdKX/zGD9UTN5D3Ty3KEzogkg==
-----END RSA PRIVATE KEY-----"""
if session.device_config.private_key_available:
key = RSA.importKey(kes)
key = RSA.importKey(open(session.device_config.device_private_key_filename).read())
session.device_key = key
else:
self.logger.error("need device private key, other methods unimplemented")
@ -342,8 +318,7 @@ JPmXCYQt2Qu51bJ+MqZRWYeyN01O6rdKX/zGD9UTN5D3Ty3KEzogkg==
lic_hmac = HMAC.new(session.derived_keys['auth_1'], digestmod=SHA256)
lic_hmac.update(license.Msg.SerializeToString())
self.logger.debug(
"calculated sig: {} actual sig: {}".format(lic_hmac.hexdigest(), binascii.hexlify(license.Signature)))
self.logger.debug("calculated sig: {} actual sig: {}".format(lic_hmac.hexdigest(), binascii.hexlify(license.Signature)))
if lic_hmac.digest() != license.Signature:
self.logger.info("license signature doesn't match - writing bin so they can be debugged")

22
tx.py

@ -215,7 +215,7 @@ class Txckey:
h = (h << 5) - h + ord(c)
return str(h & 0xffffffff)
def ckey81(self, vid, tm, appVer='3.5.57', guid='', platform="10201",
def ckey81(self, vid, tm, appVer='3.5.57', guid='52f0ea142b32b633', platform="10201",
url="https://v.qq.com/x/cover/mzc00200b4jsdq6/l00469csvi7.html"):
url = url[0:48]
navigator = self.userAgent[0:48]
@ -395,13 +395,16 @@ class TX:
"defn": defn,
"ehost": url,
"refer": url,
"platform": "10201",
"guid": self.guid,
"cKey": ckey,
"logintoken": json.dumps(self.logintoken, separators=(',', ':')),
"tm": tm,
"charge": "0",
"otype": "ojson",
"defnpayver": "3",
"spau": "1",
"spaudio": "0",
"spwm": "1",
"sphls": "2",
"host": "v.qq.com",
@ -415,21 +418,18 @@ class TX:
"auth_from": "",
"auth_ext": "",
"fhdswitch": "0",
"dtype": "3",
"spsrt": "2",
"lang_code": "0",
"spvvpay": "1",
"spadseg": "3",
"spav1": "15",
"hevclv": "33",
"spsfrhdr": "0",
"spvideo": "0",
"spm3u8tag": "67",
"spmasterm3u8": "3",
"drm": "40",
"platform": "10201",
"dtype": 3,
"spav1": 15,
"hevclv": 28,
"spsfrhdr": 100,
"spvideo": 1044,
"spaudio": 70,
"defnpayver": 7
"drm": "40"
}
response = self.re.get("https://h5vv6.video.qq.com/getinfo", params=params)
data = response.json()
@ -581,6 +581,6 @@ class TX:
if __name__ == '__main__':
ck = ''
ck = ""
tx = TX(ck)
tx.run()

253
yk.py

@ -23,6 +23,8 @@ class YouKu:
}
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' # 固定值
@ -75,7 +77,49 @@ class YouKu:
def takeOne(self, elem):
return float(elem[0])
def m3u8_url(self, t, params_data, sign):
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 = {
@ -103,9 +147,12 @@ class YouKu:
ret = data["ret"]
video_lists = []
if ret == ["SUCCESS::调用成功"]:
stream = data["data"]["data"]["stream"]
title = data["data"]["data"]["video"]["title"]
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"]
@ -113,9 +160,19 @@ class YouKu:
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 drm_type == "cbcs":
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]:
@ -124,37 +181,26 @@ class YouKu:
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",
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请输入要下载的视频序号:")
ch = input(f"{tb}\n请输入要下载的视频序号,输入0尝试自动选择最高清晰度视频:")
if ch == "0":
self.save_m3u82(video_lists)
return 0
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("下载链接已生成")
video = video_lists[int(i) - 1]
self.save_m3u8(video)
elif ret == ["FAIL_SYS_ILLEGAL_ACCESS::非法请求"]:
print("请求参数错误")
elif ret == ["FAIL_SYS_TOKEN_EXOIRED::令牌过期"]:
@ -165,10 +211,13 @@ class YouKu:
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 = {
@ -193,6 +242,90 @@ class YouKu:
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()
@ -221,10 +354,12 @@ class YouKu:
"encryptR_client": self.R,
"skh": 1,
"last_clarity": 5,
"clarity_chg_ts": 1689341442
"clarity_chg_ts": 1689341442,
"needad": 0,
}
ad_params = {
"vip": 1,
"needad":0,
}
params_data = {
"steal_params": json.dumps(steal_params),
@ -233,19 +368,61 @@ class YouKu:
}
params_data = json.dumps(params_data)
sign = self.youku_sign(t, params_data, user_info["token"])
return self.m3u8_url(t, params_data, sign)
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)
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 =""
cookie = ''
youku = YouKu(cookie)
youku.start()
youku.start("XNjE0OTU0NDU0NA==")

Loading…
Cancel
Save