這是一個用 Python 的 tkinter 庫做的一個網路音樂播放器,我不說它的 UI 設計的有多好看,但是它的功能絕對是全站首個!堅持看到底,你不點贊算我輸!
- 酷我音樂搜索、下載
- 進度條查看、控制(本文)
- 滾動歌詞
成果展示

程式截圖


前期準備
程式結構:

| 檔案或檔案夾 | 描述 |
|---|---|
| _pycache_ | 匯入模塊形成的檔案夾 |
| musics | 存盤下載的音樂 |
| get.py | 爬取音樂 |
| gui.py | GUI界面 |
| lrc.py | 歌詞操作 |
| main.pyw | 入口檔案,雙擊運行 |
| player.py | 播放器檔案 |
| requirements.txt | 所需庫 |
所需第三方庫(requirement.txt):
mutagen==1.45.1
pygame==2.0.1
requests==2.26.0
Pillow==8.3.1
安裝:
pip install mutagen # 查看歌曲長度
pip install pygame # 播放音樂
pip install requests # 爬取音樂
pip install pillow # 顯示圖片
或者:
pip install -r requirements.txt
一、爬取音樂
下面的代碼為 get.py 里的內容,
具體教程見 酷我音樂搜索、下載詳解,
import requests
search_url = 'http://www.kuwo.cn/api/www/search/searchMusicBykeyWord'
search_headers = {
'Referer': 'http://www.kuwo.cn/search/list?key=',
'Cookie': '_ga=GA1.2.12......',
'csrf': 'YO4OH2VYH1A'}
search_params = {
'key': 'str', # 查找關鍵字
'pn': '1', # 頁數
'rn': '20', # 項數
'httpsStatus': '1',
'reqId': '6e028fc0-db8f-11eb-b6f5-ff7d54a57f2b'
}
from_url = 'http://www.kuwo.cn/url'
from_params = {
'rid': '148526468', # 歌曲 rid
'type': 'convert_url3',
'br': '128kmp3',
}
lrc_url = 'http://m.kuwo.cn/newh5/singles/songinfoandlrc?musicId={rid}'
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36 Edg/91.0.864.59'}
class Kuwo:
def search_kuwo(self, kw):#
search_params.update({'key': kw})
response = requests.get(search_url,
params=search_params,
headers={**headers, **search_headers},
timeout=2,
).json()
datas = response.get('data', {}).get('list', {})
result = [[r.get('name', ''),
r.get('artist', ''),
r.get('album', ''),
r.get('songTimeMinutes', ''),
r.get('pic', ''),
r.get('pic120', ''),
r.get('rid', '')]
for r in datas]
return result
def get_music_url(self, rid):#
from_params['rid'] = rid
url = requests.get(from_url, params=from_params, headers=headers, timeout=2).json()['url']
return url
def get_music_content(self, rid):
url = self.get_music_url(rid)
content = requests.get(url, headers=headers, timeout=2).content
return content
def get_music_lrc(self, rid):
lrc_data = requests.get(lrc_url.format(rid=rid), headers=headers, timeout=2).json()
lrc_list = lrc_data.get('data', {}).get('lrclist', [{1: '無歌詞', 2: '0'}])
lrc = [list(l.values()) for l in lrc_list]
return lrc
def get_pic(self, url):
pic = requests.get(url, headers=headers).content
return pic
二、歌詞操作
下面代碼為 lrc.py 里的代碼,
import re
class Lrc:
def __init__(self):
self.LRC = [[0.0, '無歌詞']]
self.Times = [0.0]
self.Words = ['無歌詞']
def decode_from_str(self, lrc: str):
lrc = lrc.strip('\n')
res1 = self.SP_DTWDSTR.findall(lrc)
res2 = list()
res3 = dict()
result = dict()
for r in res1:
res2.append([self.SP_DTSTR.findall(r[0]), r[1]])
for r in res2:
for t in r[0]:
res3[self.tosec(t)] = r[1]
result = sorted(list(res3.items()), key=lambda x: x[0])
self.decode(result)
return self.LRC
def decode(self, lrcs):
self.LRC = lrcs
r = list(zip(*lrcs))
self.Words = list(r[0])
self.Times = list(map(self.tosec, list(r[1])))
# 將字串時間變為秒
def tosec(self, t:str):
res1 = t.split(':')[::-1]
res2 = [float(r) * (60 ** i) for i, r in enumerate(res1)]
result = sum(res2)
return result
# 根據浮點數播放進度獲取對應歌詞索引
def get_index(self, t:float):
times = [*self.Times, t]
times.sort()
return times.index(t) - 1
三、播放器
下面代碼為 player.py 里的內容,
播放器的方法其實和 pygame.mixer.music 的方法差別不大,但要注意的是第 1~2 行代碼是用于去除匯入 pygame 模塊時自動列印的資訊,
from os import environ
environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'
import pygame
from io import BytesIO
class Player:
def __init__(self):
pygame.mixer.init()
self.music = pygame.mixer.music
def reset(self):
self.music.stop()
pygame.mixer.pre_init()
def load(self, filename):
self.music.load(filename)
def play(self):
self.music.play()
def pause(self):
self.music.pause()
def unpause(self):
self.music.unpause()
def stop(self):
self.music.stop()
def get_length(self):
return self.music.get_length()
def get_pos(self):
return self.music.get_pos()
def set_pos(self, value=0):
self.music.rewind()
self.music.set_pos(value)
def get_volume(self):
return self.music.get_volume()
def set_volume(self, value=0.5):
return self.music.get_volume(value)
def addsong(self, filename):
self.music.quene(filename)
四、GUI 界面
下面代碼為 gui.py 中的代碼,
有關教程見
- 進度條查看、控制
- 滾動歌詞
- pygame 播放網路音樂
from tkinter import *
from tkinter import ttk
from io import BytesIO
from mutagen.mp3 import MP3
from PIL import Image, ImageTk
import get
import player
import lrc
class Window(Tk):
ischanging = False
last_pos = 0
words = ['']
times = [0]
def __init__(self):
Tk.__init__(self)
self.title('JIE 音樂')
self.geometry('650x400')
self.resizable(0, 0)
self.set_notebook()
self.set_control()
self.set_weight()
self.update()
self.after(100, self.timer)
self.mainloop()
def set_notebook(self):
self.nb = ttk.Notebook(self)
self.nb.grid(row=0, column=0, sticky='nswe', padx=2, pady=1)
self.set_search_frame()
self.set_lrc_frame()
self.nb.add(self.search_frame, text=' 搜索 ')
self.nb.add(self.lrc_frame, text=' 歌詞 ')
# 控制框
def set_control(self):
self.control_frame = Frame(self)
self.control_frame.grid(row=1, column=0, sticky='nswe', padx=2, pady=1)
self.ctrl_pic = Canvas(self.control_frame, height=40, width=40)
self.ctrl_pic.grid(row=0, column=0)
self.play_btn = Label(self.control_frame, text='?', font=('宋體', 24, 'bold'),
width=2, height=1, relief='flat')
self.play_btn.bind('<Enter>', lambda event: self.play_btn.configure(fg='orange'))
self.play_btn.bind('<Leave>', lambda event: self.play_btn.configure(fg='black'))
self.play_btn.bind('<Button-1>', self.play_or_pause)
self.play_btn.grid(row=0, column=1, sticky='nswe')
self.var = IntVar()
self.var.set(0)
self.bar = Scale(self.control_frame, label='無歌曲', orient='horizontal',
variable=self.var, showvalue=False, from_=0, to=0,
command=self.change, width=10, length=500)
self.bar.grid(row=0, column=2, sticky='nwe')
self.download_btn = Label(self.control_frame, text='↓', font=('微軟雅黑', 15), width=2)
self.download_btn.bind('<Enter>', lambda event: self.download_btn.configure(fg='orange'))
self.download_btn.bind('<Leave>', lambda event: self.download_btn.configure(fg='black'))
self.download_btn.bind('<Button-1>', self.download)
self.download_btn.grid(row=0, column=3, sticky='nswe')
# 搜索界面
def set_search_frame(self):
self.search_frame = Frame(self.nb)
self.inputbox = ttk.Entry(self.search_frame, width=14)
self.inputbox.bind('<Return>',lambda event: self.get_datas(self.inputbox.get()))
self.inputbox.grid(row=0, column=0, sticky='nswe', padx=(2, 0), pady=2)
self.surebtn = ttk.Button(self.search_frame, text='搜索', width=6,
command=lambda: self.get_datas(self.inputbox.get()))
self.surebtn.grid(row=0, column=1, columnspan=2, sticky='nswe', padx=(0, 2), pady=2)
columns = [0, 1, 2, 3, 4]
self.songstable = ttk.Treeview(self.search_frame, columns=columns, show='headings')
self.songstable.column(0, width=25, anchor='w', stretch='no')
self.songstable.heading(0, text='')
self.songstable.column(1, width=200, anchor='w')
self.songstable.heading(1, text='歌曲')
self.songstable.column(2, width=70, anchor='w')
self.songstable.heading(2, text='歌手')
self.songstable.column(3, width=100, anchor='w')
self.songstable.heading(3, text='專輯')
self.songstable.column(4, width=45, anchor='w', stretch='no')
self.songstable.heading(4, text='時長')
self.songstable.grid(row=1, column=0, columnspan=2, sticky='nswe')
self.songstable.bind('<Double-Button-1>',
lambda event: self.selected(self.songstable.item(self.songstable.selection()[0], 'value')))
self.songscroll = ttk.Scrollbar(self.search_frame, orient='vertical',
command=self.songstable.yview)
self.songscroll.grid(row=1, column=2, sticky='nswe')
self.songstable.configure(yscrollcommand=self.songscroll.set)
# 歌詞界面
def set_lrc_frame(self):
self.lrc_frame = Frame(self.nb)
self.lrc_title = Label(self.lrc_frame, text='無歌曲', font=('微軟雅黑', 15), anchor='w')
self.lrc_title.grid(row=0, column=1, sticky='nswe', padx=(0, 40), pady=(40, 0))
self.lrc_title2 = Label(self.lrc_frame, text='佚名', font=('微軟雅黑', 10), fg='grey', anchor='w')
self.lrc_title2.grid(row=1, column=1, sticky='nswe', padx=(0, 40))
self.lrc_list = Listbox(self.lrc_frame, relief='flat', font=('微軟雅黑', 12),
highlightthickness=0, selectmode='single',
bg='SystemButtonFace', fg='#303030',
selectbackground='SystemButtonFace', selectforeground='orange')
self.lrc_list.grid(row=2, column=1, sticky='nswe', padx=(0, 40), pady=(10, 40))
self.lrc_list.insert('end', *([''] * 3), '無歌詞')
self.lrc_pic = Canvas(self.lrc_frame, width=240, height=240, relief='flat')
self.lrc_pic.grid(row=0, column=0, rowspan=3, padx=40, pady=40)
def set_weight(self):
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.search_frame.grid_rowconfigure(1, weight=1)
self.search_frame.grid_columnconfigure(0, weight=1)
self.lrc_frame.grid_rowconfigure(2, weight=1)
self.lrc_frame.grid_columnconfigure(1, weight=1)
# 搜索
def get_datas(self, kw='str'):
t = self.songstable.get_children()
for item in t:
self.songstable.delete(item)
datas = kuwo.search_kuwo(kw)
for index, value in enumerate(datas):
self.songstable.insert('','end',value=[index+1, *value])
# 選中歌曲
def selected(self, datas):
player.reset()
self.mdatas = datas
self.last_pos = 0
self.index = 0
self.lrc = lrcdecoder.decode(kuwo.get_music_lrc(datas[-1]))
self.words = lrcdecoder.Words
self.times = lrcdecoder.Times
self.content = kuwo.get_music_content(datas[-1])
self.song_name = datas[1]
self.song_artist = datas[2]
self.pic_small = self.Tkpic(kuwo.get_pic(datas[-2]), 40)
self.pic_large = self.Tkpic(kuwo.get_pic(datas[-3]), 240)
self.ctrl_pic.create_image(0, 0, anchor='nw', image=self.pic_small)
self.lrc_pic.create_image(0, 0, anchor='nw', image=self.pic_large)
self.play_btn.configure(text='||')
self.lrc_title.configure(text=self.song_name)
self.lrc_title2.configure(text=self.song_artist)
self.lrc_list.delete(0, 'end')
self.lrc_list.insert('end', *[*([''] * 2), *self.words])
byte = BytesIO(self.content)
self.bar.configure(from_=0, to=MP3(byte).info.length, label=f'{datas[1]} - {datas[2]}')
player.load(byte)
player.play()
# 將網路 png 圖片用于 tkinter 中
def Tkpic(self, pic, res):
byte_obj = BytesIO(pic)
pic = Image.open(byte_obj)
pic = pic.resize((res, res), Image.ANTIALIAS)
pic = ImageTk.PhotoImage(pic)
return pic
# 拖動進度條時
def change(self, value):
self.ischanging = True
# 暫停、繼續
def play_or_pause(self, event):
if self.play_btn['text'] == '||' :
player.pause()
self.play_btn.configure(text='?')
else:
player.unpause()
self.play_btn.configure(text='||')
# 下載音樂
def download(self, event):
with open(f'musics/{self.song_name} - {self.song_artist}.mp3', 'wb+') as f:
f.write(self.content)
# 定時器
def timer(self):
# 歌詞同步
if self.ischanging:
self.ischanging = False
self.last_pos = self.var.get() - player.get_pos() / 1000
player.set_pos(self.var.get())
else:
self.var.set(player.get_pos() / 1000 + self.last_pos)
# 歌詞高亮
index = lrcdecoder.get_index(player.get_pos() / 1000 + self.last_pos)
self.lrc_list.selection_clear(0, 'end')
self.lrc_list.selection_set(index + 2)
# 滾動到指定位置
index = index / len(self.words)
index = index if index >= 0 else 0
self.lrc_list.yview_moveto(index)
self.after(200, self.timer)
kuwo = get.Kuwo()
player = player.Player()
lrcdecoder = lrc.Lrc()
五、啟動程式
下面代碼為 main.pyw 中的代碼,雙擊此檔案可以直接運行,
import gui
if __name__ == '__main__':
gui.Window()
后記
這個音樂播放器還有一些不完善的地方,比如只能在有網路的情況下搜索,否則會報錯;沒有播放串列等等,
小伙伴們可以自己嘗試改善這個音樂播放器,捕獲網路例外,或者增加一些功能,歡迎評論區留言或者私信作者喔!
決議入口
本文:tkinter 做的音樂播放器
- 酷我音樂搜索、下載
- 進度條查看、控制(本文)
- 滾動歌詞
問題解決:
- pygame 播放網路音樂
原始碼下載:https://download.csdn.net/download/weixin_48448842/20978386
點擊上面的超鏈接可以查看對應部分的講解和代碼,
這是一些解決播放器問題的、用于播放器中一些復雜部分的講解,
作者博客:https://blog.csdn.net/weixin_48448842
作者寫這個花了大半月,很累,麻煩點個贊支持一下謝謝!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/293439.html
標籤:其他
