mirror of
https://github.com/soimort/you-get.git
synced 2025-02-12 04:55:21 +03:00
终于解决了腾讯视频,不仅不会慢,也解决了之前只能下载分段的问题,还可以用mpv merge-files播放视频
This commit is contained in:
parent
192eb82dcf
commit
f2532b1a94
@ -1,73 +1,203 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
__all__ = ['qq_download']
|
__all__ = ['qq_download']
|
||||||
|
|
||||||
from ..common import *
|
from ..common import *
|
||||||
from .qie import download as qieDownload
|
from .qie import download as qieDownload, VideoExtractor
|
||||||
from urllib.parse import urlparse,parse_qs
|
from urllib.parse import urlparse,parse_qs
|
||||||
|
import struct
|
||||||
|
import base64
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
|
||||||
|
DELTA = 0x9e3779b9
|
||||||
|
ROUNDS = 16
|
||||||
|
|
||||||
|
SALT_LEN = 2
|
||||||
|
ZERO_LEN = 7
|
||||||
|
|
||||||
|
SEED = 0xdead
|
||||||
|
|
||||||
|
def rand():
|
||||||
|
global SEED
|
||||||
|
if SEED == 0:
|
||||||
|
SEED = 123459876
|
||||||
|
k1 = 0xffffffff & (-2836 * (SEED // 127773))
|
||||||
|
k2 = 0xffffffff & (16807 * (SEED % 127773))
|
||||||
|
SEED = 0xffffffff & (k1 + k2)
|
||||||
|
if SEED < 0:
|
||||||
|
SEED = SEED + 2147483647
|
||||||
|
return SEED
|
||||||
|
|
||||||
|
def pack(data):
|
||||||
|
target = []
|
||||||
|
for i in data:
|
||||||
|
target.extend(struct.pack('>I', i))
|
||||||
|
return target
|
||||||
|
|
||||||
|
def unpack(data):
|
||||||
|
data = bytes(data)
|
||||||
|
target = []
|
||||||
|
for i in range(0, len(data), 4):
|
||||||
|
target.extend(struct.unpack('>I', data[i:i+4]))
|
||||||
|
return target
|
||||||
|
|
||||||
|
def tea_encrypt(v, key):
|
||||||
|
s = 0
|
||||||
|
key = unpack(key)
|
||||||
|
v = unpack(v)
|
||||||
|
for i in range(ROUNDS):
|
||||||
|
s += DELTA
|
||||||
|
s &= 0xffffffff
|
||||||
|
v[0] += (v[1]+s) ^ ((v[1]>>5)+key[1]) ^ ((v[1]<<4)+key[0])
|
||||||
|
v[0] &= 0xffffffff
|
||||||
|
v[1] += (v[0]+s) ^ ((v[0]>>5)+key[3]) ^ ((v[0]<<4)+key[2])
|
||||||
|
v[1] &= 0xffffffff
|
||||||
|
return pack(v)
|
||||||
|
|
||||||
|
def oi_symmetry_encrypt2(raw_data, key):
|
||||||
|
pad_salt_body_zero_len = 1 + SALT_LEN + len(raw_data) + ZERO_LEN
|
||||||
|
pad_len = pad_salt_body_zero_len % 8
|
||||||
|
if pad_len:
|
||||||
|
pad_len = 8 - pad_len
|
||||||
|
data = []
|
||||||
|
data.append(rand() & 0xf8 | pad_len)
|
||||||
|
while pad_len + SALT_LEN:
|
||||||
|
data.append(rand() & 0xff)
|
||||||
|
pad_len = pad_len - 1
|
||||||
|
data.extend(raw_data)
|
||||||
|
data.extend([0x00] * ZERO_LEN)
|
||||||
|
|
||||||
|
temp = [0x00] * 8
|
||||||
|
enc = tea_encrypt(data[:8], key)
|
||||||
|
for i in range(8, len(data), 8):
|
||||||
|
d1 = data[i:]
|
||||||
|
for j in range(8):
|
||||||
|
d1[j] = d1[j] ^ enc[i-8+j]
|
||||||
|
d1 = tea_encrypt(d1, key)
|
||||||
|
for j in range(8):
|
||||||
|
d1[j] = d1[j] ^ data[i-8+j] ^ temp[j]
|
||||||
|
enc.append(d1[j])
|
||||||
|
temp[j] = enc[i-8+j]
|
||||||
|
return enc
|
||||||
|
|
||||||
|
|
||||||
|
KEY = [
|
||||||
|
0xfa, 0x82, 0xde, 0xb5, 0x2d, 0x4b, 0xba, 0x31,
|
||||||
|
0x39, 0x6, 0x33, 0xee, 0xfb, 0xbf, 0xf3, 0xb6
|
||||||
|
]
|
||||||
|
|
||||||
|
def packstr(data):
|
||||||
|
l = len(data)
|
||||||
|
t = []
|
||||||
|
t.append((l&0xFF00) >> 8)
|
||||||
|
t.append(l&0xFF)
|
||||||
|
t.extend([ord(c) for c in data])
|
||||||
|
return t
|
||||||
|
|
||||||
|
def strsum(data):
|
||||||
|
s = 0
|
||||||
|
for c in data:
|
||||||
|
s = s*131 + c
|
||||||
|
return 0x7fffffff & s
|
||||||
|
|
||||||
|
def echo_ckeyv3(vid, guid='', t=None, player_version='3.2.38.401', platform=10902, stdfrom='bcng'):
|
||||||
|
data = []
|
||||||
|
data.extend(pack([21507, 3168485562]))
|
||||||
|
data.extend(pack([platform]))
|
||||||
|
|
||||||
|
if not t:
|
||||||
|
t = time.time()
|
||||||
|
seconds = int(t)
|
||||||
|
microseconds = int(1000000*(t - int(t)))
|
||||||
|
data.extend(pack([microseconds, seconds]))
|
||||||
|
data.extend(packstr(stdfrom))
|
||||||
|
|
||||||
|
r = random.random()
|
||||||
|
data.extend(packstr('%.16f' % r))
|
||||||
|
data.extend(packstr(player_version))
|
||||||
|
data.extend(packstr(vid))
|
||||||
|
data.extend(packstr('2%s' % guid))
|
||||||
|
data.extend(packstr('4null'))
|
||||||
|
data.extend(packstr('4null'))
|
||||||
|
data.extend([0x00, 0x00, 0x00, 0x01])
|
||||||
|
data.extend([0x00, 0x00, 0x00, 0x00])
|
||||||
|
|
||||||
|
l = len(data)
|
||||||
|
data.insert(0, l&0xFF)
|
||||||
|
data.insert(0, (l&0xFF00) >> 8)
|
||||||
|
|
||||||
|
enc = oi_symmetry_encrypt2(data, KEY)
|
||||||
|
|
||||||
|
pad = [0x00, 0x00, 0x00, 0x00, 0xff&rand(), 0xff&rand(), 0xff&rand(), 0xff&rand()]
|
||||||
|
pad[0] = pad[4] ^ 71 & 0xFF
|
||||||
|
pad[1] = pad[5] ^ -121 & 0xFF
|
||||||
|
pad[2] = pad[6] ^ -84 & 0xFF
|
||||||
|
pad[3] = pad[7] ^ -86 & 0xFF
|
||||||
|
|
||||||
|
pad.extend(enc)
|
||||||
|
pad.extend(pack([strsum(data)]))
|
||||||
|
|
||||||
|
result = base64.b64encode(bytes(pad), b'_-').decode('utf-8').replace('=', '')
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class QQ(VideoExtractor):
|
||||||
|
name = "腾讯视频(qq)"
|
||||||
|
|
||||||
|
stream_types = [
|
||||||
|
{'id': 'fhd'},
|
||||||
|
{'id': 'shd'},
|
||||||
|
{'id': 'hd'},
|
||||||
|
{'id': 'sd'},
|
||||||
|
]
|
||||||
|
|
||||||
|
def _getvkey(self, vid, format, idx):
|
||||||
|
import uuid
|
||||||
|
appver = '3.2.38.401'
|
||||||
|
guid = uuid.uuid4().hex
|
||||||
|
platform = 11
|
||||||
|
cKey = echo_ckeyv3(vid=vid, guid=guid, player_version=appver, platform=platform)
|
||||||
|
key_api = 'http://vv.video.qq.com/getvkey?vid={vid}&appver={appver}&platform={platform}&otype=json&filename={vid}.p{format1000}.{idx}.mp4&format={format}&cKey={cKey}&guid={guid}&charge=0&encryptVer=5.4&lnk={vid}'.format(vid=vid, appver=appver, format1000=format%1000, format=format, cKey=cKey, guid=guid, platform=platform, idx=idx)
|
||||||
|
part_info = get_html(key_api)
|
||||||
|
key_json = json.loads(match1(part_info, r'QZOutputJson=(.*)')[:-1])
|
||||||
|
return key_json['key']
|
||||||
|
|
||||||
|
def prepare(self, **kwargs):
|
||||||
|
# vid = kwargs['vid'] if 'vid' in kwargs else None
|
||||||
|
# vid = vid if vid is not None else self.vid
|
||||||
|
vid = ('vid' in kwargs and kwargs['vid']) or self.vid
|
||||||
|
if vid is None:
|
||||||
|
return
|
||||||
|
|
||||||
def qq_download_by_vid(vid, title, output_dir='.', merge=True, info_only=False):
|
|
||||||
info_api = 'http://vv.video.qq.com/getinfo?otype=json&appver=3%2E2%2E19%2E333&platform=11&defnpayver=1&vid=' + vid
|
info_api = 'http://vv.video.qq.com/getinfo?otype=json&appver=3%2E2%2E19%2E333&platform=11&defnpayver=1&vid=' + vid
|
||||||
info = get_html(info_api)
|
info = get_html(info_api)
|
||||||
video_json = json.loads(match1(info, r'QZOutputJson=(.*)')[:-1])
|
video_json = json.loads(match1(info, r'QZOutputJson=(.*)')[:-1])
|
||||||
parts_vid = video_json['vl']['vi'][0]['vid']
|
vi0 = video_json['vl']['vi'][0]
|
||||||
parts_ti = video_json['vl']['vi'][0]['ti']
|
self.title = vi0['ti']
|
||||||
parts_prefix = video_json['vl']['vi'][0]['ul']['ui'][0]['url']
|
url_prefix = vi0['ul']['ui'][0]['url']
|
||||||
parts_formats = video_json['fl']['fi']
|
url_prefix = urlparse(url_prefix)._replace(netloc='lmbsy.qq.com').geturl() # fast
|
||||||
# find best quality
|
# url_prefix = urlparse(url_prefix)._replace(netloc='ugcbsy.qq.com').geturl() # fast
|
||||||
# only looking for fhd(1080p) and shd(720p) here.
|
# url_prefix = urlparse(url_prefix)._replace(netloc='vmind.qqvideo.tc.qq.com').geturl() # fast
|
||||||
# 480p usually come with a single file, will be downloaded as fallback.
|
fi = video_json['fl']['fi'] # streams
|
||||||
best_quality = ''
|
self.streams = {}
|
||||||
for part_format in parts_formats:
|
for f in fi:
|
||||||
if part_format['name'] == 'fhd':
|
urls = ['{prefix}/{vid}.p{format1000}.{idx}.mp4?vkey={vkey}'.format(prefix=url_prefix, vid=vid, format1000=f['id']%1000, idx=idx, vkey=self._getvkey(vid, f['id'], idx)) for idx in range(1, vi0['cl']['fc']+1)]
|
||||||
best_quality = 'fhd'
|
|
||||||
break
|
|
||||||
|
|
||||||
if part_format['name'] == 'shd':
|
# print(vi0['ul']['ui'])
|
||||||
best_quality = 'shd'
|
# vkey = self._getvkey(vid, f['id'], 1)
|
||||||
|
# for ui in vi0['ul']['ui']:
|
||||||
|
# test = '{prefix}/{vid}.p{format1000}.1.mp4?vkey={vkey}'.format(prefix=ui['url'], vid=vid, format1000=f['id']%1000, vkey=vkey)
|
||||||
|
# print(test)
|
||||||
|
|
||||||
for part_format in parts_formats:
|
self.streams[f['name']] = {
|
||||||
if (not best_quality == '') and (not part_format['name'] == best_quality):
|
'video_profile': f['cname'],
|
||||||
continue
|
'size': f['fs'],
|
||||||
part_format_id = part_format['id']
|
'container': 'mp4',
|
||||||
part_format_sl = part_format['sl']
|
'src': urls,
|
||||||
if part_format_sl == 0:
|
}
|
||||||
part_urls= []
|
|
||||||
total_size = 0
|
|
||||||
try:
|
|
||||||
# For fhd(1080p), every part is about 100M and 6 minutes
|
|
||||||
# try 100 parts here limited download longest single video of 10 hours.
|
|
||||||
for part in range(1,100):
|
|
||||||
filename = vid + '.p' + str(part_format_id % 1000) + '.' + str(part) + '.mp4'
|
|
||||||
key_api = "http://vv.video.qq.com/getkey?otype=json&platform=11&format=%s&vid=%s&filename=%s" % (part_format_id, parts_vid, filename)
|
|
||||||
#print(filename)
|
|
||||||
#print(key_api)
|
|
||||||
part_info = get_html(key_api)
|
|
||||||
key_json = json.loads(match1(part_info, r'QZOutputJson=(.*)')[:-1])
|
|
||||||
#print(key_json)
|
|
||||||
vkey = key_json['key']
|
|
||||||
url = '%s/%s?vkey=%s' % (parts_prefix, filename, vkey)
|
|
||||||
part_urls.append(url)
|
|
||||||
_, ext, size = url_info(url, faker=True)
|
|
||||||
total_size += size
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
print_info(site_info, parts_ti, ext, total_size)
|
|
||||||
if not info_only:
|
|
||||||
download_urls(part_urls, parts_ti, ext, total_size, output_dir=output_dir, merge=merge)
|
|
||||||
else:
|
|
||||||
fvkey = video_json['vl']['vi'][0]['fvkey']
|
|
||||||
mp4 = video_json['vl']['vi'][0]['cl'].get('ci', None)
|
|
||||||
if mp4:
|
|
||||||
mp4 = mp4[0]['keyid'].replace('.10', '.p') + '.mp4'
|
|
||||||
else:
|
|
||||||
mp4 = video_json['vl']['vi'][0]['fn']
|
|
||||||
url = '%s/%s?vkey=%s' % ( parts_prefix, mp4, fvkey )
|
|
||||||
_, ext, size = url_info(url, faker=True)
|
|
||||||
|
|
||||||
print_info(site_info, title, ext, size)
|
|
||||||
if not info_only:
|
site = QQ()
|
||||||
download_urls([url], title, ext, size, output_dir=output_dir, merge=merge)
|
|
||||||
|
|
||||||
|
|
||||||
def qq_download(url, output_dir='.', merge=True, info_only=False, **kwargs):
|
def qq_download(url, output_dir='.', merge=True, info_only=False, **kwargs):
|
||||||
@ -80,7 +210,7 @@ def qq_download(url, output_dir='.', merge=True, info_only=False, **kwargs):
|
|||||||
content = get_html(url)
|
content = get_html(url)
|
||||||
vids = matchall(content, [r'\bvid=(\w+)'])
|
vids = matchall(content, [r'\bvid=(\w+)'])
|
||||||
for vid in vids:
|
for vid in vids:
|
||||||
qq_download_by_vid(vid, vid, output_dir, merge, info_only)
|
site.download_by_vid(vid=vid, output_dir=output_dir, merge=merge, info_only=info_only, **kwargs)
|
||||||
return
|
return
|
||||||
|
|
||||||
#do redirect
|
#do redirect
|
||||||
@ -108,8 +238,9 @@ def qq_download(url, output_dir='.', merge=True, info_only=False, **kwargs):
|
|||||||
title = match1(content, r'"title":"([^"]+)"') if not title else title
|
title = match1(content, r'"title":"([^"]+)"') if not title else title
|
||||||
title = vid if not title else title #general fallback
|
title = vid if not title else title #general fallback
|
||||||
|
|
||||||
qq_download_by_vid(vid, title, output_dir, merge, info_only)
|
site.download_by_vid(vid=vid, output_dir=output_dir, merge=merge, info_only=info_only, **kwargs)
|
||||||
|
|
||||||
|
qq_download_by_vid=site.download_by_vid
|
||||||
site_info = "QQ.com"
|
site_info = "QQ.com"
|
||||||
download = qq_download
|
download = qq_download
|
||||||
download_playlist = playlist_not_supported('qq')
|
download_playlist = playlist_not_supported('qq')
|
||||||
|
Loading…
Reference in New Issue
Block a user