七夕送女朋友什么礼物好?我反手写了个音乐播放器,女友眼冒金光!
这是一个用 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',
'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 中的代码。
有关教程见
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()
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 做的音乐播放器
问题解决:
源码下载:https://download.csdn.net/download/weixin_48448842/20978386
点击上面的超链接可以查看对应部分的讲解和代码。 这是一些解决播放器问题的、用于播放器中一些复杂部分的讲解。
作者博客:https://blog.csdn.net/weixin_48448842 作者写这个花了大半月,很累,麻烦点个赞支持一下谢谢!
|