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
@@ -0,0 +1,144 @@
1
+ '''
2
+ Function:
3
+ Implementation of YinyuedaoMusicClient: https://1mp3.top/
4
+ Author:
5
+ Zhenchao Jin
6
+ WeChat Official Account (微信公众号):
7
+ Charles的皮卡丘
8
+ '''
9
+ import json_repair
10
+ from bs4 import BeautifulSoup
11
+ from .base import BaseMusicClient
12
+ from rich.progress import Progress
13
+ from ..utils import legalizestring, usesearchheaderscookies, safeextractfromdict, SongInfo, QuarkParser
14
+
15
+
16
+ '''YinyuedaoMusicClient'''
17
+ class YinyuedaoMusicClient(BaseMusicClient):
18
+ source = 'YinyuedaoMusicClient'
19
+ def __init__(self, **kwargs):
20
+ super(YinyuedaoMusicClient, self).__init__(**kwargs)
21
+ if not self.quark_parser_config.get('cookies'): self.logger_handle.warning(f'{self.source}.__init__ >>> "quark_parser_config" is not configured, so song downloads are restricted and only mp3 files can be downloaded.')
22
+ self.default_search_headers = {
23
+ "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
24
+ "accept-encoding": "gzip, deflate, br, zstd",
25
+ "accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
26
+ "priority": "u=0, i",
27
+ "referer": "https://1mp3.top/",
28
+ "sec-ch-ua": "\"Chromium\";v=\"142\", \"Google Chrome\";v=\"142\", \"Not_A Brand\";v=\"99\"",
29
+ "sec-ch-ua-mobile": "?0",
30
+ "sec-ch-ua-platform": "\"Windows\"",
31
+ "sec-fetch-dest": "document",
32
+ "sec-fetch-mode": "navigate",
33
+ "sec-fetch-site": "same-origin",
34
+ "sec-fetch-user": "?1",
35
+ "upgrade-insecure-requests": "1",
36
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
37
+ }
38
+ self.default_download_headers = {
39
+ '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',
40
+ }
41
+ self.default_headers = self.default_search_headers
42
+ self._initsession()
43
+ '''_constructsearchurls'''
44
+ def _constructsearchurls(self, keyword: str, rule: dict = None, request_overrides: dict = None):
45
+ # init
46
+ rule, request_overrides = rule or {}, request_overrides or {}
47
+ # construct search urls
48
+ search_urls = [f'https://1mp3.top/?key={keyword}&search=1']
49
+ self.search_size_per_page = self.search_size_per_source
50
+ # return
51
+ return search_urls
52
+ '''_parsesearchresultsfromhtml'''
53
+ def _parsesearchresultsfromhtml(self, html_text: str):
54
+ soup = BeautifulSoup(html_text, "lxml")
55
+ search_results = []
56
+ for li in soup.select("#musicList > li"):
57
+ music_id_attr = li.get("data-music-id")
58
+ music_title_attr = li.get("data-music-title")
59
+ music_singer_attr = li.get("data-music-singer")
60
+ music_cover_attr = li.get("data-music-cover")
61
+ a_download = li.select_one("a.download-btn")
62
+ music_json_str = a_download.get("data-music")
63
+ music_data = json_repair.loads(music_json_str)
64
+ search_results.append({
65
+ "id_attr": music_id_attr, "title_attr": music_title_attr, "singer_attr": music_singer_attr, "cover_attr": music_cover_attr,
66
+ "id": music_data.get("id"), "title": music_data.get("title"), "singer": music_data.get("singer"), "picurl": music_data.get("picurl"),
67
+ "create_time": music_data.get("create_time"), "mtype": music_data.get("mtype"), "downlist": music_data.get("downlist", []),
68
+ "ktmdownlist": music_data.get("ktmdownlist", []),
69
+ })
70
+ return search_results
71
+ '''_search'''
72
+ @usesearchheaderscookies
73
+ def _search(self, keyword: str = '', search_url: str = '', request_overrides: dict = None, song_infos: list = [], progress: Progress = None, progress_id: int = 0):
74
+ # init
75
+ request_overrides = request_overrides or {}
76
+ # successful
77
+ try:
78
+ # --search results
79
+ resp = self.get(search_url, **request_overrides)
80
+ resp.raise_for_status()
81
+ search_results = self._parsesearchresultsfromhtml(resp.text)
82
+ for search_result in search_results:
83
+ # --download results
84
+ if not isinstance(search_result, dict) or ('id' not in search_result):
85
+ continue
86
+ song_info = SongInfo(source=self.source)
87
+ # ----parse from quark links
88
+ if self.quark_parser_config.get('cookies'):
89
+ quark_download_urls = [*search_result.get('downlist', []), *search_result.get('ktmdownlist', [])]
90
+ for quark_download_url in quark_download_urls:
91
+ song_fmt = safeextractfromdict(quark_download_url, ['format'], '')
92
+ if not song_fmt or song_fmt.lower() in ['mp3']: continue
93
+ song_info = SongInfo(source=self.source)
94
+ try:
95
+ quark_wav_download_url = quark_download_url['url']
96
+ download_result, download_url = QuarkParser.parsefromurl(quark_wav_download_url, **self.quark_parser_config)
97
+ if not download_url: continue
98
+ download_url_status = self.quark_audio_link_tester.test(download_url, request_overrides)
99
+ download_url_status['probe_status'] = self.quark_audio_link_tester.probe(download_url, request_overrides)
100
+ ext = download_url_status['probe_status']['ext']
101
+ if ext == 'NULL': ext = 'flac'
102
+ song_info.update(dict(
103
+ download_url=download_url, download_url_status=download_url_status, raw_data={'search': search_result, 'download': download_result},
104
+ default_download_headers=self.quark_default_download_headers, ext=ext, file_size=download_url_status['probe_status']['file_size']
105
+ ))
106
+ if song_info.with_valid_download_url: break
107
+ except:
108
+ continue
109
+ # ----parse from play url
110
+ if not song_info.with_valid_download_url:
111
+ song_info = SongInfo(source=self.source)
112
+ try:
113
+ resp = self.get(f'https://1mp3.top/include/geturl.php?id={search_result["id"]}', **request_overrides)
114
+ resp.raise_for_status()
115
+ download_url = resp.text.strip()
116
+ download_url_status = self.audio_link_tester.test(download_url, request_overrides)
117
+ download_url_status['probe_status'] = self.audio_link_tester.probe(download_url, request_overrides)
118
+ ext = download_url_status['probe_status']['ext']
119
+ if ext == 'NULL': download_url.split('.')[-1].split('?')[0] or 'mp3'
120
+ song_info.update(dict(
121
+ download_url=download_url, download_url_status=download_url_status, raw_data={'search': search_result, 'download': {}},
122
+ ext=ext, file_size=download_url_status['probe_status']['file_size']
123
+ ))
124
+ except:
125
+ continue
126
+ if not song_info.with_valid_download_url: continue
127
+ # ----parse more infos
128
+ lyric_result, lyric = dict(), 'NULL'
129
+ song_info.raw_data['lyric'] = lyric_result
130
+ song_info.update(dict(
131
+ lyric=lyric, duration='-:-:-', song_name=legalizestring(search_result.get('title', 'NULL'), replace_null_string='NULL'),
132
+ singers=legalizestring(search_result.get('singer', 'NULL'), replace_null_string='NULL'), album='NULL', identifier=search_result['id'],
133
+ ))
134
+ # --append to song_infos
135
+ song_infos.append(song_info)
136
+ # --judgement for search_size
137
+ if self.strict_limit_search_size_per_page and len(song_infos) >= self.search_size_per_page: break
138
+ # --update progress
139
+ progress.update(progress_id, description=f"{self.source}.search >>> {search_url} (Success)")
140
+ # failure
141
+ except Exception as err:
142
+ progress.update(progress_id, description=f"{self.source}.search >>> {search_url} (Error: {err})")
143
+ # return
144
+ return song_infos
@@ -0,0 +1,238 @@
1
+ '''
2
+ Function:
3
+ Implementation of YouTubeMusicClient: https://music.youtube.com/
4
+ Author:
5
+ Zhenchao Jin
6
+ WeChat Official Account (微信公众号):
7
+ Charles的皮卡丘
8
+ '''
9
+ import copy
10
+ import random
11
+ from ytmusicapi import YTMusic
12
+ from .base import BaseMusicClient
13
+ from rich.progress import Progress
14
+ from ..utils.youtubeutils import YouTube
15
+ from ..utils import legalizestring, resp2json, usesearchheaderscookies, byte2mb, seconds2hms, usedownloadheaderscookies, touchdir, SongInfo, SongInfoUtils
16
+
17
+
18
+ '''constants'''
19
+ REPAIDAPI_KEYS = [
20
+ "1556f6ccb2msh64b807485156b33p12a66djsn7e197bc23f19", "323be663ccmsh35173cb3c5403c2p10f08fjsnad5cf7b49fa9", "7ca8a11abdmsh7eb89d767db710cp191ce7jsnf0274562b5b9", "14a93bf7a5msh51f11db7d121aeap1c4625jsn5cfbb89ff05b",
21
+ "6111b62d2dmshd13d20b55abbbe8p1f8551jsn2e425d987b94", "fcdf2c5ba1msh397175bf89fe87ep19fe51jsn76bc6eddb936", "ab47a3c2camsh4097dc8ff0fb89fp1d95e6jsna047ccc76ab6", "3b6e8de0bfmshf1110629d4a95b0p11b000jsn264f29acc2b0",
22
+ "9203689df7msh9f35abfe7467ce8p12ca71jsn625743a17f66", "ef908b5eeamsh50984dd5b890d6ep1ab791jsn2fb248ebf19a",
23
+ ]
24
+
25
+
26
+ '''YouTubeMusicClient'''
27
+ class YouTubeMusicClient(BaseMusicClient):
28
+ source = 'YouTubeMusicClient'
29
+ def __init__(self, **kwargs):
30
+ super(YouTubeMusicClient, self).__init__(**kwargs)
31
+ self.default_search_headers = {}
32
+ self.default_download_headers = {}
33
+ self.default_headers = self.default_search_headers
34
+ self._initsession()
35
+ '''_download'''
36
+ @usedownloadheaderscookies
37
+ def _download(self, song_info: SongInfo, request_overrides: dict = None, downloaded_song_infos: list = [], progress: Progress = None, song_progress_id: int = 0):
38
+ if isinstance(song_info.download_url, str): return super()._download(song_info=song_info, request_overrides=request_overrides, downloaded_song_infos=downloaded_song_infos, progress=progress, song_progress_id=song_progress_id)
39
+ request_overrides = request_overrides or {}
40
+ try:
41
+ touchdir(song_info.work_dir)
42
+ total_size, chunk_size, downloaded_size = int(song_info.download_url.filesize), song_info.get('chunk_size', 1024 * 1024), 0
43
+ progress.update(song_progress_id, total=total_size)
44
+ with open(song_info.save_path, "wb") as fp:
45
+ for chunk in song_info.download_url.iterchunks(chunk_size=chunk_size):
46
+ if not chunk: continue
47
+ fp.write(chunk)
48
+ downloaded_size = downloaded_size + len(chunk)
49
+ if total_size > 0:
50
+ downloading_text = "%0.2fMB/%0.2fMB" % (downloaded_size / 1024 / 1024, total_size / 1024 / 1024)
51
+ else:
52
+ progress.update(song_progress_id, total=downloaded_size)
53
+ downloading_text = "%0.2fMB/%0.2fMB" % (downloaded_size / 1024 / 1024, downloaded_size / 1024 / 1024)
54
+ progress.advance(song_progress_id, len(chunk))
55
+ progress.update(song_progress_id, description=f"{self.source}.download >>> {song_info['song_name']} (Downloading: {downloading_text})")
56
+ progress.update(song_progress_id, description=f"{self.source}.download >>> {song_info['song_name']} (Success)")
57
+ downloaded_song_infos.append(SongInfoUtils.fillsongtechinfo(copy.deepcopy(song_info), logger_handle=self.logger_handle, disable_print=self.disable_print))
58
+ except Exception as err:
59
+ progress.update(song_progress_id, description=f"{self.source}.download >>> {song_info['song_name']} (Error: {err})")
60
+ return downloaded_song_infos
61
+ '''_searchwithytsearchanddownloadmp3rapidapi'''
62
+ def _searchwithytsearchanddownloadmp3rapidapi(self, keyword: str, request_overrides: dict = None):
63
+ # init
64
+ request_overrides = request_overrides or {}
65
+ default_headers = {
66
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
67
+ "X-Rapidapi-Host": "youtube-music-api3.p.rapidapi.com", "X-Rapidapi-Key": random.choice(REPAIDAPI_KEYS),
68
+ "Referer": "https://music-download-lake.vercel.app/", "Origin": "https://music-download-lake.vercel.app",
69
+ "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
70
+ }
71
+ # search
72
+ params = {'q': keyword, 'type': 'song', 'limit': self.search_size_per_source}
73
+ for repaidapi_key in REPAIDAPI_KEYS:
74
+ try:
75
+ default_headers['X-Rapidapi-Key'] = repaidapi_key
76
+ resp = self.get(f'https://youtube-music-api3.p.rapidapi.com/search', params=params, headers=default_headers, **request_overrides)
77
+ resp.raise_for_status()
78
+ search_results = resp2json(resp=resp)['result']
79
+ assert len(search_results) > 0
80
+ break
81
+ except:
82
+ continue
83
+ # return
84
+ return search_results
85
+ '''_parsewithytsearchanddownloadmp3rapidapi'''
86
+ def _parsewithytsearchanddownloadmp3rapidapi(self, search_result: dict, request_overrides: dict = None):
87
+ # init
88
+ request_overrides, vid = request_overrides or {}, search_result['videoId']
89
+ default_headers = {
90
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
91
+ "X-Rapidapi-Host": "yt-search-and-download-mp3.p.rapidapi.com", "X-Rapidapi-Key": random.choice(REPAIDAPI_KEYS),
92
+ "Referer": "https://music-download-lake.vercel.app/", "Origin": "https://music-download-lake.vercel.app",
93
+ "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
94
+ }
95
+ # parse
96
+ for repaidapi_key in REPAIDAPI_KEYS:
97
+ try:
98
+ self.default_headers['X-Rapidapi-Key'] = repaidapi_key
99
+ resp = self.get(f'https://yt-search-and-download-mp3.p.rapidapi.com/mp3?url=https://www.youtube.com/watch?v={vid}', headers=default_headers, **request_overrides)
100
+ resp.raise_for_status()
101
+ download_result = resp2json(resp=resp)
102
+ except:
103
+ continue
104
+ if not download_result.get('download'): continue
105
+ song_info = SongInfo(
106
+ source=self.source, download_url=download_result['download'], download_url_status=self.audio_link_tester.test(download_result['download'], request_overrides),
107
+ ext='mp3', raw_data={'search': search_result, 'download': download_result}, duration=search_result.get('duration')
108
+ )
109
+ song_info.download_url_status['probe_status'] = self.audio_link_tester.probe(song_info.download_url, request_overrides)
110
+ ext, file_size = song_info.download_url_status['probe_status']['ext'], song_info.download_url_status['probe_status']['file_size']
111
+ if file_size and file_size != 'NULL': song_info.file_size = file_size
112
+ if not song_info.file_size: song_info.file_size = 'NULL'
113
+ if ext and ext != 'NULL': song_info.ext = ext
114
+ if song_info.with_valid_download_url: break
115
+ # return
116
+ return song_info
117
+ '''_parsvidewithmp3youtube'''
118
+ def _parsvidewithmp3youtube(self, search_result: dict, request_overrides: dict = None):
119
+ # init
120
+ request_overrides, vid = request_overrides or {}, search_result['videoId']
121
+ request_headers = {"Content-Type": "application/json", "Origin": "https://iframe.y2meta-uk.com", "Accept": "*/*"}
122
+ download_headers = {"Content-Type": "application/json", "Origin": "https://iframe.y2meta-uk.com", "Accept": "*/*", "Referer": "https://iframe.y2meta-uk.com/"}
123
+ request_overrides_test = copy.deepcopy(request_overrides)
124
+ request_overrides_test['headers'] = download_headers
125
+ # get key
126
+ resp = self.get('https://api.mp3youtube.cc/v2/sanity/key', headers=request_headers, **request_overrides)
127
+ resp.raise_for_status()
128
+ # parse
129
+ request_headers.update(dict(key=resp2json(resp)['key']))
130
+ for quality in ['320', '256', '192', '128', '64']:
131
+ audio_payload = {"link": f"https://youtu.be/{vid}", "format": "mp3", "audioBitrate": quality, "videoQuality": "720", "vCodec": "h264"}
132
+ try:
133
+ resp = self.post('https://api.mp3youtube.cc/v2/converter', json=audio_payload, headers=request_headers, **request_overrides)
134
+ resp.raise_for_status()
135
+ except:
136
+ continue
137
+ download_result = resp2json(resp=resp)
138
+ if not download_result.get('url'): continue
139
+ song_info = SongInfo(
140
+ source=self.source, default_download_headers=download_headers, download_url=download_result['url'],
141
+ download_url_status=self.audio_link_tester.test(download_result['url'], request_overrides_test), ext='mp3',
142
+ raw_data={'search': search_result, 'download': download_result}, duration=search_result.get('duration')
143
+ )
144
+ song_info.download_url_status['probe_status'] = self.audio_link_tester.probe(song_info.download_url, request_overrides_test)
145
+ ext, file_size = song_info.download_url_status['probe_status']['ext'], song_info.download_url_status['probe_status']['file_size']
146
+ if file_size and file_size != 'NULL': song_info.file_size = file_size
147
+ if not song_info.file_size: song_info.file_size = 'NULL'
148
+ if ext and ext != 'NULL': song_info.ext = ext
149
+ if song_info.with_valid_download_url: break
150
+ # return
151
+ return song_info
152
+ '''_constructsearchurls'''
153
+ def _constructsearchurls(self, keyword: str, rule: dict = None, request_overrides: dict = None):
154
+ # init
155
+ rule, request_overrides = rule or {}, request_overrides or {}
156
+ # adapt ytmusicapi to conduct music file search
157
+ proxies = None
158
+ if self.proxied_session_client is not None: proxies = self.proxied_session_client.getrandomproxy()
159
+ ytmusic_api = YTMusic(
160
+ auth=rule.get('auth', None), user=rule.get('user', None), requests_session=None, proxies=request_overrides.get('proxies', None) or proxies,
161
+ language=rule.get('language', 'en'), location=rule.get('location', ''), oauth_credentials=rule.get('oauth_credentials', ''),
162
+ )
163
+ # search rules
164
+ search_rule = {
165
+ 'query': keyword, 'filter': rule.get('filter', None), 'scope': rule.get('scope', None), 'limit': self.search_size_per_source,
166
+ 'ignore_spelling': rule.get('ignore_spelling', False)
167
+ }
168
+ search_urls = [{'ytmusic_api': ytmusic_api, 'search_rule': search_rule}]
169
+ self.search_size_per_page = self.search_size_per_source
170
+ # return
171
+ return search_urls
172
+ '''_search'''
173
+ @usesearchheaderscookies
174
+ def _search(self, keyword: str = '', search_url: dict = {}, request_overrides: dict = None, song_infos: list = [], progress: Progress = None, progress_id: int = 0):
175
+ # init
176
+ request_overrides = request_overrides or {}
177
+ search_meta = copy.deepcopy(search_url)
178
+ ytmusic_api, search_rule = search_meta['ytmusic_api'], search_meta['search_rule']
179
+ # successful
180
+ try:
181
+ # --search results, if search scale not very large, try rapid api first
182
+ if self.search_size_per_source <= 10:
183
+ try:
184
+ search_results = self._searchwithytsearchanddownloadmp3rapidapi(keyword=keyword, request_overrides=request_overrides)
185
+ if len(search_results) == 0: raise
186
+ except:
187
+ search_results = ytmusic_api.search(**search_rule)
188
+ search_results = [s for s in search_results if s['resultType'] in ['song']]
189
+ else:
190
+ search_results = ytmusic_api.search(**search_rule)
191
+ search_results = [s for s in search_results if s['resultType'] in ['song']]
192
+ for search_result in search_results:
193
+ # --download results
194
+ if not isinstance(search_result, dict) or 'videoId' not in search_result:
195
+ continue
196
+ song_info, ump_success = SongInfo(source=self.source), False
197
+ # ----download with UMP
198
+ try:
199
+ download_url = YouTube(video_id=search_result['videoId']).streams.getaudioonly()
200
+ song_info = SongInfo(
201
+ source=self.source, download_url=download_url, download_url_status={'ok': True}, file_size=byte2mb(download_url.filesize),
202
+ file_size_bytes=download_url.filesize, duration_s=int(download_url.durationMs) // 1000, duration=seconds2hms(int(download_url.durationMs) // 1000),
203
+ ext='mp3', raw_data={'search': search_result, 'download': {}},
204
+ )
205
+ if song_info.file_size_bytes < 100: raise
206
+ ump_success = True
207
+ except:
208
+ ump_success = False
209
+ pass
210
+ # ----parse with _parsvidewithmp3youtube
211
+ if not ump_success:
212
+ song_info = self._parsvidewithmp3youtube(search_result=search_result, request_overrides=request_overrides)
213
+ # ----parse with _parsewithytsearchanddownloadmp3rapidapi
214
+ if (not ump_success) and (not song_info.with_valid_download_url):
215
+ song_info = self._parsewithytsearchanddownloadmp3rapidapi(search_result=search_result, request_overrides=request_overrides)
216
+ # ----parse more information
217
+ format_duration = lambda d: "{:02}:{:02}:{:02}".format(*([0] * (3 - len(d.split(":"))) + list(map(int, d.split(":")))))
218
+ song_info.duration = format_duration(song_info.duration)
219
+ singers = ', '.join([singer.get('name', 'NULL') for singer in search_result.get('artists', [])]) if search_result.get('artists') else search_result.get('author', 'NULL')
220
+ song_info.update(dict(
221
+ singers=legalizestring(singers, replace_null_string='NULL'), song_name=legalizestring(search_result.get('title', 'NULL'), replace_null_string='NULL'),
222
+ album=legalizestring(search_result.get('album', 'NULL'), replace_null_string='NULL'), identifier=search_result['videoId'],
223
+ ))
224
+ # --lyric results
225
+ lyric_result, lyric = dict(), 'NULL'
226
+ song_info.raw_data['lyric'] = lyric_result
227
+ song_info.lyric = lyric
228
+ # --append to song_infos
229
+ song_infos.append(song_info)
230
+ # --judgement for search_size
231
+ if self.strict_limit_search_size_per_page and len(song_infos) >= self.search_size_per_page: break
232
+ # --update progress
233
+ progress.update(progress_id, description=f"{self.source}.search >>> {search_url} (Success)")
234
+ # failure
235
+ except Exception as err:
236
+ progress.update(progress_id, description=f"{self.source}.search >>> {search_url} (Error: {err})")
237
+ # return
238
+ return song_infos
@@ -1,4 +1,12 @@
1
- '''import all'''
2
- from .downloader import Downloader
3
- from .logger import Logger, printTable
4
- from .misc import checkDir, seconds2hms, loadConfig, filterBadCharacter
1
+ '''initialize'''
2
+ from .data import SongInfo
3
+ from .lyric import WhisperLRC
4
+ from .quarkparser import QuarkParser
5
+ from .songinfoutils import SongInfoUtils
6
+ from .modulebuilder import BaseModuleBuilder
7
+ from .logger import LoggerHandle, colorize, printtable, printfullline, smarttrunctable
8
+ from .misc import (
9
+ AudioLinkTester, legalizestring, touchdir, seconds2hms, byte2mb, cachecookies, resp2json, isvalidresp, safeextractfromdict, replacefile,
10
+ usedownloadheaderscookies, useparseheaderscookies, usesearchheaderscookies, cookies2dict, cookies2string, estimatedurationwithfilesizebr,
11
+ estimatedurationwithfilelink
12
+ )