musicdl 2.1.11__py3-none-any.whl → 2.7.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- musicdl/__init__.py +5 -5
- musicdl/modules/__init__.py +10 -3
- musicdl/modules/common/__init__.py +2 -0
- musicdl/modules/common/gdstudio.py +204 -0
- musicdl/modules/js/__init__.py +1 -0
- musicdl/modules/js/youtube/__init__.py +2 -0
- musicdl/modules/js/youtube/botguard.js +1 -0
- musicdl/modules/js/youtube/jsinterp.py +902 -0
- musicdl/modules/js/youtube/runner.js +2 -0
- musicdl/modules/sources/__init__.py +41 -10
- musicdl/modules/sources/apple.py +207 -0
- musicdl/modules/sources/base.py +256 -28
- musicdl/modules/sources/bilibili.py +118 -0
- musicdl/modules/sources/buguyy.py +148 -0
- musicdl/modules/sources/fangpi.py +153 -0
- musicdl/modules/sources/fivesing.py +108 -0
- musicdl/modules/sources/gequbao.py +148 -0
- musicdl/modules/sources/jamendo.py +108 -0
- musicdl/modules/sources/joox.py +104 -68
- musicdl/modules/sources/kugou.py +129 -76
- musicdl/modules/sources/kuwo.py +188 -68
- musicdl/modules/sources/lizhi.py +107 -0
- musicdl/modules/sources/migu.py +172 -66
- musicdl/modules/sources/mitu.py +140 -0
- musicdl/modules/sources/mp3juice.py +264 -0
- musicdl/modules/sources/netease.py +163 -115
- musicdl/modules/sources/qianqian.py +125 -77
- musicdl/modules/sources/qq.py +232 -94
- musicdl/modules/sources/tidal.py +342 -0
- musicdl/modules/sources/ximalaya.py +256 -0
- musicdl/modules/sources/yinyuedao.py +144 -0
- musicdl/modules/sources/youtube.py +238 -0
- musicdl/modules/utils/__init__.py +12 -4
- musicdl/modules/utils/appleutils.py +563 -0
- musicdl/modules/utils/data.py +107 -0
- musicdl/modules/utils/logger.py +211 -58
- musicdl/modules/utils/lyric.py +73 -0
- musicdl/modules/utils/misc.py +335 -23
- musicdl/modules/utils/modulebuilder.py +75 -0
- musicdl/modules/utils/neteaseutils.py +81 -0
- musicdl/modules/utils/qqutils.py +184 -0
- musicdl/modules/utils/quarkparser.py +105 -0
- musicdl/modules/utils/songinfoutils.py +54 -0
- musicdl/modules/utils/tidalutils.py +738 -0
- musicdl/modules/utils/youtubeutils.py +3606 -0
- musicdl/musicdl.py +184 -86
- musicdl-2.7.3.dist-info/LICENSE +203 -0
- musicdl-2.7.3.dist-info/METADATA +704 -0
- musicdl-2.7.3.dist-info/RECORD +53 -0
- {musicdl-2.1.11.dist-info → musicdl-2.7.3.dist-info}/WHEEL +5 -5
- musicdl-2.7.3.dist-info/entry_points.txt +2 -0
- musicdl/modules/sources/baiduFlac.py +0 -69
- musicdl/modules/sources/xiami.py +0 -104
- musicdl/modules/utils/downloader.py +0 -80
- musicdl-2.1.11.dist-info/LICENSE +0 -22
- musicdl-2.1.11.dist-info/METADATA +0 -82
- musicdl-2.1.11.dist-info/RECORD +0 -24
- {musicdl-2.1.11.dist-info → musicdl-2.7.3.dist-info}/top_level.txt +0 -0
- {musicdl-2.1.11.dist-info → musicdl-2.7.3.dist-info}/zip-safe +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Function:
|
|
3
|
+
Implementation of QQMusicClient utils
|
|
4
|
+
Author:
|
|
5
|
+
Zhenchao Jin
|
|
6
|
+
WeChat Official Account (微信公众号):
|
|
7
|
+
Charles的皮卡丘
|
|
8
|
+
'''
|
|
9
|
+
import time
|
|
10
|
+
import orjson
|
|
11
|
+
import base64
|
|
12
|
+
import random
|
|
13
|
+
import string
|
|
14
|
+
import hashlib
|
|
15
|
+
import requests
|
|
16
|
+
import binascii
|
|
17
|
+
from uuid import uuid4
|
|
18
|
+
from typing import ClassVar, cast
|
|
19
|
+
from datetime import datetime, timedelta
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from cryptography.hazmat.primitives import serialization
|
|
22
|
+
from cryptography.hazmat.primitives.asymmetric import padding
|
|
23
|
+
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
|
24
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
'''constants'''
|
|
28
|
+
PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
|
|
29
|
+
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEIxgwoutfwoJxcGQeedgP7FG9qaIuS0qzfR8gWkrkTZKM2iWHn2ajQpBRZjMSoSf6+KJGvar2ORhBfpDXyVtZCKpqLQ+FLkpncClKVIrBwv6PHyUvuCb0rIarmgDnzkfQAqVufEtR64iazGDKatvJ9y6B9NMbHddGSAUmRTCrHQIDAQAB
|
|
30
|
+
-----END PUBLIC KEY-----"""
|
|
31
|
+
SECRET = "ZdJqM15EeO2zWc08"
|
|
32
|
+
APP_KEY = "0AND0HD6FE4HY80F"
|
|
33
|
+
DEFAULT_VIP_QUALITIES = {
|
|
34
|
+
"MASTER": ("AIM0", ".mflac"), "ATMOS_2": ("Q0M0", ".mflac"), "ATMOS_51": ("Q0M1", ".mflac"), "FLAC": ("F0M0", ".mflac"),
|
|
35
|
+
"OGG_640": ("O801", ".mgg"), "OGG_320": ("O800", ".mgg"), "OGG_192": ("O6M0", ".mgg"), "OGG_96": ("O4M0", ".mgg"),
|
|
36
|
+
}
|
|
37
|
+
DEFAULT_QUALITIES = {
|
|
38
|
+
"MASTER": ("AI00", ".flac"), "ATMOS_2": ("Q000", ".flac"), "ATMOS_51": ("Q001", ".flac"), "FLAC": ("F000", ".flac"),
|
|
39
|
+
"OGG_640": ("O801", ".ogg"), "OGG_320": ("O800", ".ogg"), "OGG_192": ("O600", ".ogg"), "OGG_96": ("O400", ".ogg"),
|
|
40
|
+
"MP3_320": ("M800", ".mp3"), "MP3_128": ("M500", ".mp3"), "ACC_192": ("C600", ".m4a"), "ACC_96": ("C400", ".m4a"),
|
|
41
|
+
"ACC_48": ("C200", ".m4a"),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
'''QQMusicClientUtils'''
|
|
46
|
+
class QQMusicClientUtils(object):
|
|
47
|
+
'''randomimei'''
|
|
48
|
+
@staticmethod
|
|
49
|
+
def randomimei():
|
|
50
|
+
imei, sum_ = [], 0
|
|
51
|
+
for i in range(14):
|
|
52
|
+
num = random.randint(0, 9)
|
|
53
|
+
if (i + 2) % 2 == 0:
|
|
54
|
+
num *= 2
|
|
55
|
+
if num >= 10: num = (num % 10) + 1
|
|
56
|
+
sum_ += num
|
|
57
|
+
imei.append(str(num))
|
|
58
|
+
ctrl_digit = (sum_ * 9) % 10
|
|
59
|
+
imei.append(str(ctrl_digit))
|
|
60
|
+
return "".join(imei)
|
|
61
|
+
'''rsaencrypt'''
|
|
62
|
+
@staticmethod
|
|
63
|
+
def rsaencrypt(content: bytes):
|
|
64
|
+
key = cast(RSAPublicKey, serialization.load_pem_public_key(PUBLIC_KEY.encode()))
|
|
65
|
+
return key.encrypt(content, padding.PKCS1v15())
|
|
66
|
+
'''aesencrypt'''
|
|
67
|
+
@staticmethod
|
|
68
|
+
def aesencrypt(key: bytes, content: bytes):
|
|
69
|
+
cipher = Cipher(algorithms.AES(key), modes.CBC(key))
|
|
70
|
+
padding_size = 16 - len(content) % 16
|
|
71
|
+
encryptor = cipher.encryptor()
|
|
72
|
+
return encryptor.update(content + (padding_size * chr(padding_size)).encode()) + encryptor.finalize()
|
|
73
|
+
'''calcmd5'''
|
|
74
|
+
@staticmethod
|
|
75
|
+
def calcmd5(*strings: str | bytes):
|
|
76
|
+
md5 = hashlib.md5()
|
|
77
|
+
for item in strings:
|
|
78
|
+
assert isinstance(item, (str, bytes))
|
|
79
|
+
if isinstance(item, bytes): md5.update(item)
|
|
80
|
+
elif isinstance(item, str): md5.update(item.encode())
|
|
81
|
+
return md5.hexdigest()
|
|
82
|
+
'''randombeaconid'''
|
|
83
|
+
@staticmethod
|
|
84
|
+
def randombeaconid():
|
|
85
|
+
beacon_id = ""
|
|
86
|
+
time_month = datetime.now().strftime("%Y-%m-") + "01"
|
|
87
|
+
rand1 = random.randint(100000, 999999)
|
|
88
|
+
rand2 = random.randint(100000000, 999999999)
|
|
89
|
+
for i in range(1, 41):
|
|
90
|
+
if i in [1, 2, 13, 14, 17, 18, 21, 22, 25, 26, 29, 30, 33, 34, 37, 38]:
|
|
91
|
+
beacon_id += f"k{i}:{time_month}{rand1}.{rand2}"
|
|
92
|
+
elif i == 3:
|
|
93
|
+
beacon_id += "k3:0000000000000000"
|
|
94
|
+
elif i == 4:
|
|
95
|
+
beacon_id += f"k4:{''.join(random.choices('123456789abcdef', k=16))}"
|
|
96
|
+
else:
|
|
97
|
+
beacon_id += f"k{i}:{random.randint(0, 9999)}"
|
|
98
|
+
beacon_id += ";"
|
|
99
|
+
return beacon_id
|
|
100
|
+
'''randompayloadbydevice'''
|
|
101
|
+
@staticmethod
|
|
102
|
+
def randompayloadbydevice(device, version: str):
|
|
103
|
+
fixed_rand = random.randint(0, 14400)
|
|
104
|
+
reserved = {
|
|
105
|
+
"harmony": "0", "clone": "0", "containe": "", "oz": "UhYmelwouA+V2nPWbOvLTgN2/m8jwGB+yUB5v9tysQg=", "oo": "Xecjt+9S1+f8Pz2VLSxgpw==",
|
|
106
|
+
"kelong": "0", "uptimes": (datetime.now() - timedelta(seconds=fixed_rand)).strftime("%Y-%m-%d %H:%M:%S"), "multiUser": "0",
|
|
107
|
+
"bod": device.brand, "dv": device.device, "firstLevel": "", "manufact": device.brand, "name": device.model, "host": "se.infra",
|
|
108
|
+
"kernel": device.proc_version,
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
"androidId": device.android_id, "platformId": 1, "appKey": APP_KEY, "appVersion": version, "beaconIdSrc": QQMusicClientUtils.randombeaconid(),
|
|
112
|
+
"brand": device.brand, "channelId": "10003505", "cid": "", "imei": device.imei, "imsi": "", "mac": "", "model": device.model, "networkType": "unknown",
|
|
113
|
+
"oaid": "", "osVersion": f"Android {device.version.release},level {device.version.sdk}", "qimei": "", "qimei36": "", "sdkVersion": "1.2.13.6",
|
|
114
|
+
"targetSdkVersion": "33", "audit": "", "userId": "{}", "packageId": "com.tencent.qqmusic", "deviceType": "Phone", "sdkName": "",
|
|
115
|
+
"reserved": orjson.dumps(reserved).decode(),
|
|
116
|
+
}
|
|
117
|
+
'''obtainqimei'''
|
|
118
|
+
@staticmethod
|
|
119
|
+
def obtainqimei(version: str, device):
|
|
120
|
+
try:
|
|
121
|
+
payload = QQMusicClientUtils.randompayloadbydevice(device, version)
|
|
122
|
+
crypt_key = "".join(random.choices("adbcdef1234567890", k=16))
|
|
123
|
+
nonce = "".join(random.choices("adbcdef1234567890", k=16))
|
|
124
|
+
ts = int(time.time())
|
|
125
|
+
key = base64.b64encode(QQMusicClientUtils.rsaencrypt(crypt_key.encode())).decode()
|
|
126
|
+
params = base64.b64encode(QQMusicClientUtils.aesencrypt(crypt_key.encode(), orjson.dumps(payload))).decode()
|
|
127
|
+
extra = '{"appKey":"' + APP_KEY + '"}'
|
|
128
|
+
sign = QQMusicClientUtils.calcmd5(key, params, str(ts * 1000), nonce, SECRET, extra)
|
|
129
|
+
resp = requests.post(
|
|
130
|
+
"https://api.tencentmusic.com/tme/trpc/proxy",
|
|
131
|
+
headers={
|
|
132
|
+
"Host": "api.tencentmusic.com", "method": "GetQimei", "service": "trpc.tme_datasvr.qimeiproxy.QimeiProxy", "appid": "qimei_qq_android",
|
|
133
|
+
"sign": QQMusicClientUtils.calcmd5("qimei_qq_androidpzAuCmaFAaFaHrdakPjLIEqKrGnSOOvH", str(ts)), "user-agent": "QQMusic", "timestamp": str(ts),
|
|
134
|
+
},
|
|
135
|
+
json={
|
|
136
|
+
"app": 0, "os": 1, "qimeiParams": {"key": key, "params": params, "time": str(ts), "nonce": nonce, "sign": sign, "extra": extra},
|
|
137
|
+
},
|
|
138
|
+
)
|
|
139
|
+
data = orjson.loads(orjson.loads(resp.content)["data"])["data"]
|
|
140
|
+
device.qimei = data["q36"]
|
|
141
|
+
result = {"q16": data["q16"], "q36": data["q36"]}
|
|
142
|
+
return result
|
|
143
|
+
except:
|
|
144
|
+
result = {"q16": "", "q36": "6c9d3cd110abca9b16311cee10001e717614"}
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
'''OSVersion'''
|
|
149
|
+
@dataclass
|
|
150
|
+
class OSVersion:
|
|
151
|
+
incremental: str = "5891938"
|
|
152
|
+
release: str = "10"
|
|
153
|
+
codename: str = "REL"
|
|
154
|
+
sdk: int = 29
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
'''Device'''
|
|
158
|
+
@dataclass
|
|
159
|
+
class Device:
|
|
160
|
+
display: str = field(default_factory=lambda: f"QMAPI.{random.randint(100000, 999999)}.001")
|
|
161
|
+
product: str = "iarim"
|
|
162
|
+
device: str = "sagit"
|
|
163
|
+
board: str = "eomam"
|
|
164
|
+
model: str = "MI 6"
|
|
165
|
+
fingerprint: str = field(default_factory=lambda: f"xiaomi/iarim/sagit:10/eomam.200122.001/{random.randint(1000000, 9999999)}:user/release-keys")
|
|
166
|
+
boot_id: str = field(default_factory=lambda: str(uuid4()))
|
|
167
|
+
proc_version: str = field(default_factory=lambda: f"Linux 5.4.0-54-generic-{''.join(random.choices(string.ascii_letters + string.digits, k=8))} (android-build@google.com)")
|
|
168
|
+
imei: str = field(default_factory=QQMusicClientUtils.randomimei)
|
|
169
|
+
brand: str = "Xiaomi"
|
|
170
|
+
bootloader: str = "U-boot"
|
|
171
|
+
base_band: str = ""
|
|
172
|
+
version: OSVersion = field(default_factory=OSVersion)
|
|
173
|
+
sim_info: str = "T-Mobile"
|
|
174
|
+
os_type: str = "android"
|
|
175
|
+
mac_address: str = "00:50:56:C0:00:08"
|
|
176
|
+
ip_address: ClassVar[list[int]] = [10, 0, 1, 3]
|
|
177
|
+
wifi_bssid: str = "00:50:56:C0:00:08"
|
|
178
|
+
wifi_ssid: str = "<unknown ssid>"
|
|
179
|
+
imsi_md5: list[int] = field(default_factory=lambda: list(hashlib.md5(bytes([random.randint(0, 255) for _ in range(16)])).digest()))
|
|
180
|
+
android_id: str = field(default_factory=lambda: binascii.hexlify(bytes([random.randint(0, 255) for _ in range(8)])).decode("utf-8"))
|
|
181
|
+
apn: str = "wifi"
|
|
182
|
+
vendor_name: str = "MIUI"
|
|
183
|
+
vendor_os_name: str = "qmapi"
|
|
184
|
+
qimei: None | str = None
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Function:
|
|
3
|
+
Implementation of QuarkParser
|
|
4
|
+
Author:
|
|
5
|
+
Zhenchao Jin
|
|
6
|
+
WeChat Official Account (微信公众号):
|
|
7
|
+
Charles的皮卡丘
|
|
8
|
+
'''
|
|
9
|
+
import time
|
|
10
|
+
import requests
|
|
11
|
+
from .misc import resp2json
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
'''QuarkParser'''
|
|
16
|
+
class QuarkParser():
|
|
17
|
+
'''parsefromurl'''
|
|
18
|
+
@staticmethod
|
|
19
|
+
def parsefromurl(url: str, passcode: str = '', cookies: str | dict = '', max_tries: int = 3):
|
|
20
|
+
for _ in range(max_tries):
|
|
21
|
+
try:
|
|
22
|
+
download_result, download_url = QuarkParser._parsefromurl(url=url, passcode=passcode, cookies=cookies)
|
|
23
|
+
break
|
|
24
|
+
except:
|
|
25
|
+
download_result, download_url = {}, ""
|
|
26
|
+
return download_result, download_url
|
|
27
|
+
'''_parsefromurl'''
|
|
28
|
+
@staticmethod
|
|
29
|
+
def _parsefromurl(url: str, passcode: str = '', cookies: str | dict = ''):
|
|
30
|
+
# init
|
|
31
|
+
session, download_result = requests.Session(), {}
|
|
32
|
+
parsed_url = urlparse(url)
|
|
33
|
+
pwd_id = parsed_url.path.strip('/').split('/')[-1]
|
|
34
|
+
if cookies and isinstance(cookies, str): cookies = dict(item.split("=", 1) for item in cookies.split("; "))
|
|
35
|
+
headers = {
|
|
36
|
+
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Core/1.94.225.400 QQBrowser/12.2.5544.400',
|
|
37
|
+
'origin': 'https://pan.quark.cn', 'referer': 'https://pan.quark.cn/', 'accept-language': 'zh-CN,zh;q=0.9',
|
|
38
|
+
}
|
|
39
|
+
# share/sharepage/token
|
|
40
|
+
json_data = {'pwd_id': pwd_id, 'passcode': passcode}
|
|
41
|
+
params = {'pr': 'ucpro', 'fr': 'pc', 'uc_param_str': '', '__dt': '596', '__t': f'{str(int(time.time() * 1000))}'}
|
|
42
|
+
resp = session.post('https://drive-h.quark.cn/1/clouddrive/share/sharepage/token', params=params, json=json_data, cookies=cookies, headers=headers)
|
|
43
|
+
resp.raise_for_status()
|
|
44
|
+
token_data = resp2json(resp=resp)
|
|
45
|
+
stoken = token_data['data']['stoken']
|
|
46
|
+
download_result['token_data'] = token_data
|
|
47
|
+
time.sleep(0.1)
|
|
48
|
+
# share/sharepage/detail
|
|
49
|
+
params = {
|
|
50
|
+
"pr": "ucpro", "fr": "pc", "uc_param_str": "", "ver": "2", "pwd_id": pwd_id, "stoken": stoken, "pdir_fid": "0", "force": "0",
|
|
51
|
+
"_page": "1", "_size": "50", "_fetch_banner": "1", "_fetch_share": "1", "fetch_relate_conversation": "1", "_fetch_total": "1",
|
|
52
|
+
"_sort": "file_type:asc,file_name:asc", "__dt": "1020", "__t": f"{int(time.time() * 1000)}"
|
|
53
|
+
}
|
|
54
|
+
resp = session.get('https://drive-h.quark.cn/1/clouddrive/share/sharepage/detail', params=params, cookies=cookies, headers=headers)
|
|
55
|
+
resp.raise_for_status()
|
|
56
|
+
detail_data = resp2json(resp=resp)
|
|
57
|
+
fid = detail_data["data"]["list"][0]["fid"]
|
|
58
|
+
share_fid_token = detail_data["data"]["list"][0]["share_fid_token"]
|
|
59
|
+
download_result['detail_data'] = detail_data
|
|
60
|
+
time.sleep(0.1)
|
|
61
|
+
# clouddrive/file/info/path_list
|
|
62
|
+
params = {"pr": "ucpro", "fr": "pc", "uc_param_str": "", "__dt": "1266", "__t": f"{int(time.time() * 1000)}"}
|
|
63
|
+
json_data = {"file_path": ["/来自:分享"]}
|
|
64
|
+
resp = session.post('https://drive-pc.quark.cn/1/clouddrive/file/info/path_list', params=params, json=json_data, cookies=cookies, headers=headers)
|
|
65
|
+
resp.raise_for_status()
|
|
66
|
+
path_list_data = resp2json(resp=resp)
|
|
67
|
+
to_pdir_fid = path_list_data["data"][0]["fid"]
|
|
68
|
+
download_result['path_list_data'] = path_list_data
|
|
69
|
+
time.sleep(0.1)
|
|
70
|
+
# share/sharepage/save
|
|
71
|
+
params = {"pr": "ucpro", "fr": "pc", "uc_param_str": "", "__dt": "5660", "__t": f"{int(time.time() * 1000)}"}
|
|
72
|
+
json_data = {"pwd_id": pwd_id, "stoken": stoken, "pdir_fid": "0", "to_pdir_fid": to_pdir_fid, "fid_list": [fid], "fid_token_list": [share_fid_token], "scene": "link"}
|
|
73
|
+
resp = session.post(url='https://drive-pc.quark.cn/1/clouddrive/share/sharepage/save', params=params, cookies=cookies, json=json_data, headers=headers)
|
|
74
|
+
resp.raise_for_status()
|
|
75
|
+
save_data = resp2json(resp=resp)
|
|
76
|
+
task_id = save_data['data']['task_id']
|
|
77
|
+
download_result['save_data'] = save_data
|
|
78
|
+
time.sleep(0.1)
|
|
79
|
+
# clouddrive/task
|
|
80
|
+
for retry_index in range(5):
|
|
81
|
+
try:
|
|
82
|
+
params = {'pr': 'ucpro', 'fr': 'pc', 'uc_param_str': '', 'task_id': task_id, 'retry_index': str(retry_index), '__dt': '6355', '__t': f'{str(int(time.time() * 1000))}'}
|
|
83
|
+
resp = session.get('https://drive-pc.quark.cn/1/clouddrive/task', params=params, cookies=cookies, headers=headers)
|
|
84
|
+
resp.raise_for_status()
|
|
85
|
+
task_data = resp2json(resp=resp)
|
|
86
|
+
fid_encrypt = task_data['data']['save_as']['save_as_top_fids'][0]
|
|
87
|
+
download_result['task_data'] = task_data
|
|
88
|
+
break
|
|
89
|
+
except:
|
|
90
|
+
time.sleep(0.1)
|
|
91
|
+
continue
|
|
92
|
+
# clouddrive/file/download
|
|
93
|
+
headers = {
|
|
94
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.56 Chrome/100.0.4896.160 Electron/18.3.5.12-a038f7b798 Safari/537.36 Channel/pckk_other_ch",
|
|
95
|
+
"Accept": "application/json, text/plain, */*", "Content-Type": "application/json", "accept-language": "zh-CN", "origin": "https://pan.quark.cn", "referer": "https://pan.quark.cn/",
|
|
96
|
+
}
|
|
97
|
+
params = {'pr': 'ucpro', 'fr': 'pc', 'uc_param_str': '', '__dt': '6743', '__t': f'{str(int(time.time() * 1000))}'}
|
|
98
|
+
json_data = {'fids': [fid_encrypt]}
|
|
99
|
+
resp = session.post('https://drive-pc.quark.cn/1/clouddrive/file/download', params=params, json=json_data, cookies=cookies, headers=headers)
|
|
100
|
+
resp.raise_for_status()
|
|
101
|
+
download_data = resp2json(resp=resp)
|
|
102
|
+
download_url = download_data["data"][0]["download_url"]
|
|
103
|
+
download_result['download_data'] = download_data
|
|
104
|
+
# return
|
|
105
|
+
return download_result, download_url
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Function:
|
|
3
|
+
Implementation of SongInfoUtils
|
|
4
|
+
Author:
|
|
5
|
+
Zhenchao Jin
|
|
6
|
+
WeChat Official Account (微信公众号):
|
|
7
|
+
Charles的皮卡丘
|
|
8
|
+
'''
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from .data import SongInfo
|
|
12
|
+
from tinytag import TinyTag
|
|
13
|
+
from .lyric import WhisperLRC
|
|
14
|
+
from .logger import LoggerHandle
|
|
15
|
+
from .misc import seconds2hms, byte2mb
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
'''SongInfoUtils'''
|
|
19
|
+
class SongInfoUtils:
|
|
20
|
+
'''fillsongtechinfo'''
|
|
21
|
+
@staticmethod
|
|
22
|
+
def fillsongtechinfo(song_info: SongInfo, logger_handle: LoggerHandle, disable_print: bool) -> SongInfo:
|
|
23
|
+
path = Path(song_info.save_path)
|
|
24
|
+
# correct file size
|
|
25
|
+
size = path.stat().st_size
|
|
26
|
+
song_info.file_size_bytes = size
|
|
27
|
+
song_info.file_size = byte2mb(size=size)
|
|
28
|
+
# tinytag parse
|
|
29
|
+
try:
|
|
30
|
+
tag = TinyTag.get(str(path))
|
|
31
|
+
except Exception as err:
|
|
32
|
+
logger_handle.warning(f'SongInfoUtils.fillsongtechinfo >>> {str(path)} (Err: {err})', disable_print=disable_print)
|
|
33
|
+
return song_info
|
|
34
|
+
if tag.duration:
|
|
35
|
+
song_info.duration_s = int(round(tag.duration))
|
|
36
|
+
song_info.duration = seconds2hms(tag.duration)
|
|
37
|
+
if tag.bitrate:
|
|
38
|
+
song_info.bitrate = int(round(tag.bitrate))
|
|
39
|
+
if tag.samplerate:
|
|
40
|
+
song_info.samplerate = int(tag.samplerate)
|
|
41
|
+
if tag.channels:
|
|
42
|
+
song_info.channels = int(tag.channels)
|
|
43
|
+
if getattr(tag, "codec", None):
|
|
44
|
+
song_info.codec = tag.codec
|
|
45
|
+
elif getattr(tag, "extra", None) and isinstance(tag.extra, dict):
|
|
46
|
+
song_info.codec = tag.extra.get("codec") or tag.extra.get("mime-type")
|
|
47
|
+
# lyric
|
|
48
|
+
if os.environ.get('ENABLE_WHISPERLRC', 'False').lower() == 'true' and ((not song_info.lyric) or (song_info.lyric == 'NULL')):
|
|
49
|
+
lyric_result = WhisperLRC(model_size_or_path='small').fromfilepath(str(path))
|
|
50
|
+
lyric = lyric_result['lyric']
|
|
51
|
+
song_info.lyric = lyric
|
|
52
|
+
song_info.raw_data['lyric'] = lyric_result
|
|
53
|
+
# return
|
|
54
|
+
return song_info
|