Skip to content

Commit 0abd831

Browse files
xxynetCopilot
andcommitted
feat: add xcvts_api
Co-authored-by: Copilot <copilot@github.com>
1 parent 6060b83 commit 0abd831

7 files changed

Lines changed: 149 additions & 11 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ build/
66
venv/
77
Thumbs.db
88
.env
9-
main.spec
9+
main.spec
10+
cookie.txt
11+
/music/

api.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,102 @@ def get_song_info(self, song_id, quality=4):
143143
return None
144144

145145

146+
class XcvtsApi:
147+
"""小尘API - 波点音乐点歌接口,作为备用音源"""
148+
149+
def __init__(self):
150+
self.desc = "小尘API(波点音乐)"
151+
self.base_url = "https://api.xcvts.cn/api/music/bdyy"
152+
self.default_quality = "320kmp3"
153+
154+
def search_and_get_url(self, keyword, quality=None):
155+
"""
156+
通过关键词搜索歌曲并获取音频URL
157+
158+
参数:
159+
keyword: 搜索关键词(歌名/歌手名)
160+
quality: 音质,可选值: 20201kmflac, 2000kflac, 320kmp3, 128kmp3, 48kaac, 192kogg, 100kogg
161+
返回:
162+
dict: {'name', 'artist', 'cover', 'play_url', 'lrc'} 或 None
163+
"""
164+
params = {
165+
'msg': keyword,
166+
'n': 1,
167+
'type': 'json',
168+
}
169+
if quality:
170+
params['br'] = quality
171+
else:
172+
params['br'] = self.default_quality
173+
174+
try:
175+
response = requests.get(self.base_url, params=params, timeout=15)
176+
response.raise_for_status()
177+
data = response.json()
178+
179+
if data.get('code') != 200:
180+
print(f"小尘API错误: {data}")
181+
return None
182+
183+
song_data = data.get('data')
184+
if not song_data or not song_data.get('play_url'):
185+
print("小尘API: 未获取到有效的音频URL")
186+
return None
187+
188+
return song_data
189+
190+
except requests.exceptions.RequestException as e:
191+
print(f"小尘API请求失败: {e}")
192+
return None
193+
194+
def get_lyrics(self, keyword):
195+
"""
196+
通过关键词获取歌词
197+
198+
参数:
199+
keyword: 搜索关键词
200+
返回:
201+
str: 歌词文本,失败返回 None
202+
"""
203+
params = {
204+
'msg': keyword,
205+
'n': 1,
206+
'type': 'lyric',
207+
}
208+
try:
209+
response = requests.get(self.base_url, params=params, timeout=15)
210+
response.raise_for_status()
211+
return response.text
212+
except requests.exceptions.RequestException as e:
213+
print(f"小尘API歌词请求失败: {e}")
214+
return None
215+
216+
def get_mp3_data(self, keyword, quality=None):
217+
"""
218+
通过关键词搜索并下载音频数据
219+
220+
参数:
221+
keyword: 搜索关键词
222+
quality: 音质
223+
返回:
224+
tuple: (is_success, audio_response, song_data_or_none)
225+
"""
226+
song_data = self.search_and_get_url(keyword, quality)
227+
if not song_data:
228+
return False, None, None
229+
230+
audio_url = song_data.get('play_url')
231+
try:
232+
audio_response = requests.get(audio_url, timeout=30)
233+
content_type = audio_response.headers.get('Content-Type', '')
234+
if 'text/html' in content_type or audio_response.status_code != 200:
235+
return False, None, None
236+
return True, audio_response, song_data
237+
except requests.exceptions.RequestException as e:
238+
print(f"小尘API下载失败: {e}")
239+
return False, None, None
240+
241+
146242
if __name__ == '__main__':
147243
api = NCMApi()
148244
# playlist_id = input("Playlist id: ")

config.ini

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[output]
22

3-
#设置歌单输出路径,如果为空则默认为程序所在目录(路径无需引号包裹)
3+
#设置歌单输出路径,如果为空则默认为程序所在目录的music目录下(路径无需引号包裹)
44
path =
55

66
#默认ncm文件查找路径
@@ -10,7 +10,7 @@ ncm_path = E:/CloudMusic/VipSongsDownload
1010
filename = 0
1111

1212
#是否下载歌词 1 -> 下载LRC歌词文件 2 -> 内嵌歌词 0 -> False
13-
lrc = 0
13+
lrc = 1
1414

1515
[settings]
1616

@@ -20,4 +20,11 @@ detect-update = 1
2020
[v_key_api]
2121

2222
# 是否在下载歌曲失败时尝试使用第三方API下载,设置为 true 启用
23-
enabled = false
23+
enabled = true
24+
25+
[xcvts_api]
26+
27+
# 是否在下载歌曲失败时尝试使用小尘API(波点音乐)下载,设置为 true 启用
28+
# 音质选项: 20201kmflac, 2000kflac, 320kmp3, 128kmp3, 48kaac, 192kogg, 100kogg
29+
enabled = true
30+
quality = 320kmp3

config.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import configparser
22

3-
VERSION = 'v2.6.0'
3+
VERSION = 'v2.7.0'
44

55
config_file = '''[output]
66
7-
#设置歌单输出路径,如果为空则默认为程序所在目录(路径无需引号包裹)
7+
#设置歌单输出路径,如果为空则默认为程序所在目录的music目录下(路径无需引号包裹)
88
path =
99
1010
#默认ncm文件查找路径
@@ -25,4 +25,11 @@
2525
2626
# 是否在下载歌曲失败时尝试使用第三方API下载,设置为 true 启用
2727
enabled = false
28+
29+
[xcvts_api]
30+
31+
# 是否在下载歌曲失败时尝试使用小尘API(波点音乐)下载,设置为 true 启用
32+
# 音质选项: 20201kmflac, 2000kflac, 320kmp3, 128kmp3, 48kaac, 192kogg, 100kogg
33+
enabled = false
34+
quality = 320kmp3
2835
'''

cookie.txt

Whitespace-only changes.

main.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
# 请打开config.ini配置文件配置相应信息
55

6-
# pyinstaller -F main.py -i music.ico
6+
# pyinstaller main.spec
77

88
from rich.panel import Panel
99
from rich.table import Table
@@ -12,7 +12,7 @@
1212
import metadata
1313

1414
from utils import *
15-
from api import NCMApi, VKeyApi
15+
from api import NCMApi, VKeyApi, XcvtsApi
1616
from ncmdump import dump as dump_ncm
1717

1818
proj_logo = r"""
@@ -79,7 +79,7 @@ def _download_audio(self):
7979

8080
# vkey api
8181
if global_config.v_key_enabled:
82-
formatted_print('i', "尝试使用第三方API解析...")
82+
formatted_print('i', "尝试使用落月API解析...")
8383
song_info = v_key_api.get_song_info(self.song_id)
8484
if song_info:
8585
audio_url = song_info.get('url')
@@ -91,6 +91,25 @@ def _download_audio(self):
9191
# download lyrics
9292
self._download_lyrics()
9393

94+
# xcvts api (小尘API - 波点音乐)
95+
if not self.success and global_config.xcvts_enabled:
96+
formatted_print('i', "尝试使用小尘API(波点音乐)解析...")
97+
keyword = f"{self.name} - {self.artists_str}"
98+
success, audio_resp, song_data = xcvts_api.get_mp3_data(keyword, global_config.xcvts_quality)
99+
if success and audio_resp:
100+
with open(f"{self.full_path}.mp3", "wb") as file:
101+
file.write(audio_resp.content)
102+
self.success = True
103+
# 尝试从 xcvts 获取歌词(如果官方歌词未获取到)
104+
if global_config.lrc_enabled != "0" and (not self.olrc or self.olrc.strip() == ''):
105+
try:
106+
xcvts_lrc = xcvts_api.get_lyrics(keyword)
107+
if xcvts_lrc:
108+
self.olrc = xcvts_lrc
109+
except Exception:
110+
pass
111+
self._download_lyrics()
112+
94113
def _download_lyrics(self):
95114
if global_config.lrc_enabled == '1':
96115
merged_lrc = metadata.merge_lrc(self.olrc, self.tlrc)
@@ -119,7 +138,7 @@ def download(self):
119138

120139
class Playlist:
121140

122-
def __init__(self, playlist_id, download_path):
141+
def __init__(self, playlist_id: int, download_path: str):
123142
self.playlist_id: int = playlist_id
124143
self.download_path: str = download_path
125144
self.playlist_name: str
@@ -331,6 +350,7 @@ def main():
331350

332351
api = NCMApi(global_config.cookie)
333352
v_key_api = VKeyApi()
353+
xcvts_api = XcvtsApi()
334354

335355
try:
336356
main()

utils.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ def __init__(self):
9595

9696
self.v_key_enabled: Union[bool, None] = None
9797

98+
self.xcvts_enabled: Union[bool, None] = None
99+
self.xcvts_quality: Union[str, None] = None
100+
98101
self._check_config_file()
99102
self._get_config()
100103

@@ -126,7 +129,7 @@ def _get_config(self):
126129
config.read('config.ini', encoding='utf-8')
127130
path = config.get('output', 'path')
128131
if path == '':
129-
self.music_path = os.getcwd()
132+
self.music_path = f"{os.getcwd()}/music"
130133

131134
self.ncm_path = config.get('output', 'ncm_path')
132135

@@ -138,6 +141,9 @@ def _get_config(self):
138141

139142
self.v_key_enabled = True if config.get('v_key_api', 'enabled') == "true" else False
140143

144+
self.xcvts_enabled = True if config.get('xcvts_api', 'enabled') == "true" else False
145+
self.xcvts_quality = config.get('xcvts_api', 'quality')
146+
141147
with open("cookie.txt", "r") as cookie_file:
142148
self.cookie = cookie_file.read().strip()
143149

0 commit comments

Comments
 (0)