前言
前段時間寫的Python/ target=_blank class=infotextkey>Python自制一款炫酷音樂播放器,有不少小伙伴私信我,對播放器提了不少改進建議,讓我完善播放器的功能。今天音樂播放器2.0版本完成了,大家一起來看看是如何用python自制一款炫酷的音樂播放器的吧~
首先我們還是一起來看看實現的音樂播放器最終效果如何:
私信小編01即可獲取大量Python學習資源
由于之前已經介紹過了音樂播放器V1.0版本的大致功能,如果有不了解的小伙伴可以先去看看V1.0版本的音樂播放器大致介紹。【python自制一款炫酷音樂播放器,想聽啥隨便搜!】下面我們就介紹這個音樂播放器V2.0版本新加的部分功能制作過程。
一、核心功能設計
在之前的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 typerr
typerr = typer
print(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 loves
global loveurls
global lovepics
global lovelrc
if 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:
pass
except:
pass
self.work = firstThread()
self.work.start()
self.work.trigger.connect(self.dispng)
r = 0
self.listwidget3.clear()
for i in loves:
self.listwidget3.addItem(i)
self.listwidget3.item(r).setForeground(Qt.white)
r = r + 1
print(loves)
else:
pass
# 通過右鍵音樂列表進行喜愛的歌曲添加
def addItem(self):
try:
global loves
global loveurls
global lovepics
global lovelrc
if 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:
pass
r = 0
self.listwidget3.clear()
for i in loves:
self.listwidget3.addItem(i)
self.listwidget3.item(r).setForeground(Qt.white)
r = r + 1
print('done')
print(loves)
這樣我們就可以把歌曲添加到喜愛的歌子列表頁面下,如下圖所示。
3. 喜愛的歌初始化讀取顯示及列表播放
對于添加到喜愛的歌曲列表,我們需要在退出音樂播放器程序時,將這些數據保存到本地文件,方便后面重新運行時初始化讀取顯示列表。
喜愛歌曲本地保存:我們可以把喜愛的歌曲列表、喜愛歌曲圖片、歌曲url鏈接、歌詞等數據保存到本地。核心代碼如下:
def close(self):
reply = QMessageBox.question(self, u'警告', u'確定退出?', QMessageBox.Yes,
QMessageBox.No)
if reply == QMessageBox.Yes:
close = True
try:
mixer.music.stop()
except:
pass
try:
rmtree(str(data))
except Exception as e:
print('刪除錯誤類型是', e.__class__.__name__)
print('刪除錯誤明細是', e)
filepath = '{}/musicdata'.format(apdata)
try:
mkdir(filepath)
except:
pass
print(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 lovelrc
global loveurls
global loves
global lovepics
global 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'))
pass
with open(filepathas + "/loves", 'r', encoding='utf-8') as f:
a = f.read()
print(a)
strer = a
loves = literal_eval(strer)
with open(filepathas + "/lovepics", 'r', encoding='utf-8') as f:
a = f.read()
print(a)
strer = a
lovepics = literal_eval(strer)
with open(filepathas + "/loveurls", 'r', encoding='utf-8') as f:
a = f.read()
print(a)
strer = a
loveurls = literal_eval(strer)
with open(filepathas + "/lovelrc", 'r', encoding='utf-8') as f:
a = f.read()
print(a)
strer = a
lovelrc = 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 urllib
global pause
global songs
global music
global downloading
downloading = True
self.console_button_3.setIcon(icon('fa.pause', color='#F76677', font=18))
pause = False
try:
mixer.stop()
except:
pass
mixer.init()
try:
self.Timer = QTimer()
self.Timer.start(500)
except:
pass
try:
self.label.setText('正在尋找文件...')
self.work = WorkThread()
self.work.start()
self.work.trigger.connect(self.display)
except:
print('無法播放,歌曲下載錯誤')
downloading = False
pass
except:
sleep(0.1)
print('播放系統錯誤')
pass
# 播放所有歌曲
def playall(self, typer):
global num
global bo
try:
bo = typer
num = 0
self.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:
pass
if 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 = True
except:
print('圖片錯誤')
picno = False
pass
url1 = loveurls[num]
print(url1)
# os.makedirs('music', exist_ok=True)
number = number + 1
path = 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 = 0
for 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')
break
stopdown = False
to = 'downloadmusic{}.mp3'.format(songed[num])
makedirs('downloadmusic', exist_ok=True)
except:
self.trigger.emit(str('nofinish'))
pass
try:
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 == '':
pass
else:
linew = line2[1]
# print(linew)
lrct.append(linew)
self.trigger.emit(str('lrcfinish'))
except:
print('歌詞錯誤')
except:
pass
try:
copyfile(path, to)
except:
pass
downloading = False
self.trigger.emit(str('finish'))
except:
self.trigger.emit(str('nofinish'))
效果如下所示:
5. 本地歌曲讀取播放
首先我們可以對本地文件夾目錄下的音樂進行讀取添加到本地歌曲列表中,根據歌曲的文件名進行列表顯示。核心代碼如下:
# 本地歌曲文件夾讀取添加
def add(self):
try:
global SongPath
global SongName
global num
global filew
global asas
fileN = QFileDialog.getExistingDirectory(None, "選取文件夾", "")
if not fileN == '':
self.listwidget5.clear()
filew = fileN + '/'
asas = filew
l1 = [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 = l1
SongPathadd = [filew + i for i in SongNameadd]
SongName = SongName + SongNameadd
SongPath = SongPath + SongPathadd
print(SongPath)
r = 0
for i in SongName:
self.listwidget5.addItem(i) # 將文件名添加到listWidget
self.listwidget5.item(r).setForeground(Qt.white)
r = r + 1
except:
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 num
global 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 pause
try:
self.photo('local')
except:
pass
self.console_button_3.setIcon(icon('fa.pause', color='#F76677', font=18))
pause = False
fill = SongPath[num]
print(fill)
try:
global timenum
mp3 = str(SongPath[num])
xx = load(mp3)
timenum = xx.info.time_secs
global start
start = True
except:
print('進度條錯誤,播放失敗')
try:
mixer.stop()
except:
pass
try:
mixer.music.load(SongPath[num]) # 載入音樂
mixer.music.play() # 播放音樂
except:
print('MP3音頻文件出現錯誤')
except:
sleep(0.1)
print('system error')
self.next()
pass
音樂播放器的本地音樂播放功能效果如下: