实现一个音乐下载小工具,搜索歌曲名或演唱者名找到相关联的几首歌曲,选择序号,下载歌曲。支持下载歌词、选择下载路径、选择下载平台。
通过命令行在线搜索你喜欢的音乐,下载并试听。
这里是它的 Github 地址 Github:mxget,有详细的使用说明,我这里就不多介绍了。不过这个 repository 是它的 Go 语言实现。至于 Python 版本的 mxget,可以去 Github 上找一找,看看是否有人 forked。
至于下载,可以直接通过命令 pip install mxget
安装包,如果无法找到包,可以尝试切换镜像源(如:清华大学、阿里云、豆瓣等)安装。
Pyside6 是一个开发 GUI 的库,基于 Qt 框架,与 PyQt 同出一个 API 接口,用法几乎一致。以下是 Qt for Python 的 官方文档: Qt for Python。
首先需要完成的是界面设计。如图:
第一行,输入框填入搜索的关键词,button 用于搜索。第二行,显示框输出搜索到的歌曲信息。第三行,下载完成的输出提示。第四行,选择对应的歌曲 id,是否下载歌词,以及下载平台。第五行,选择歌曲下载的路径。第六行,下载按钮。
接下来就是具体的代码实现。如下:
import os
import re
import sys
import subprocess
from PySide6 import QtWidgets, QtGui
from PySide6.QtWidgets import QFileDialog
FONT_SIZE = 'font: 12px;'
BASE_DIR = os.path.dirname(__file__)
class Window(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.init_ui(self)
self.song_id_dict = {} # 歌曲编号与对应的 id
self.song_name_dict = {} # 歌曲编号与对应的 name
self.song_name_and_singer_dict = {} # 歌曲编号与对应的 name 和 singer
def init_ui(self, window):
window.setWindowTitle('DownloadMusic')
window.setFixedSize(660, 640)
window.setWindowIcon(QtGui.QIcon(BASE_DIR + '/mico.ico'))
# row 1
song_title_label = QtWidgets.QLabel(window) # 标题
song_title_label.setText('Song Title')
song_title_label.setGeometry(10, 10, 90, 25)
song_title_label.setStyleSheet(FONT_SIZE)
self.song_title_editor = QtWidgets.QLineEdit(window) # 输入框
self.song_title_editor.setGeometry(100, 10, 450, 25)
self.song_title_editor.setStyleSheet(FONT_SIZE)
query_btn = QtWidgets.QPushButton(window) # 查询
query_btn.setText('Query')
query_btn.setGeometry(560, 10, 70, 25)
query_btn.setStyleSheet(FONT_SIZE)
query_btn.clicked.connect(lambda: self.search())
# row 2
song_list_label = QtWidgets.QLabel(window)
song_list_label.setText('Song List')
song_list_label.setGeometry(10, 150, 90, 25)
song_list_label.setStyleSheet(FONT_SIZE)
self.show_song_list = QtWidgets.QTextBrowser(window) # 显示歌曲列表
self.show_song_list.setGeometry(100, 45, 450, 300)
self.show_song_list.setStyleSheet(FONT_SIZE)
# row 3
download_label = QtWidgets.QLabel(window)
download_label.setText('Download Msg')
download_label.setGeometry(10, 400, 90, 25)
download_label.setStyleSheet(FONT_SIZE)
self.show_download_msg = QtWidgets.QTextBrowser(window) # 下载信息列表
self.show_download_msg.setGeometry(100, 350, 450, 140)
self.show_download_msg.setStyleSheet(FONT_SIZE)
# row 4
self.song_numbers = tuple(str(i).zfill(2) for i in range(1, 10))
self.song_id_list = QtWidgets.QComboBox(window)
self.song_id_list.addItems(self.song_numbers)
self.song_id_list.setCurrentIndex(0)
self.song_id_list.setGeometry(100, 500, 50, 25)
self.song_id_list.setStyleSheet(FONT_SIZE)
self.is_download_lyric_cbx = QtWidgets.QCheckBox(window)
self.is_download_lyric_cbx.setText('Download Lyric')
self.is_download_lyric_cbx.setGeometry(200, 500, 140, 25)
self.is_download_lyric_cbx.setStyleSheet('background: rgba(216,216,216,.3);'
'border: 1px solid rgba(112,112,112,0.8);'
'border-radius: 2px;'
'padding-left: 5px;'
'font: 14px')
self.music_platforms_dict = {'NeteaseCloud': 'nc', 'QQ': 'qq', 'KuWo': 'kw', "MiGu": 'mg'}
self.music_platforms = ('NeteaseCloud', 'QQ', 'KuWo', 'MiGu')
self.change_music_platform = QtWidgets.QComboBox(window)
self.change_music_platform.addItems(self.music_platforms)
self.change_music_platform.setCurrentIndex(0)
self.change_music_platform.setGeometry(350, 500, 110, 25)
self.change_music_platform.setStyleSheet(FONT_SIZE)
os.system(r'mxget config --from ' + 'nc')
# row 5
self.select_path_btn = QtWidgets.QPushButton(window)
self.select_path_btn.setText('Select Path')
self.select_path_btn.setGeometry(100, 535, 90, 25)
self.select_path_btn.setStyleSheet(FONT_SIZE)
self.select_path_btn.clicked.connect(lambda: self.set_download_path())
default_save_to = (os.path.expanduser('~') + '\\Downloads')
self.save_path = QtWidgets.QTextBrowser(window)
self.save_path.setGeometry(200, 535, 350, 27)
self.save_path.setStyleSheet(FONT_SIZE)
self.save_path.setText(default_save_to)
os.system(r'mxget config --dir ' + default_save_to)
# row 5
self.download_btn = QtWidgets.QPushButton(window)
self.download_btn.setText('Download')
self.download_btn.setGeometry(100, 570, 90, 25)
self.download_btn.setStyleSheet(FONT_SIZE)
self.download_btn.clicked.connect(lambda: self.download())
def set_download_path(self):
""" 选择下载路径 """
dialog = QFileDialog()
dialog.setFileMode(QFileDialog.Directory)
path = dialog.getExistingDirectory(self, 'select path', 'C:\\', QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)
if path:
self.save_path.setText(path.replace("/", os.sep))
os.system(f'mxget config --dir {path}')
def set_music_platform(self):
""" 设置下载平台 """
platform = self.music_platforms[self.change_music_platform.currentIndex()]
if not platform == 'NeteaseCloud':
platform_no = self.music_platforms_dict[platform]
os.system(f'mxget config --from {platform_no}')
def search(self):
""" 查询关键词歌曲 """
self.set_music_platform()
song_title = self.song_title_editor.text() # 获取输入的歌曲名
if not song_title: # 没有输入歌名就查询,会出现弹窗警告
info_msg_box = QtWidgets.QMessageBox()
info_msg_box.information(QtWidgets.QMainWindow(), 'Info', 'Please input song title...')
return
try:
songs_data = subprocess.run(f'mxget search -k "{song_title}"', stdout=subprocess.PIPE, encoding='gbk')
songs_list = re.findall(r'\[\d{2}.*', songs_data.stdout.replace(' ', ' ').replace('/nbsp;', ' ').replace('/', '&').replace(''', "' "))[:len(self.song_numbers)]
if songs_list:
display_songs = []
for song in songs_list:
single_song_info = song.strip().split('-')
self.song_id_dict[single_song_info[0][1:3]] = single_song_info[-1] # 序号和id对应
self.song_name_dict[single_song_info[0][1:3]] = single_song_info[0] # 序号和name对应
self.song_name_and_singer_dict[single_song_info[0][1:3]] = (single_song_info[1]+ '-' +single_song_info[0][4:]).strip() + '.mp3'
display_songs.append(' - '.join(single_song_info[:-1]))
self.show_song_list.setText('\n' + '\n\n'.join(display_songs[:len(self.song_numbers)]))
else:
self.song_id_dict = {}
self.show_song_list.setText(f'\n Not found songs about {song_title}...')
except Exception as e:
self.show_song_list.setText(f'\n Not found songs about {song_title}....')
raise e
def download(self):
""" 下载歌曲 """
self.set_music_platform()
song_number = self.song_numbers[self.song_id_list.currentIndex()]
song_id = self.song_id_dict.get(song_number, False)
if song_id:
download_lyric = ''
if self.is_download_lyric_cbx.isChecked():
download_lyric = '--lyric --force'
subprocess.run(f'mxget song --id {song_id} {download_lyric}') # 同时下载歌词,格式 lrc
config_data = subprocess.run('mxget config --show', stdout=subprocess.PIPE, encoding='utf8')
download_dir = re.search(r'->.*?\n', config_data.stdout).group().replace('->', '').replace('music', '').strip()
if os.path.exists(os.path.join(download_dir, self.song_name_and_singer_dict[song_number])):
self.show_download_msg.insertPlainText('\n' + self.song_name_dict[song_number] + ' - ' + 'Downloaded...\n')
else:
self.show_download_msg.insertPlainText('\n' + self.song_name_dict[song_number] + ' - ' + 'Unavailable...\n')
self.show_download_msg.moveCursor(QtGui.QTextCursor.Start)
else:
info_msg_box = QtWidgets.QMessageBox()
info_msg_box.information(QtWidgets.QMainWindow(), 'Info', 'Please get valid song info...')
if __name__ == "__main__":
app = QtWidgets.QApplication()
window = Window()
window.show()
sys.exit(app.exec())
对于代码说明一下,mxget 是个命令行工具,这里二次封装成 GUI 工具。代码中需要执行 mxget 命令,直接想到的就是 os.system(),不过 os.system() 没有返回值,而代码需要返回搜索信息,筛选后展示,所以无法使用 os.system()。调查后,锁定 subprocess 模块,Python 3.5 以后,可以选择 subprocess 模块中的 run 方法来执行命令,似乎更简单一些。
以下是效果图:
本文介绍了,如何使用 PySide6 创建一个 GUI 应用,实现 mxget 下载单首音乐界面化。所以代码还有很多的优化空间,比如搜索后,增加选择展示多少首歌曲的切换功能,增加一次下载多首歌曲的功能,以及下载专辑的功能等等。在写作此文章之时,存在部分平台不可用,存在部分平台的音乐不可下载。如,作者之前用的 kuwo,现部分音乐不可下载。请读者自行尝试。
源代码在此处:Download Music,此外还有其他几个版本,基于 tkinter 和 PySide2。