Compare commits

..

9 Commits
main ... old

Author SHA1 Message Date
Tontonnow ef4fb03931
Add files via upload 11 months ago
Tontonnow 93e9105eb2
Add files via upload 11 months ago
Tontonnow d582ce9e02
Add files via upload 11 months ago
Tontonnow 688d577d83
Create README.md 11 months ago
Tontonnow 640ab216ce
增加爱奇艺4k 11 months ago
Tontonnow 581b753984
增加杜比视界 11 months ago
Tontonnow e26bc70c5b
Add files via upload 11 months ago
Tontonnow 210df982c5
Add files via upload 11 months ago
Tontonnow 4ab9965c21
Add files via upload 11 months ago
  1. 32
      README.md
  2. 2
      iqy.py
  3. 10
      main.py
  4. 53
      pywidevine/L3/cdm/cdm.py
  5. 22
      tx.py
  6. 253
      yk.py

@ -6,12 +6,18 @@
1.爱奇艺普通视频以及wvdrm内容
2.腾讯普通视频(不再支持)
2.腾讯普通视频
3.优酷普通视频,wv加密内容以及自研加密内容
#### 暂不支持
腾讯wv(我网页都播放不了),chacha20(超前买不起)
### 使用说明
自行编译,下载相关依赖
只提供win10文件,其余系统自行编译,下载相关依赖
本软件支持解析,不支持破解
1.下载链接中全部文件,放到同一个文件夹
@ -51,25 +57,3 @@ 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=13&ut=4&ut=5&ut=14&ut=57"
dash = dash[:-1] + "&bop=" + parse.quote(self.bop) + "&ut=14"
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,7 +47,9 @@ if __name__ == '__main__':
print(e)
while True:
url = input("请输入视频链接:")
if "iqiyi.com" in url:
if "v.qq.com" in url:
tx.run(url)
elif "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 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 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 Cryptodome.Random import get_random_bytes
from Cryptodome.Random import random
from Cryptodome.Cipher import PKCS1_OAEP, AES
@ -19,12 +19,13 @@ from Cryptodome.Signature import pss
from Cryptodome.Util import Padding
import logging
class Cdm:
def __init__(self):
self.logger = logging.getLogger(__name__)
self.sessions = {}
def open_session(self, init_data_b64, device, raw_init_data = None, offline=False):
def open_session(self, init_data_b64, device, raw_init_data=None, offline=False):
self.logger.debug("open_session(init_data_b64={}, device={}".format(init_data_b64, device))
self.logger.info("opening new cdm session")
if device.session_id_type == 'android':
@ -145,14 +146,9 @@ 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(f.read())
cid_bytes = client_id.ParseFromString(base64.b64decode(
"CAES6wkKrgIIAhIQB/4kJvq2K3B8G1zrpJL8ERig5cfsBSKOAjCCAQoCggEBAKLCESj1kOvr6bQjM0qWeG+L+YPJKfqrNgYDnqRTiRuk7o9T4TM7CtspJsoBK01tl/TxetdII5gkRLJWM23FXSfffgQCNWKdfHxSQqDqmEVK7NJnG9RmlboVPoZZpdgBPIzrx5f993yYq+AsLNeP8uvNDBjiWD7R7WJ7xazFJjAEXqpS7BzL8jHRi0d6Ejkt+fsZ4dSrs7a5cPylZRkgDRUNG2DEBMuguIwuGhbvSFDI/lD3BqSdO7fHAj14e3hNc290ibmLxamSjE+zp3rYZ2ogwBOMakMLOc+lo68ZoKKfzs/ITtOPBv46zaA53wStp7Fk/uBG7sBWU1rCtvFshHcCAwEAASjzOBKAAjQe71JDWSetDJFDUJVQkFsfwZJesASZJ8yJUNdC3kgwSzKzFBDPzHxZ8PFTqx2xnfVUnl6KFfkAeQShHwkjLDoefbwmthwtQnPOJIW6I3HCA1rCxH6LiP5762LuTsqmt9mR+ULnvY0onkGFzG0NsGmSz+FzKv2P01Zizf4kJLKj7T9ZqHbjycZq6oOZr/4Y2Ess/erCn+jo9SCdBR7o6Y2JDh6XfwuqUH8weSbJzy4ytlXJ+KAZHL441sjwPuoZC1aQT3deq6VY5BikH1DB2hlou0oZTerOwY3A2IQZiTM4sAcTDzkttZxqyUTYv+cgMjSTeQ4KrGieZKrZhuu3534atAUKrgIIARIQxS0LhJyzfbS0sGrz6mt3xxjJ3YjFBSKOAjCCAQoCggEBAL91mBQAmYbp2Y3h+UUPMqeNd54JmmfbBK5/HQtYwRkUfOv5guK6EQBVzmptU6ST3WQha1A7SohSjrd1juFASN8BVxdjCgKLPUnDAT+wpaFfX0FkKSvObQg+Q62uHBn2tcS2TyhhSxCy3kBSTDy17x4cYl9A/5muarGhdQ+s3J9DiIDvnKUjB0ORH5zos/G66SXdZiDQryi1ToUkAFblMzuRtAybZ2YowUJ68zDy6stxtzgs+KzjW5fMq4X/lDLvf4rugEOuUQaL+BgrD9noLiMypiuMbp+ozkJ/omItZivyPhLUs1OfLdr0WZXJTtoW1hW2sEJc4kDo98TC+fVHEKcCAwEAASjzOBKAA1aVnOzS5La/KOzAGMIJFnrAGetNg3qascdFHhcgnn1WnDQqGNIQlDh4RfyRAjVqZRT7dT7TyDGaw7gpxYso14GZ3z4J7lSotHG+o0UrnMeSuSUMANSSfQT5Qm9PNtRvkRLjuSJa4VzToBeslRoicv5BEiBiHtz/xk+JFHfnEH2z6FvYAzpifC5UR0H4Qf8dJkUlJf+wghGW50DZywj1f5TwSvz+JSde5J7UMG2gooZXuaAcO8Yj3FjMgFrRNaFL9mPUIbiIG2AME8l4AF58s5SuxkDphqP6xtvjLz6z/pq9wpyn+sFl8ixv7fg0tonXzDhnKj26zvEyLlV2WzCk2n70K6+NfEEBwQhdQ2ThnKclYLGwFbNkyRL1VetHNqn1GAoqNlw6AwScL+g2mz2U5kZp7k1BYvJolvrmqu9t6KgxLwYSB0gjyqKOnyaWXL1AeohQEEDf4Py/wsddMEYcjNmRKoxtgFHPoIY80U9ZutLRuczORcwdT9faP3CRCHLc3hoWCgxjb21wYW55X25hbWUSBlhpYW9taRoSCgptb2RlbF9uYW1lEgRNSSA4Gh4KEWFyY2hpdGVjdHVyZV9uYW1lEglhcm02NC12OGEaFQoLZGV2aWNlX25hbWUSBmRpcHBlchoWCgxwcm9kdWN0X25hbWUSBmRpcHBlchpNCgpidWlsZF9pbmZvEj9YaWFvbWkvZGlwcGVyL2RpcHBlcjo5L1BLUTEuMTgwNzI5LjAwMS85LjUuMTc6dXNlci9yZWxlYXNlLWtleXMaHgoUd2lkZXZpbmVfY2RtX3ZlcnNpb24SBjE0LjAuMBokCh9vZW1fY3J5cHRvX3NlY3VyaXR5X3BhdGNoX2xldmVsEgEwMg4QASAAKA0wAEAASABQAA=="))
except DecodeError:
self.logger.error("client id failed to parse as protobuf")
return 1
@ -175,7 +171,7 @@ class Cdm:
license_request.Msg.RequestTime = int(time.time())
license_request.Msg.ProtocolVersion = wv_proto2.ProtocolVersion.Value('CURRENT')
if session.device_config.send_key_control_nonce:
license_request.Msg.KeyControlNonce = random.randrange(1, 2**31)
license_request.Msg.KeyControlNonce = random.randrange(1, 2 ** 31)
if session.privacy_mode:
if session.device_config.vmp:
@ -218,8 +214,36 @@ 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(open(session.device_config.device_private_key_filename).read())
key = RSA.importKey(kes)
session.device_key = key
else:
self.logger.error("need device private key, other methods unimplemented")
@ -318,7 +342,8 @@ class Cdm:
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='52f0ea142b32b633', platform="10201",
def ckey81(self, vid, tm, appVer='3.5.57', guid='', platform="10201",
url="https://v.qq.com/x/cover/mzc00200b4jsdq6/l00469csvi7.html"):
url = url[0:48]
navigator = self.userAgent[0:48]
@ -395,16 +395,13 @@ 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",
@ -418,18 +415,21 @@ 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"
"drm": "40",
"platform": "10201",
"dtype": 3,
"spav1": 15,
"hevclv": 28,
"spsfrhdr": 100,
"spvideo": 1044,
"spaudio": 70,
"defnpayver": 7
}
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,8 +23,6 @@ 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' # 固定值
@ -77,49 +75,7 @@ class YouKu:
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):
def m3u8_url(self, t, params_data, sign):
url = "https://acs.youku.com/h5/mtop.youku.play.ups.appinfo.get/1.1/"
params = {
@ -147,12 +103,9 @@ class YouKu:
ret = data["ret"]
video_lists = []
if ret == ["SUCCESS::调用成功"]:
stream = data["data"]["data"].get("stream", [])
title = data["data"]["data"].get("video", {}).get("title", "")
stream = data["data"]["data"]["stream"]
title = data["data"]["data"]["video"]["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"]
@ -160,19 +113,9 @@ 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 audio_lang not in keys.keys():
if drm_type == "cbcs":
elif drm_type == "cbcs":
license_url = video["stream_ext"]["uri"]
key = self.get_cbcs_key(license_url, m3u8_url)
if key[0]:
@ -181,26 +124,37 @@ class YouKu:
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",
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请输入要下载的视频序号,输入0尝试自动选择最高清晰度视频:")
if ch == "0":
self.save_m3u82(video_lists)
return 0
ch = input(f"{tb}\n请输入要下载的视频序号:")
ch = ch.split(",")
for i in ch:
video = video_lists[int(i) - 1]
self.save_m3u8(video)
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::令牌过期"]:
@ -211,13 +165,10 @@ 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 = {
@ -242,90 +193,6 @@ 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()
@ -354,12 +221,10 @@ class YouKu:
"encryptR_client": self.R,
"skh": 1,
"last_clarity": 5,
"clarity_chg_ts": 1689341442,
"needad": 0,
"clarity_chg_ts": 1689341442
}
ad_params = {
"vip": 1,
"needad":0,
}
params_data = {
"steal_params": json.dumps(steal_params),
@ -368,61 +233,19 @@ 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, page_info["vid"])
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) if url.startswith("https://") else f"https://v.youku.com/v_show/id_{url}.html"
url = self.redirect(url)
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("XNjE0OTU0NDU0NA==")
youku.start()

Loading…
Cancel
Save