相比第一版,第三版增加了多线程下载功能,利用threading和queue队列实现多线程下载。
搜索功能:1.网易歌单链接https://music.163.com/#/playlist?id=2525632823,
或者 https://music.163.com/#/discover/toplist?id=3779629
2.歌曲名或者歌手,默认搜索20首
音乐播放功能实现上一首下一首切换(listbox选择后点击播放,点下一首会自动播放),循环播放。
thinter的优点就是简单,毛病就是运行会很卡,多线程下载还是那个样,这个小工具可以批量下载,也可以在listbox多选后下载。
制作播放进度条,发现线程暂停和恢复有点问题,暂未完美实现,下次准备用pyqt5实现这个功能点。
GUI界面
"""
之前做过一个版本,接口失效,现在更新版本
"""
import re,os
from tkinter import Tk,Label,StringVar,Listbox,Scrollbar,SINGLE,HORIZONTAL,Radiobutton,messagebox,\
ALL,filedialog,Canvas,END,MULTIPLE,EXTENDED
from tkinter.ttk import Button,Entry
from neteasy_music.music_down import Music_download
from neteasy_music.music_play import Playclass
from tkinter import messagebox
class App:
song_dic={}
index=0
def __init__(self):
self.Mu=Music_download()#导入下载模块
# self.Play=Playclass()#导入播放模块
self.w=Tk()
self.w.title('网易云音乐下载器V3.0')
self.w.geometry('450x450')
self.w.resizable(False,False)#锁住
# self.w.config(bg='#535353')
self.creat_base_surface()#设置基本框架
self.surface_place_set()#设置布局
self.tk_config_()
self.w.mainloop()
def creat_base_surface(self):
self.E_get_url=StringVar()
self.E_save_file=StringVar()
self.E_get_searchflag=StringVar()
self.R_process_get=StringVar()#线程
self.pause_flag=StringVar(value='^_^')
self.S_songlist_r=Scrollbar()
self.S_songlist_l=Scrollbar(orient=HORIZONTAL)
self.L_title=Label(self.w,text='网易云音乐下载播放器升级版',fg='blue')
self.L_enterurl=Label(self.w,text='请输入URL歌单或单曲名或歌手!')
self.E_url=Entry(self.w,textvariable=self.E_get_url)
self.B_search=Button(self.w,text='搜索')
self.L_songbox=Listbox(self.w)
self.L_songlist=Label(self.w,text='【歌曲列表】',fg='#BA55D3')
self.B_all_down=Button(self.w,text='批量下载')
self.B_single_down=Button(self.w,text='单个下载')
self.L_all_down=Label(self.w,bg='#EEE5DE')
self.L_single_down=Label(self.w,bg='#EEE5DE')
self.E_save=Entry(self.w,textvariable=self.E_save_file)
self.B_save=Button(self.w,text='目录')
self.R_down=Radiobutton(self.w,value='d',text='下载',variable=self.E_get_searchflag,fg='black')
self.R_play=Radiobutton(self.w,value='p',text='播放',variable=self.E_get_searchflag,fg='black')
self.B_play_song=Button(self.w,text='播放')
self.B_pause=Button(self.w,textvariable=self.pause_flag)
self.B_stop_play=Button(self.w,text='停止')
self.B_previous_song=Button(self.w,text='上一首')
self.B_next_song=Button(self.w,text='下一首')
self.Show_canvas1=Canvas(self.w,bg='#B2DFEE')
self.Show_canvas_process=Canvas(self.w,bg='white')
self.Show_canvas2=Canvas(self.w,bg='#7EC0EE')
self.R_process_s=Radiobutton(self.w,text='单线程',variable=self.R_process_get,value='s',fg='#BA55D3')
self.R_process_d=Radiobutton(self.w,text='多线程',variable=self.R_process_get,value='m',fg='#BA55D3')
def surface_place_set(self):
self.L_title.place(x=10,y=10,width=180,height=30)
self.L_enterurl.place(x=10,y=45,width=180,height=30)
self.E_url.place(x=10,y=80,width=180,height=30)
self.B_search.place(x=200,y=70,width=50,height=40)
self.L_songbox.place(x=260,y=30,width=170,height=180)
self.L_songlist.place(x=260,y=6,width=170,height=25)
self.S_songlist_r.place(x=428,y=30,width=15,height=180)
self.S_songlist_l.place(x=260,y=209,width=170,height=15)
self.B_all_down.place(x=10,y=118,width=60,height=30)
self.B_single_down.place(x=10,y=158,width=60,height=30)
self.L_all_down.place(x=80,y=118,width=170,height=30)
self.L_single_down.place(x=80,y=158,width=170,height=30)
self.R_process_s.place(x=80,y=190,width=70,height=20)
self.R_process_d.place(x=170,y=190,width=70,height=20)
self.E_save.place(x=10,y=215,width=180,height=30)
self.B_save.place(x=200,y=215,width=50,height=30)
self.R_down.place(x=200,y=15,width=45,height=20)
self.R_play.place(x=200,y=40,width=45,height=20)
self.B_play_song.place(x=10,y=250,width=50,height=30)
self.B_pause.place(x=10,y=285,width=50,height=30)
self.B_stop_play.place(x=10,y=322,width=50,height=30)
self.B_previous_song.place(x=10,y=362,width=50,height=35)
self.B_next_song.place(x=10,y=402,width=50,height=35)
self.Show_canvas1.place(x=70,y=250,width=370,height=30)
self.Show_canvas_process.place(x=70,y=275,width=370,height=15)
self.Show_canvas2.place(x=70,y=285,width=370,height=160)
def tk_config_(self):
self.S_songlist_r.config(command=self.L_songbox.yview)
self.L_songbox['yscrollcommand']=self.S_songlist_r.set
self.S_songlist_l.config(command=self.L_songbox.xview)
self.L_songbox['xscrollcommand']=self.S_songlist_l.set
self.L_songbox.config(selectmode=EXTENDED)
self.E_get_searchflag.set('d')#设置下载
self.R_process_get.set('m')#设置多线程
self.B_save.config(command=self.file_open)
self.B_search.config(command=self.search_song)
self.B_single_down.config(command=self.single_down)
self.B_all_down.config(command=self.all_songs_down)
self.B_play_song.config(command=self.play_song)
self.B_pause.config(command=self.pause_play)
self.B_stop_play.config(command=self.stop_play)
self.B_previous_song.config(command=self.play_previous_song)
self.B_next_song.config(command=self.play_next_song)
def file_open(self):
self.file_=filedialog.askdirectory()
self.E_save_file.set(self.file_)
def show_listbox(self,msg):#显示在box,dic
if isinstance(msg,dict):
for id,name in msg.items():
self.L_songbox.insert(END,id+'-'+name)
elif isinstance(msg,list):
for name in msg:
self.L_songbox.insert(END,name)
def show_canvas_msg(self,msg,w,h,color):#显示歌单信息
self.Show_canvas1.create_text(w,h,text=msg,fill=color)
def clear_box(self):#清除
self.L_songbox.delete(0,END)
def clear_canvas(self):#清空canvas
self.Show_canvas1.delete(ALL)
def creat_process_line(self):#进度条
pass
def drow_canvas_line(self):
self.drow_line()
def drow_line(self):
self.Show_canvas1.create_line(0,24,370,24,fill='black',width=1)
def search_song(self):
"""
https://music.163.com/#/discover/toplist?id=2884035
https://music.163.com/#/playlist?id=2337333174
"""
pa_playlist=r'((https|http)?:\/\/)(music.163.com\/#\/)(playlist|discover\/toplist)\?id=[\d]{2,}'
pa_name=r'[a-z]+|[A-Z]+|[\u4e00-\u9fa5]+'#汉字 字母数字
self.clear_box()
if re.match(pa_playlist,self.E_get_url.get()):#匹配歌单
try:
self.song_dic=self.Mu.get_music_list_res(self.E_get_url.get())
# print(song_dic)
# self.clear_canvas()
self.show_listbox(self.song_dic)
self.show_canvas_msg('歌单共有歌曲:{}首'.format(len(self.song_dic)),60,10,'blue')
except Exception:
messagebox.showerror(title='错误',message='此歌单未解析出歌曲')
elif re.match(pa_name,self.E_get_url.get()) and '/' not in self.E_get_url.get():#匹配歌名,无//
print('歌名')
self.song_dic=self.Mu.search_songs_id(self.E_get_url.get())
self.show_listbox(self.song_dic)
self.show_canvas_msg('暂搜索歌曲:{}首'.format(len(self.song_dic)), 60, 10, 'blue')
elif self.E_get_url.get()=='' and self.E_get_searchflag.get()=='p':
if os.path.exists(self.E_save_file.get()):
file_lis=os.listdir(self.E_save_file.get())
file_lis=[i for i in file_lis if re.match("(.+.mp3)|(.+.wma)|(.+.wav)",i)]#清除非音乐文件
self.show_listbox(file_lis)
print(file_lis)
else:
messagebox.showwarning(title='警告',message='请选择目录')
else:
messagebox.showwarning(title='警告',message='清输入正确的歌单或者歌名')
def single_down(self):#下载标识,线程,路径,id
dic={}
print('*'*100)
print('下载单曲')
print(self.song_dic)
print(self.L_songbox.curselection())
print(len(self.L_songbox.curselection()))
print('*'*100)
if self.E_get_searchflag.get()=='d':
if os.path.exists(self.E_save_file.get()):
if len(self.L_songbox.curselection())>0:
mp3_list=list(self.L_songbox.curselection())
for i in mp3_list:
id = self.L_songbox.get(i).split('-')[0]
name = self.L_songbox.get(i).split('-')[1]
dic[id]=name
self.Mu.down_single_song(self.E_save_file.get(),dic)
if len(mp3_list)>1:
self.L_single_down.config(text='成功下载{}首歌曲'.format(len(mp3_list)),fg='green')
elif len(mp3_list)==1:
self.L_single_down.config(text='成功下载歌曲{}'.format(self.L_songbox.get(self.L_songbox.curselection()).split('-')[1]),fg='green')
else:
messagebox.showwarning(title='警告',message='请选择单曲')
else:
messagebox.showerror(title='警告',message='文件夹路径不存在')
else:
messagebox.showerror(title='错误',message='请选择下载控件')
def all_songs_down(self):
print('*'*100)
print('批量下载')
print(self.song_dic)
print(self.R_process_get.get())
print(self.E_save_file.get())
print('*'*100)
if self.E_get_searchflag.get()=='d':
if len(self.song_dic)>0:
if os.path.exists(self.E_save_file.get()):
self.Mu.down_songs(self.R_process_get.get(),self.E_save_file.get(), self.song_dic)
self.L_all_down.config(text='列表已全部下载完成',fg='green')
else:
messagebox.showwarning(title='警告',message='文件夹错误')
else:
messagebox.showwarning(title='警告',message='列表为空')
else:
messagebox.showerror(title='错误', message='请选择下载控件')
def play_song(self):
if self.E_get_searchflag.get()=='p':
print('播放歌曲')
print(self.L_songbox.curselection())
if len(self.L_songbox.curselection())==0:
messagebox.showwarning(title='警告',message='列表为空')
else:
self.play=Playclass()
print(self.pause_flag.get())
file_mp3=self.L_songbox.get(self.L_songbox.curselection())
if re.match('.+(mp3|wma|wav)',file_mp3):
self.play.play_music_mp3(self.E_save_file.get()+'/'+file_mp3)
self.pause_flag.set('暂停')
else:
messagebox.showerror(title='错误',message='文件不是标准mp3文件')
else:
messagebox.showerror(title='错误',message='请选择播放控件')
def pause_play(self):
# print('暂停')
self.play.pause_button_click(self.pause_flag)
def stop_play(self):
self.play.stop_button_click()
def play_previous_song(self):#上一首
try:
self.index=min(self.L_songbox.curselection())
print(self.index)
if len(self.L_songbox.curselection())>0:
self.index = self.L_songbox.curselection()[0]
self.index -= 1
if self.index<0: # 选择空
self.index = self.L_songbox.size()-1
self.L_songbox.selection_clear(0) # 此处要清除掉di一行
print('bf', self.index)
self.L_songbox.select_set(self.index)
self.L_songbox.selection_clear(self.index +1) # 此处会多选,要清除掉
self.play_song()
except:
messagebox.showwarning(title='警告',message='列表为空')
def play_next_song(self):#下一首
try:
self.index=min(self.L_songbox.curselection())
print(self.index)
if len(self.L_songbox.curselection())>0:
self.index = self.L_songbox.curselection()[0]
self.index += 1
if self.index==self.L_songbox.size(): # 选择空
self.index = 0
self.L_songbox.selection_clear(self.L_songbox.size()-1) # 此处要清除掉最后一行
print('bf', self.index)
self.L_songbox.select_set(self.index)
self.L_songbox.selection_clear(self.index - 1) # 此处会多选,要清除掉
self.play_song()
except:
messagebox.showwarning(title='警告',message='列表为空')
if __name__ == '__main__':
a=App()
多线程下载代码
#coding=gbk
import os
import time
import threading
import queue
import requests
from functools import reduce
lock=threading.Lock()
class MyThreading(threading.Thread):
def __init__(self,headers,base_url,path,que):
super().__init__()
self.headers=headers
self.base_url=base_url
self.path=path
self.que=que
def replace_char(self,s, oldChar, newChar):
return reduce(lambda s, char: s.replace(char, newChar), oldChar, s)
def downloader(self):
#队列的id和name组合url和mp3名
msg=self.que.get().split(':')
url=self.base_url+msg[0]
name=msg[1]
if not os.path.exists(self.path):
os.mkdir(self.path)
print('文件夹{}已创建'.format(self.path))
try:
if not os.path.exists(self.path+'/'+name+'.mp3'):
data = requests.get(url, headers=self.headers).content
with open(self.path+'/'+name+'.mp3','wb') as f:
f.write(data)
print('歌曲:{}已下载完成'.format(name))
else:
print('歌曲:{}已存在'.format(name))
except Exception as f:
print('解析错误',f.args)
def run(self):
while True:
if not self.que.empty():
#执行下载程序会阻塞
self.downloader()
# time.sleep(0.1)
else:
print('队列空')
break
class Threading_fuc:
que = queue.Queue()
def __init__(self,dic,headers,base_url,path):
self.dic=dic
self.headers=headers
self.base_url=base_url
self.path=path
def replace_char(self,s, oldChar, newChar):
return reduce(lambda s, char: s.replace(char, newChar), oldChar, s)
def threading_run(self):
start=time.time()
for id,name in self.dic.items():
name=self.replace_char(name,',/|\t?:',' ')
self.que.put(id+':'+name)
for n in range(1,5):
tar=MyThreading(self.headers,self.base_url,self.path,self.que)
tar.start()
tar.join()
end=time.time()
print('耗时:{}'.format(end-start))