这是一个用 Python 的 tkinter 库做的一个网络音乐播放器。我不说它的 UI 设计的有多好看,但是它的功能绝对是全站首个!坚持看到底,你不点赞算我输!
成果展示
程序截图
前期准备
程序结构:
所需第三方库(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 中的代码。
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()
后记
这个音乐播放器还有一些不完善的地方,比如只能在有网络的情况下搜索,否则会报错;没有播放列表等等。
⑥项目源码案例分享有
如果你用得到的话可以直接拿走,在我的QQ技术交流群里群号:948351247(纯技术交流和资源共享,广告勿入)以自助拿走
此文转载文,著作权归作者所有,如有侵权联系小编删除哈!