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.
Files changed (59) hide show
  1. musicdl/__init__.py +5 -5
  2. musicdl/modules/__init__.py +10 -3
  3. musicdl/modules/common/__init__.py +2 -0
  4. musicdl/modules/common/gdstudio.py +204 -0
  5. musicdl/modules/js/__init__.py +1 -0
  6. musicdl/modules/js/youtube/__init__.py +2 -0
  7. musicdl/modules/js/youtube/botguard.js +1 -0
  8. musicdl/modules/js/youtube/jsinterp.py +902 -0
  9. musicdl/modules/js/youtube/runner.js +2 -0
  10. musicdl/modules/sources/__init__.py +41 -10
  11. musicdl/modules/sources/apple.py +207 -0
  12. musicdl/modules/sources/base.py +256 -28
  13. musicdl/modules/sources/bilibili.py +118 -0
  14. musicdl/modules/sources/buguyy.py +148 -0
  15. musicdl/modules/sources/fangpi.py +153 -0
  16. musicdl/modules/sources/fivesing.py +108 -0
  17. musicdl/modules/sources/gequbao.py +148 -0
  18. musicdl/modules/sources/jamendo.py +108 -0
  19. musicdl/modules/sources/joox.py +104 -68
  20. musicdl/modules/sources/kugou.py +129 -76
  21. musicdl/modules/sources/kuwo.py +188 -68
  22. musicdl/modules/sources/lizhi.py +107 -0
  23. musicdl/modules/sources/migu.py +172 -66
  24. musicdl/modules/sources/mitu.py +140 -0
  25. musicdl/modules/sources/mp3juice.py +264 -0
  26. musicdl/modules/sources/netease.py +163 -115
  27. musicdl/modules/sources/qianqian.py +125 -77
  28. musicdl/modules/sources/qq.py +232 -94
  29. musicdl/modules/sources/tidal.py +342 -0
  30. musicdl/modules/sources/ximalaya.py +256 -0
  31. musicdl/modules/sources/yinyuedao.py +144 -0
  32. musicdl/modules/sources/youtube.py +238 -0
  33. musicdl/modules/utils/__init__.py +12 -4
  34. musicdl/modules/utils/appleutils.py +563 -0
  35. musicdl/modules/utils/data.py +107 -0
  36. musicdl/modules/utils/logger.py +211 -58
  37. musicdl/modules/utils/lyric.py +73 -0
  38. musicdl/modules/utils/misc.py +335 -23
  39. musicdl/modules/utils/modulebuilder.py +75 -0
  40. musicdl/modules/utils/neteaseutils.py +81 -0
  41. musicdl/modules/utils/qqutils.py +184 -0
  42. musicdl/modules/utils/quarkparser.py +105 -0
  43. musicdl/modules/utils/songinfoutils.py +54 -0
  44. musicdl/modules/utils/tidalutils.py +738 -0
  45. musicdl/modules/utils/youtubeutils.py +3606 -0
  46. musicdl/musicdl.py +184 -86
  47. musicdl-2.7.3.dist-info/LICENSE +203 -0
  48. musicdl-2.7.3.dist-info/METADATA +704 -0
  49. musicdl-2.7.3.dist-info/RECORD +53 -0
  50. {musicdl-2.1.11.dist-info → musicdl-2.7.3.dist-info}/WHEEL +5 -5
  51. musicdl-2.7.3.dist-info/entry_points.txt +2 -0
  52. musicdl/modules/sources/baiduFlac.py +0 -69
  53. musicdl/modules/sources/xiami.py +0 -104
  54. musicdl/modules/utils/downloader.py +0 -80
  55. musicdl-2.1.11.dist-info/LICENSE +0 -22
  56. musicdl-2.1.11.dist-info/METADATA +0 -82
  57. musicdl-2.1.11.dist-info/RECORD +0 -24
  58. {musicdl-2.1.11.dist-info → musicdl-2.7.3.dist-info}/top_level.txt +0 -0
  59. {musicdl-2.1.11.dist-info → musicdl-2.7.3.dist-info}/zip-safe +0 -0
@@ -1,106 +1,244 @@
1
1
  '''
2
2
  Function:
3
- qq音乐下载: https://y.qq.com/
3
+ Implementation of QQMusicClient: https://y.qq.com/
4
4
  Author:
5
- Charles
6
- 微信公众号:
5
+ Zhenchao Jin
6
+ WeChat Official Account (微信公众号):
7
7
  Charles的皮卡丘
8
8
  '''
9
+ import copy
10
+ import json
11
+ import time
12
+ import base64
9
13
  import random
10
- import requests
11
- from .base import Base
12
- from ..utils.misc import *
14
+ from .base import BaseMusicClient
15
+ from rich.progress import Progress
16
+ from ..utils.qqutils import QQMusicClientUtils, Device, DEFAULT_VIP_QUALITIES, DEFAULT_QUALITIES
17
+ from ..utils import byte2mb, resp2json, seconds2hms, legalizestring, safeextractfromdict, usesearchheaderscookies, SongInfo
13
18
 
14
19
 
15
- '''QQ音乐下载类'''
16
- class qq(Base):
17
- def __init__(self, config, logger_handle, **kwargs):
18
- super(qq, self).__init__(config, logger_handle, **kwargs)
19
- self.source = 'qq'
20
- self.__initialize()
21
- '''歌曲搜索'''
22
- def search(self, keyword):
23
- self.logger_handle.info('正在%s中搜索 ——> %s...' % (self.source, keyword))
24
- cfg = self.config.copy()
25
- params = {
26
- 'w': keyword,
27
- 'format': 'json',
28
- 'p': '1',
29
- 'n': cfg['search_size_per_source']
20
+ '''QQMusicClient'''
21
+ class QQMusicClient(BaseMusicClient):
22
+ source = 'QQMusicClient'
23
+ def __init__(self, **kwargs):
24
+ super(QQMusicClient, self).__init__(**kwargs)
25
+ self.uid = '3931641530'
26
+ self.version_info = dict(
27
+ version="13.2.5.8", version_code=13020508,
28
+ )
29
+ self.device = Device()
30
+ self.qimei_info = QQMusicClientUtils.obtainqimei(version=self.version_info['version'], device=self.device)
31
+ self.default_search_headers = {
32
+ 'Referer': 'https://y.qq.com/',
33
+ 'Origin': 'https://y.qq.com/',
34
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
30
35
  }
31
- response = self.session.get(self.search_url, headers=self.headers, params=params)
32
- all_items = response.json()['data']['song']['list']
33
- songinfos = []
34
- for item in all_items:
35
- params = {
36
- 'guid': str(random.randrange(1000000000, 10000000000)),
37
- 'loginUin': '3051522991',
38
- 'format': 'json',
39
- 'platform': 'yqq',
40
- 'cid': '205361747',
41
- 'uin': '3051522991',
42
- 'songmid': item['songmid'],
43
- 'needNewCode': '0'
36
+ self.default_download_headers = {
37
+ 'Referer': 'http://y.qq.com',
38
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
39
+ }
40
+ self.default_headers = self.default_search_headers
41
+ self._initsession()
42
+ '''_randomsearchid'''
43
+ def _randomsearchid(self):
44
+ e = random.randint(1, 20)
45
+ t = e * 18014398509481984
46
+ n = random.randint(0, 4194304) * 4294967296
47
+ a = time.time()
48
+ r = round(a * 1000) % (24 * 60 * 60 * 1000)
49
+ return str(t + n + r)
50
+ '''_randomguid'''
51
+ def _randomguid(self):
52
+ return "".join(random.choices("abcdef1234567890", k=32))
53
+ '''_constructsearchurls'''
54
+ def _constructsearchurls(self, keyword: str, rule: dict = None, request_overrides: dict = None):
55
+ # init
56
+ rule, request_overrides = rule or {}, request_overrides or {}
57
+ # search rules
58
+ default_rule = {
59
+ 'comm': {
60
+ 'cv': self.version_info['version_code'], 'v': self.version_info['version_code'], 'QIMEI36': self.qimei_info['q36'], 'ct': '11',
61
+ 'tmeAppID': 'qqmusic', 'format': 'json', 'inCharset': 'utf-8', 'outCharset': 'utf-8', 'uid': self.uid,
62
+ },
63
+ 'music.search.SearchCgiService.DoSearchForQQMusicMobile': {
64
+ 'module': 'music.search.SearchCgiService', 'method': 'DoSearchForQQMusicMobile',
65
+ 'param': {'searchid': self._randomsearchid(), 'query': keyword, 'search_type': 0, 'num_per_page': 10, 'page_num': 1, 'highlight': 1, 'grp': 1}
44
66
  }
45
- ext = ''
46
- download_url = ''
47
- filesize = '-MB'
48
- for quality in [("A000", "ape", 800), ("F000", "flac", 800), ("M800", "mp3", 320), ("C400", "m4a", 128), ("M500", "mp3", 128)]:
49
- params['filename'] = '%s%s.%s' % (quality[0], item['songmid'], quality[1])
50
- response = self.session.get(self.mobile_fcg_url, headers=self.ios_headers, params=params)
51
- response_json = response.json()
52
- if response_json['code'] != 0: continue
53
- vkey = response_json.get('data', {}).get('items', [{}])[0].get('vkey', '')
54
- if vkey:
55
- ext = quality[1]
56
- download_url = 'http://dl.stream.qqmusic.qq.com/{}?vkey={}&guid={}&uin=3051522991&fromtag=64'.format('%s%s.%s' % (quality[0], item['songmid'], quality[1]), vkey, params['guid'])
57
- if ext in ['ape', 'flac']:
58
- filesize = item['size%s' % ext]
59
- elif ext in ['mp3', 'm4a']:
60
- filesize = item['size%s' % quality[-1]]
61
- break
62
- if not download_url:
67
+ }
68
+ default_rule.update(rule)
69
+ # construct search urls based on search rules
70
+ base_url = 'https://u.y.qq.com/cgi-bin/musicu.fcg'
71
+ search_urls, page_size, count = [], self.search_size_per_page, 0
72
+ while self.search_size_per_source > count:
73
+ page_rule = copy.deepcopy(default_rule)
74
+ page_rule['music.search.SearchCgiService.DoSearchForQQMusicMobile']['param']['num_per_page'] = page_size
75
+ page_rule['music.search.SearchCgiService.DoSearchForQQMusicMobile']['param']['page_num'] = int(count // page_size) + 1
76
+ search_urls.append({'url': base_url, 'json': page_rule})
77
+ count += page_size
78
+ # return
79
+ return search_urls
80
+ '''_search'''
81
+ @usesearchheaderscookies
82
+ def _search(self, keyword: str = '', search_url: dict = {}, request_overrides: dict = None, song_infos: list = [], progress: Progress = None, progress_id: int = 0):
83
+ # init
84
+ search_meta, request_overrides = copy.deepcopy(search_url), request_overrides or {}
85
+ search_url = search_meta.pop('url')
86
+ # successful
87
+ try:
88
+ # --search results
89
+ resp = self.post(search_url, **search_meta, **request_overrides)
90
+ resp.raise_for_status()
91
+ search_results = resp2json(resp)['music.search.SearchCgiService.DoSearchForQQMusicMobile']['data']['body']['item_song']
92
+ for search_result in search_results:
93
+ # --download results
94
+ if not isinstance(search_result, dict) or ('mid' not in search_result):
95
+ continue
96
+ # ----init
97
+ file_size_infos, song_info = dict(
98
+ size_new=safeextractfromdict(search_result, ['file', 'size_new'], ['0', '0', '0', '0', '0']), size_flac=safeextractfromdict(search_result, ['file', 'size_flac'], '0'),
99
+ size_192ogg=safeextractfromdict(search_result, ['file', 'size_192ogg'], '0'), size_96ogg=safeextractfromdict(search_result, ['file', 'size_96ogg'], '0'),
100
+ size_320mp3=safeextractfromdict(search_result, ['file', 'size_320mp3'], '0'), size_128mp3=safeextractfromdict(search_result, ['file', 'size_128mp3'], '0'),
101
+ size_192aac=safeextractfromdict(search_result, ['file', 'size_192aac'], '0'), size_96aac=safeextractfromdict(search_result, ['file', 'size_96aac'], '0'),
102
+ size_48aac=safeextractfromdict(search_result, ['file', 'size_48aac'], '0'),
103
+ ), SongInfo(source=self.source)
104
+ # ----if cookies exits, assume user with vip first
105
+ if self.default_cookies or request_overrides.get('cookies', {}):
106
+ default_vip_rule = {
107
+ 'comm': {
108
+ 'cv': self.version_info['version_code'], 'v': self.version_info['version_code'], 'QIMEI36': self.qimei_info['q36'], 'ct': '11',
109
+ 'tmeAppID': 'qqmusic', 'format': 'json', 'inCharset': 'utf-8', 'outCharset': 'utf-8', 'uid': self.uid,
110
+ },
111
+ 'music.vkey.GetEVkey.CgiGetEVkey': {
112
+ 'module': 'music.vkey.GetEVkey', 'method': 'CgiGetEVkey',
113
+ 'param': {'filename': [], 'guid': self._randomguid(), 'songmid': [search_result['mid']], 'songtype': [0]}
114
+ },
115
+ }
116
+ default_file_sizes = [
117
+ file_size_infos['size_new'][0], file_size_infos['size_new'][1], file_size_infos['size_new'][2], file_size_infos['size_flac'], file_size_infos['size_new'][5],
118
+ file_size_infos['size_new'][3], file_size_infos['size_192ogg'], file_size_infos['size_96ogg'],
119
+ ]
120
+ for quality, default_file_size in zip(list(DEFAULT_VIP_QUALITIES.values()), default_file_sizes):
121
+ song_info = SongInfo(source=self.source)
122
+ try:
123
+ current_rule = copy.deepcopy(default_vip_rule)
124
+ current_rule['music.vkey.GetEVkey.CgiGetEVkey']['param']['filename'] = [f"{quality[0]}{search_result['mid']}{search_result['mid']}{quality[1]}"]
125
+ resp = self.post('https://u.y.qq.com/cgi-bin/musicu.fcg', json=current_rule, **request_overrides)
126
+ resp.raise_for_status()
127
+ download_result: dict = resp2json(resp)
128
+ if download_result.get('code', 'NULL') not in [0] or safeextractfromdict(download_result, ['music.vkey.GetEVkey.CgiGetEVkey', 'code'], 'NULL') not in [0]: continue
129
+ download_url = safeextractfromdict(download_result, ['music.vkey.GetEVkey.CgiGetEVkey', 'data', "midurlinfo", 0, "wifiurl"], "")
130
+ if not download_url: continue
131
+ download_url, ext, file_size = "https://isure.stream.qqmusic.qq.com/" + download_url, quality[1][1:], default_file_size
132
+ download_url_status = self.audio_link_tester.test(download_url, request_overrides)
133
+ song_info.update(dict(
134
+ download_url=download_url, download_url_status=download_url_status, ext=ext, file_size_bytes=file_size, file_size=byte2mb(file_size),
135
+ raw_data={'search': search_result, 'download': download_result},
136
+ ))
137
+ if song_info.with_valid_download_url: break
138
+ except:
139
+ continue
140
+ # ----common user in post try
141
+ if not song_info.with_valid_download_url:
142
+ default_rule = {
143
+ 'comm': {
144
+ 'cv': self.version_info['version_code'], 'v': self.version_info['version_code'], 'QIMEI36': self.qimei_info['q36'], 'ct': '11',
145
+ 'tmeAppID': 'qqmusic', 'format': 'json', 'inCharset': 'utf-8', 'outCharset': 'utf-8', 'uid': self.uid,
146
+ },
147
+ 'music.vkey.GetVkey.UrlGetVkey': {
148
+ 'module': 'music.vkey.GetVkey', 'method': 'UrlGetVkey',
149
+ 'param': {'filename': [], 'guid': self._randomguid(), 'songmid': [search_result['mid']], 'songtype': [0]}
150
+ },
151
+ }
152
+ default_file_sizes = [
153
+ file_size_infos['size_new'][0], file_size_infos['size_new'][1], file_size_infos['size_new'][2], file_size_infos['size_flac'], file_size_infos['size_new'][5], file_size_infos['size_new'][3],
154
+ file_size_infos['size_192ogg'], file_size_infos['size_96ogg'], file_size_infos['size_320mp3'], file_size_infos['size_128mp3'], file_size_infos['size_192aac'], file_size_infos['size_96aac'],
155
+ file_size_infos['size_48aac'],
156
+ ]
157
+ for quality, default_file_size in zip(list(DEFAULT_QUALITIES.values()), default_file_sizes):
158
+ song_info = SongInfo(source=self.source)
159
+ try:
160
+ current_rule = copy.deepcopy(default_rule)
161
+ current_rule['music.vkey.GetVkey.UrlGetVkey']['param']['filename'] = [f"{quality[0]}{search_result['mid']}{search_result['mid']}{quality[1]}"]
162
+ resp = self.post('https://u.y.qq.com/cgi-bin/musicu.fcg', json=current_rule, **request_overrides)
163
+ resp.raise_for_status()
164
+ download_result: dict = resp2json(resp)
165
+ if download_result.get('code', 'NULL') not in [0] or safeextractfromdict(download_result, ['music.vkey.GetVkey.UrlGetVkey', 'code'], 'NULL') not in [0]: continue
166
+ download_url = safeextractfromdict(download_result, ['music.vkey.GetVkey.UrlGetVkey', 'data', "midurlinfo", 0, "wifiurl"], "")
167
+ if not download_url: continue
168
+ download_url, ext, file_size = "https://isure.stream.qqmusic.qq.com/" + download_url, quality[1][1:], default_file_size
169
+ download_url_status = self.audio_link_tester.test(download_url, request_overrides)
170
+ song_info.update(dict(
171
+ download_url=download_url, download_url_status=download_url_status, ext=ext, file_size_bytes=file_size, file_size=byte2mb(file_size),
172
+ raw_data={'search': search_result, 'download': download_result},
173
+ ))
174
+ if song_info.with_valid_download_url: break
175
+ except:
176
+ continue
177
+ # ----common user in get try
178
+ if not song_info.with_valid_download_url:
179
+ params = {
180
+ 'data': json.dumps({
181
+ "req_0": {
182
+ "module": 'vkey.GetVkeyServer', "method": 'CgiGetVkey',
183
+ "param": {
184
+ "filename": [f"M500{search_result['mid']}{search_result['mid']}.mp3"], "guid": "10000", "songmid": [search_result['mid']],
185
+ "songtype": [0], "uin": "0", "loginflag": 1, "platform": "20"
186
+ }
187
+ },
188
+ "loginUin": "0",
189
+ "comm": {"uin": "0", "format": "json", "ct": 24, "cv": 0}
190
+ }, ensure_ascii=False).encode('utf-8'),
191
+ 'format': 'json',
192
+ }
193
+ song_info = SongInfo(source=self.source)
194
+ try:
195
+ resp = self.get('https://u.y.qq.com/cgi-bin/musicu.fcg', params=params, **request_overrides)
196
+ resp.raise_for_status()
197
+ download_result: dict = resp2json(resp)
198
+ if download_result.get('code', 'NULL') not in [0] or safeextractfromdict(download_result, ['req_0', 'code'], 'NULL') not in [0]: continue
199
+ download_url = safeextractfromdict(download_result, ['req_0', 'data', "midurlinfo", 0, "purl"], "")
200
+ if not download_url: continue
201
+ download_url, ext, file_size = 'http://ws.stream.qqmusic.qq.com/' + download_url, "mp3", file_size_infos['size_128mp3']
202
+ download_url_status = self.audio_link_tester.test(download_url, request_overrides)
203
+ song_info.update(dict(
204
+ download_url=download_url, download_url_status=download_url_status, ext=ext, file_size_bytes=file_size, file_size=byte2mb(file_size),
205
+ raw_data={'search': search_result, 'download': download_result},
206
+ ))
207
+ except:
208
+ continue
209
+ # ----parse more infos
210
+ if not song_info.with_valid_download_url: continue
211
+ song_info.update(dict(
212
+ duration_s=search_result.get('interval', 0), duration=seconds2hms(search_result.get('interval', 0)),
213
+ song_name=legalizestring(search_result.get('title', 'NULL'), replace_null_string='NULL'),
214
+ singers=legalizestring(', '.join([singer.get('name', 'NULL') for singer in search_result.get('singer', [])]), replace_null_string='NULL'),
215
+ album=legalizestring(safeextractfromdict(search_result, ['album', 'title'], 'NULL'), replace_null_string='NULL'),
216
+ identifier=search_result['mid'],
217
+ ))
218
+ # --lyric results
63
219
  params = {
64
- 'data': json.dumps({
65
- "req": {"module": "CDN.SrfCdnDispatchServer", "method": "GetCdnDispatch", "param": {"guid": "3982823384", "calltype": 0, "userip": ""}},
66
- "req_0": {"module": "vkey.GetVkeyServer", "method": "CgiGetVkey", "param": {"guid": "3982823384", "songmid": [item['songmid']], "songtype": [0], "uin": "0", "loginflag": 1, "platform": "20"}},
67
- "comm": {"uin": 0, "format": "json", "ct": 24, "cv": 0}
68
- })
220
+ 'songmid': str(search_result['mid']), 'g_tk': '5381', 'loginUin': '0', 'hostUin': '0', 'format': 'json',
221
+ 'inCharset': 'utf8', 'outCharset': 'utf-8', 'platform': 'yqq'
69
222
  }
70
- response = self.session.get(self.fcg_url, headers=self.ios_headers, params=params)
71
- response_json = response.json()
72
- if response_json['code'] == 0 and response_json['req']['code'] == 0 and response_json['req_0']['code'] == 0:
73
- ext = '.m4a'
74
- download_url = str(response_json["req"]["data"]["freeflowsip"][0]) + str(response_json["req_0"]["data"]["midurlinfo"][0]["purl"])
75
- filesize = item['size128']
76
- if (not download_url) or (filesize == '-MB') or (filesize == 0): continue
77
- filesize = str(round(filesize/1024/1024, 2)) + 'MB'
78
- duration = int(item.get('interval', 0))
79
- songinfo = {
80
- 'source': self.source,
81
- 'songid': str(item['songmid']),
82
- 'singers': filterBadCharacter(','.join([s.get('name', '') for s in item.get('singer', [])])),
83
- 'album': filterBadCharacter(item.get('albumname', '-')),
84
- 'songname': filterBadCharacter(item.get('songname', '-')),
85
- 'savedir': cfg['savedir'],
86
- 'savename': '_'.join([self.source, filterBadCharacter(item.get('songname', '-'))]),
87
- 'download_url': download_url,
88
- 'filesize': filesize,
89
- 'ext': ext,
90
- 'duration': seconds2hms(duration)
91
- }
92
- songinfos.append(songinfo)
93
- return songinfos
94
- '''初始化'''
95
- def __initialize(self):
96
- self.ios_headers = {
97
- 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
98
- 'Referer': 'http://y.qq.com'
99
- }
100
- self.headers = {
101
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36',
102
- 'Referer': 'http://y.qq.com'
103
- }
104
- self.search_url = 'https://c.y.qq.com/soso/fcgi-bin/client_search_cp'
105
- self.mobile_fcg_url = 'https://c.y.qq.com/base/fcgi-bin/fcg_music_express_mobile3.fcg'
106
- self.fcg_url = 'https://u.y.qq.com/cgi-bin/musicu.fcg'
223
+ request_overrides = copy.deepcopy(request_overrides)
224
+ request_overrides.pop('headers', {})
225
+ try:
226
+ resp = self.get('https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg', headers={'Referer': 'https://y.qq.com/portal/player.html'}, params=params, **request_overrides)
227
+ lyric_result: dict = resp2json(resp) or {'lyric': ''}
228
+ lyric = lyric_result.get('lyric', '')
229
+ if not lyric: lyric = 'NULL'
230
+ else: lyric = base64.b64decode(lyric).decode('utf-8')
231
+ except:
232
+ lyric_result, lyric = {}, "NULL"
233
+ song_info.raw_data['lyric'], song_info.lyric = lyric_result, lyric
234
+ # --append to song_infos
235
+ song_infos.append(song_info)
236
+ # --judgement for search_size
237
+ if self.strict_limit_search_size_per_page and len(song_infos) >= self.search_size_per_page: break
238
+ # --update progress
239
+ progress.update(progress_id, description=f"{self.source}.search >>> {search_url} (Success)")
240
+ # failure
241
+ except Exception as err:
242
+ progress.update(progress_id, description=f"{self.source}.search >>> {search_url} (Error: {err})")
243
+ # return
244
+ return song_infos