前言
前段时间写的Python自制一款炫酷音乐播放器,有不少小伙伴私信我,对播放器提了不少改进建议,让我完善播放器的功能。今天音乐播放器2.0版本完成了,大家一起来看看是如何用python自制一款炫酷的音乐播放器的吧~
首先我们还是一起来看看实现的音乐播放器最终效果如何:
由于之前已经介绍过了音乐播放器V1.0版本的大致功能,如果有不了解的小伙伴可以先去看看V1.0版本的音乐播放器大致介绍。【python自制一款炫酷音乐播放器,想听啥随便搜!】下面我们就介绍这个音乐播放器V2.0版本新加的部分功能制作过程。
直接跳到文末获取源码及exe打包程序。
20000
一、核心功能设计
在之前的V1.0版本中,我们已经完成了播放器UI界面的设计,对播放器的画面布局进行排版设计;其次音乐播放器的最重要的核心功能根据关键字搜索自动爬取音乐并且进行音乐播放已经完成了。当然也完成了一些播放器常见的附加功能,如播放方式列表循环、单曲循环、随机播放;当前上一首下一首播放;播放暂停开始;音量增加减少等。
V2.0版本中,我们主要增加了以下几个核心功能:
- 对UI排版布局增加子列表页面,主要包括最近播放、喜爱的歌、歌词显示、本地音乐播放4个子列表页面
- 音乐本地下载及添加我喜爱的歌曲功能
- 我喜爱的歌内容保存及初始化读取我喜爱的歌列表显示,喜爱歌曲列表播放功能
- 当前播放歌曲歌词读取显示功能
- 读取播放本地歌曲功能
二、实现步骤
1. UI排版布局
V1.0版本中,我们已经对排版布局进行了初步设计。头部主要包括关键字搜索和音乐来源选择,以及窗体最小化,最大化,关闭功能;底部主要来显示当前播放音乐,播放进度条,音量控制,上一首/下一首,暂停/开始,播放方式等附加功能;中间主体包含左右两侧,左侧用来显示播放音乐封面图,右侧用来进行音乐列表显示。
V2.0版本中我们将新增的几个子列表页面放在界面中间主体右侧,叠加在音乐搜索显示列表页。这里我们使用的还是pyqt5。添加子列表页面核心设计代码如下:
def init_ui(self):self.main_layout.addWidget(self.up_widget, 0, 0, 1, 110)self.main_layout.addWidget(self.left_widget, 1, 0, 90, 20)self.main_layout.addWidget(self.right_widget, 1, 20, 90, 90) # 22右侧部件在第0行第3列,占8行9列self.main_layout.addWidget(self.down_widget, 100, 0, 10, 110)self.main_layout.addWidget(self.close_widget, 0, 105, 1, 5) # 左侧部件在第0行第0列,占1行3列self.down_layout.addWidget(self.label, 1, 0, 1, 1)self.setCentralWidget(self.main_widget) # 设置窗口主部件self.tabWidget = QTabWidget(self)self.tabWidget.setGeometry(QRect(33, 20, 716, 471))self.tab = QWidget()self.tab.setObjectName("tab")self.tab_layout = QGridLayout()self.tab.setLayout(self.tab_layout)self.listwidget = QListWidget(self.tab)self.label361 = QLabel(self)self.label361.setText("")self.label361.setStyleSheet("color:#6DDF6D")self.tab_layout.addWidget(self.label361, 0, 1, 1, 1)self.button_1235 = QPushButton(icon('fa.download', color='#D0D0D0', font=24), "下载全部")self.button_1235.clicked.connect(self.downloadalls)self.button_1235.setStyleSheet('''QPushButton{background:#222225;border-radius:5px;}QPushButton:hover{background:#303030;}''')self.tab_layout.addWidget(self.button_1235, 0, 2, 1, 1)self.button_1236 = QPushButton(icon('fa.trash-o', color='#D0D0D0', font=24), "清空列表")self.button_1236.clicked.connect(self.dell)self.button_1236.setStyleSheet('''QPushButton{background:#222225;border-radius:5px;}QPushButton:hover{background:#303030;}''')self.tab_layout.addWidget(self.button_1236, 0, 3, 1, 1)self.listwidget.doubleClicked.connect(lambda: self.change_func(self.listwidget))self.listwidget.setContextMenuPolicy(Qt.CustomContextMenu)self.listwidget.customContextMenuRequested[QPoint].connect(self.myListWidgetContext)self.listwidget.setStyleSheet(self.css)self.listwidget.setObjectName("listWidget")self.tab_layout.addWidget(self.listwidget, 1, 0, 1, 4)self.tabWidget.addTab(self.tab, " 搜索页 ")self.tab2 = QWidget()self.tab2.setObjectName("tab")self.tab2_layout = QGridLayout()self.tab2.setLayout(self.tab2_layout)self.listwidget2 = QListWidget(self.tab2)self.listwidget2.doubleClicked.connect(lambda: self.change_funcse(self.listwidget2))self.listwidget2.setContextMenuPolicy(Qt.CustomContextMenu)self.listwidget2.customContextMenuRequested[QPoint].connect(self.myListWidgetContext2)self.listwidget2.setStyleSheet(self.css)self.listwidget2.setObjectName("listWidget2")self.listwidget2.setContextMenuPolicy(3)self.tab2_layout.addWidget(self.listwidget2, 0, 0, 1, 1)self.tabWidget.addTab(self.tab2, " 最近播放 ")self.tab3 = QWidget()self.tab3.setObjectName("tab")self.tab3_layout = QGridLayout()self.tab3.setLayout(self.tab3_layout)self.label223 = QLabel(self)# self.label5.setScaledContents(True)pix_img = QPixmap(str(data + '/backdown.png'))pix = pix_img.scaled(100, 100, Qt.KeepAspectRatio)self.label223.setPixmap(pix)# self.label5.setMaximumSize(1,1)self.tab3_layout.addWidget(self.label223, 0, 0, 1, 1)self.button_1237 = QPushButton(icon('fa.play', color='#FFFFFF', font=24), "播放全部")self.button_1237.clicked.connect(self.allplaylove)self.button_1237.setStyleSheet('''QPushButton{background:#EC4141;border-radius:5px;}QPushButton:hover{background:#E92121;}''')self.tab3_layout.addWidget(self.button_1237, 0, 1, 1, 1)self.button_1235 = QPushButton(icon('fa.download', color='#D0D0D0', font=24), "下载全部")self.button_1235.clicked.connect(self.downloadalllove)self.button_1235.setStyleSheet('''QPushButton{background:#222225;border-radius:5px;}QPushButton:hover{background:#303030;}''')self.tab3_layout.addWidget(self.button_1235, 0, 2, 1, 1)self.button_1236 = QPushButton(icon('fa.trash-o', color='#D0D0D0', font=24), "清空列表")self.button_1236.clicked.connect(self.delove)self.button_1236.setStyleSheet('''QPushButton{background:#222225;border-radius:5px;}QPushButton:hover{background:#303030;}''')self.tab3_layout.addWidget(self.button_1236, 0, 3, 1, 1)self.listwidget3 = QListWidget(self.tab3)self.listwidget3.doubleClicked.connect(lambda: self.change_funclove(self.listwidget3))self.listwidget3.setContextMenuPolicy(Qt.CustomContextMenu) self.listwidget3.customContextMenuRequested[QPoint].connect(self.myListWidgetContext3)self.listwidget3.setStyleSheet(self.css)self.listwidget3.setObjectName("listWidget3")self.tab3_layout.addWidget(self.listwidget3, 1, 0, 1, 4)self.tabWidget.addTab(self.tab3, " 喜爱的歌 ")self.tab4 = QWidget()self.tab4.setObjectName("tab")self.tab4_layout = QGridLayout()self.tab4.setLayout(self.tab4_layout)self.listwidget4 = QListWidget(self.tab4)# self.listwidget4.doubleClicked.connect(lambda: self.change_func(self.listwidget))self.listwidget4.setStyleSheet(self.css)self.listwidget4.setObjectName("listWidget4")self.tab4_layout.addWidget(self.listwidget4, 0, 0, 1, 1)self.tabWidget.addTab(self.tab4, " 歌词 ")self.tab5 = QWidget()self.tab5.setObjectName("tab5")self.tab5_layout = QGridLayout()self.tab5.setLayout(self.tab5_layout)self.listwidget5 = QListWidget(self.tab5)self.listwidget5.doubleClicked.connect(lambda: self.change(self.listwidget5))self.listwidget5.setContextMenuPolicy(Qt.CustomContextMenu)self.listwidget5.customContextMenuRequested[QPoint].connect(self.myListWidgetContext5)self.button_12351 = QPushButton(icon('fa.download', color='#D0D0D0', font=24), "添加目录")self.button_12351.clicked.connect(self.add)self.button_12351.setStyleSheet('''QPushButton{background:#222225;border-radius:5px;}QPushButton:hover{background:#303030;}''')self.tab5_layout.addWidget(self.button_12351, 0, 2, 1, 1)self.button_12361 = QPushButton(icon('fa.trash-o', color='#D0D0D0', font=24), "清空列表")self.button_12361.clicked.connect(self.dellocal)self.button_12361.setStyleSheet('''QPushButton{background:#222225;border-radius:5px;}QPushButton:hover{background:#303030;}''')self.tab5_layout.addWidget(self.button_12361, 0, 3, 1, 1)self.listwidget5.setStyleSheet(self.css)self.listwidget5.setObjectName("listWidget5")self.tab5_layout.addWidget(self.listwidget5, 1, 0, 1, 4)self.tabWidget.addTab(self.tab5, " 本地歌曲 ")self.right_layout.addWidget(self.tabWidget, 3, 0, 100, 90)
UI界面布局实现效果如下:
2. 音乐本地下载及添加我喜爱的歌曲
我们根据V1.0版本完成的,输入的关键字和选择音乐来源进行音乐爬取,通过多线程,将歌曲、歌手、歌曲url地址全都获取;并将这些爬取的音乐数据列表显示到搜索页面中。双击列表页面中某一首歌曲,即可实现音乐播放功能。
V2.0版本中,我们添加了对当前播放音乐的本地下载、搜索页音乐列表一键下载、喜爱的歌列表一键下载、添加我喜爱的歌曲功能。
音乐下载:
对于音乐下载,我们可以进行当前播放音乐下载、搜索页音乐列表一键下载、喜爱的歌一键下载。如下图所示:
核心代码如下:
# 当前播放音乐下载def down(self):if bo == 'local':downpath = str(filew)downpath = downpath.replace('/', '\\\\')downpath = downpath + SongName[num]print(downpath)print('explorer /select,{}'.format(downpath))call('explorer /select,{}'.format(downpath))else:call('explorer /select,{}'.format(to))# 下载所有音乐def downloadall(self, typer):try:global typerrtyperr = typerprint(typer)print(typerr)self.work = downall()self.work.start()self.work.trigger.connect(self.disdownall)except:print('默认图片下载错误')pass# 下载搜索页列表所有音乐def downloadalls(self):self.downloadall('boing')# 下载喜爱的歌列表所有音乐def downloadalllove(self):self.downloadall('love')
添加喜爱的歌:
对于添加喜爱的歌曲,我们可以通过当前播放音乐的❤标志,也可以通过右键音乐列表进行喜爱的歌曲添加。核心代码如下:
# 通过点击❤标志,对当前播放音乐添加喜爱的歌def lovesong(self):if bo == 'boing' or bo == 'boed':try:global lovesglobal loveurlsglobal lovepicsglobal lovelrcif bo == 'boing':loves.append(songs[num])loveurls.append(urls[num])lovepics.append(pic[num])lovelrc.append(lrcs[num])elif bo == 'boed':loves.append(songed[num])loveurls.append(urled[num])lovepics.append(picd[num])lovelrc.append(lrcd[num])else:passexcept:passself.work = firstThread()self.work.start()self.work.trigger.connect(self.dispng)r = 0self.listwidget3.clear()for i in loves:self.listwidget3.addItem(i)self.listwidget3.item(r).setForeground(Qt.white)r = r + 1print(loves)else:pass# 通过右键音乐列表进行喜爱的歌曲添加def addItem(self):try:global lovesglobal loveurlsglobal lovepicsglobal lovelrcif list_confident == 'boing':loves.append(songs[num_m])loveurls.append(urls[num_m])lovepics.append(pic[num_m])lovelrc.append(lrcs[num_m])else:loves.append(songed[num_m])loveurls.append(urled[num_m])lovepics.append(picd[num_m])lovelrc.append(lrcd[num_m])self.work = firstThread()self.work.start()self.work.trigger.connect(self.dispng)except:passr = 0self.listwidget3.clear()for i in loves:self.listwidget3.addItem(i)self.listwidget3.item(r).setForeground(Qt.white)r = r + 1print('done')print(loves)
这样我们就可以把歌曲添加到喜爱的歌子列表页面下,如下图所示。
3. 喜爱的歌初始化读取显示及列表播放
对于添加到喜爱的歌曲列表,我们需要在退出音乐播放器程序时,将这些数据保存到本地文件,方便后面重新运行时初始化读取显示列表。
喜爱歌曲本地保存:
我们可以把喜爱的歌曲列表、喜爱歌曲图片、歌曲url链接、歌词等数据保存到本地。核心代码如下:
def close(self):reply = QMessageBox.question(self, u'警告', u'确定退出?', QMessageBox.Yes,QMessageBox.No)if reply == QMessageBox.Yes:close = Truetry:mixer.music.stop()except:passtry:rmtree(str(data))except Exception as e:print('删除错误类型是', e.__class__.__name__)print('删除错误明细是', e)filepath = '{}/musicdata'.format(apdata)try:mkdir(filepath)except:passprint(filepath)with open(filepath + "/loves", 'w', encoding='utf-8') as f:f.truncate(0)print(f.write(str(loves)))with open(filepath + "/lovepics", 'w', encoding='utf-8') as f:f.truncate(0)print(f.write(str(lovepics)))with open(filepath + "/loveurls", 'w', encoding='utf-8') as f:f.truncate(0)print(f.write(str(loveurls)))with open(filepath + "/lovelrc", 'w', encoding='utf-8') as f:f.truncate(0)print(f.write(str(lovelrc)))with open(filepath + "/voice", 'w', encoding='utf-8') as f:f.truncate(0)print(f.write(str(voice)))try:rmtree(str(data))except Exception as e:print('删除错误类型是', e.__class__.__name__)print('删除错误明细是', e)exit()else:pass
喜爱的歌初始化读取:
重新运行音乐播放器时,我们需要加载保存在本地的喜爱歌曲数据,并将歌曲列表信息加载显示到喜爱的歌子列表页面中。核心代码如下:
def run(self):try:apdataas = getenv("APPDATA")filepathas = '{}/musicdata'.format(apdataas)global lovelrcglobal loveurlsglobal lovesglobal lovepicsglobal voice# 读取历史数据开始try:with open(filepathas + "/voice", 'r', encoding='utf-8') as f:a = f.read()# print(a)voice = float(a)print(voice)self.trigger.emit(str('voicedone'))except:self.trigger.emit(str('voicedone'))passwith open(filepathas + "/loves", 'r', encoding='utf-8') as f:a = f.read()print(a)strer = aloves = literal_eval(strer)with open(filepathas + "/lovepics", 'r', encoding='utf-8') as f:a = f.read()print(a)strer = alovepics = literal_eval(strer)with open(filepathas + "/loveurls", 'r', encoding='utf-8') as f:a = f.read()print(a)strer = aloveurls = literal_eval(strer)with open(filepathas + "/lovelrc", 'r', encoding='utf-8') as f:a = f.read()print(a)strer = alovelrc = literal_eval(strer)self.trigger.emit(str('login'))print(loves)print('read finish')except:print('read error')pass# 读取数据结束# 下载喜爱的歌列表中首项的歌曲封面try:req = get(lovepics[0])checkfile = open(str(data + '/ls3.png'), 'w+b')for i in req.iter_content(100000):checkfile.write(i)checkfile.close()lsfile = str(data + '/ls3.png')safile = str(data + '/first.png')draw(lsfile, safile)self.trigger.emit(str('first'))except:self.trigger.emit(str('nofirst'))pass
列表播放:
对于加载的喜爱歌曲列表,我们可以对歌曲进行列表一键播放。
# 音乐播放def bofang(self, num, bo):print('尝试进行播放')try:import urllibglobal pauseglobal songsglobal musicglobal downloadingdownloading = Trueself.console_button_3.setIcon(icon('fa.pause', color='#F76677', font=18))pause = Falsetry:mixer.stop()except:passmixer.init()try:self.Timer = QTimer()self.Timer.start(500)except:passtry:self.label.setText('正在寻找文件...')self.work = WorkThread()self.work.start()self.work.trigger.connect(self.display)except:print('无法播放,歌曲下载错误')downloading = Falsepassexcept:sleep(0.1)print('播放系统错误')pass# 播放所有歌曲def playall(self, typer):global numglobal botry:bo = typernum = 0self.bofang(bo, num)except:print('playall error')pass# 播放喜爱歌曲def allplaylove(self):self.playall('love')
4. 歌词显示
对于播放的歌曲,我们可以进行歌词打印显示。核心代码如下:
def run(self):if bo == 'love':try:proxies = {'http': 'http://124.72.109.183:8118','http': 'http://49.85.1.79:31666'}headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36','X-Requested-With': 'XMLHttpRequest'}try:try:try:aq = lovepics[num]aqq = aq.split('/')except:passif type == 'kugou' and len(aqq) - 1 == 6:aqqe = str(aqq[0]) + str('//') + str(aqq[2]) + str('/') + str(aqq[3]) + str('/') + str('400') + str('/') + str(aqq[5]) + str('/') + str(aqq[6])print(aqqe)elif type == 'netease' and len(aqq) - 1 == 4:aqn = aq.split('?')b = '?param=500x500'aqqe = (str(aqn[0]) + str(b))print(aqqe)else:aqqe = lovepics[num]req = get(aqqe)checkfile = open(str(data + '/ls1.png'), 'w+b')for i in req.iter_content(100000):checkfile.write(i)checkfile.close()lsfile = str(data + '/ls1.png')safile = str(data + '/back.png')draw(lsfile, safile)picno = Trueexcept:print('图片错误')picno = Falsepassurl1 = loveurls[num]print(url1)# os.makedirs('music', exist_ok=True)number = number + 1path = str(data + '\\{}.临时文件'.format(number))headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.110.430.128 Safari/537.36','X-Requested-With': 'XMLHttpRequest'}with get(url1, stream=True, headers=headers) as r, open(path, 'wb') as file:total_size = int(r.headers['content-length'])content_size = 0for content in r.iter_content(chunk_size=1024):if not stopdown:file.write(content)content_size += len(content)plan = (content_size / total_size) * 100# print(int(plan))develop = str(int(plan)) + str('%')self.trigger.emit(str(develop))else:print ('down')breakstopdown = Falseto = 'downloadmusic\\{}.mp3'.format(songed[num])makedirs('downloadmusic', exist_ok=True)except:self.trigger.emit(str('nofinish'))passtry:lrct = []f = lovelrc[num] # 按行读取# print(f)lines = f.split('\\n')# print(lines)for i in lines:line1 = i.split('[')try:line2 = line1[1].split(']')if line2 == '':passelse:linew = line2[1]# print(linew)lrct.append(linew)self.trigger.emit(str('lrcfinish'))except:print('歌词错误')except:passtry:copyfile(path, to)except:passdownloading = Falseself.trigger.emit(str('finish'))except:self.trigger.emit(str('nofinish'))
效果如下所示:
5. 本地歌曲读取播放
首先我们可以对本地文件夹目录下的音乐进行读取添加到本地歌曲列表中,根据歌曲的文件名进行列表显示。核心代码如下:
# 本地歌曲文件夹读取添加def add(self):try:global SongPathglobal SongNameglobal numglobal filewglobal asasfileN = QFileDialog.getExistingDirectory(None, "选取文件夹", "")if not fileN == '':self.listwidget5.clear()filew = fileN + '/'asas = filewl1 = [name for name in listdir(fileN) if name.endswith('.mp3') or name.endswith('.flac') or name.endswith('.wma') or name.endswith('.MP3') or name.endswith('.FLAC') or name.endswith('.WMA')]SongNameadd = l1SongPathadd = [filew + i for i in SongNameadd]SongName = SongName + SongNameaddSongPath = SongPath + SongPathaddprint(SongPath)r = 0for i in SongName:self.listwidget5.addItem(i) # 将文件名添加到listWidgetself.listwidget5.item(r).setForeground(Qt.white)r = r + 1except:filew = asas
当然我们还可以对本地加载歌曲进行双击播放,首先我们需要将本地歌曲子列表页面中进行双击事件绑定。
self.tab5 = QWidget()self.tab5.setObjectName("tab5")self.tab5_layout = QGridLayout()self.tab5.setLayout(self.tab5_layout)self.listwidget5 = QListWidget(self.tab5)self.listwidget5.doubleClicked.connect(lambda: self.change(self.listwidget5))self.listwidget5.setContextMenuPolicy(Qt.CustomContextMenu)self.listwidget5.customContextMenuRequested[QPoint].connect(self.myListWidgetContext5)
对子列表页面进行绑定之后,接下来我们就可以完成双击事件,获取歌曲名称,并进行本地播放。
# 加载当前播放的本地歌曲def change(self, listwidget):global numglobal bo# print (item.flags())bo = 'local'num = int(listwidget.currentRow())print(num)# self.label.setText(wenjianming)#设置标签的文本为音乐的名字f = str(SongName[num]).split('.mp3')f = str(f[0]).split('.flac')f = str(f[0]).split('.MP3')f = str(f[0]).split('.FLAC')f = str(f[0]).split('.wma')f = str(f[0]).split('.WMA')self.label.setText(f[0])print(listwidget.currentRow())self.bofanglocal()
# 本地音乐播放def bofanglocal(self):try:global pausetry:self.photo('local')except:passself.console_button_3.setIcon(icon('fa.pause', color='#F76677', font=18))pause = Falsefill = SongPath[num]print(fill)try:global timenummp3 = str(SongPath[num])xx = load(mp3)timenum = xx.info.time_secsglobal startstart = Trueexcept:print('进度条错误,播放失败')try:mixer.stop()except:passtry:mixer.music.load(SongPath[num]) # 载入音乐mixer.music.play() # 播放音乐except:print('MP3音频文件出现错误')except:sleep(0.1)print('system error')self.next()pass
音乐播放器的本地音乐播放功能效果如下:
至此,V2.0升级版的音乐播放器就完成了, 一起来看看效果吧!
源码及打包exe已上传,关注文末公众号回复【播放器源码】即可获取完整源码
Python往期精彩:
见过仙女蹦迪吗?一起用python做个小仙女代码蹦迪视频
python自制一款炫酷音乐播放器,想听啥随便搜!
斗地主老是输?一起用Python做个AI出牌器,豆子蹭蹭涨!
一起用Python做个车牌自动识别系统,好玩又实用!
桌面太单调?一起用Python做个自定义动态壁纸,竟然还可以放视频!
一起用Python做个自动化弹钢琴脚本,我竟然弹出了《天空之城》!
往期精彩源码均可通过下方公众号获取