python基于tkinter制作无损音乐下载工具

继续写GUI,本次依然使用Tkinter设计一款图形界面,使用Tkinter做一款音乐下载软件,听起来听平常的,但是我这款软件能够下载 无损音乐下载软件,听起来不错吧,Let`s go!

Posted in Python onMarch 29, 2021

一.准备工作

python Tkinter

二.预览

python基于tkinter制作无损音乐下载工具

1.搜索

python基于tkinter制作无损音乐下载工具

2.下载

python基于tkinter制作无损音乐下载工具

3.结果

无损音乐就这样下载完了。

python基于tkinter制作无损音乐下载工具

三.详细设计

这里仅展示我设计的整体思路。

python基于tkinter制作无损音乐下载工具

四.源代码

4.1 Music_Search-v1.0.py

from tkinter import *
from tkinter import ttk
from tkinter import messagebox
from Music_Search_Engine import Spider
import threading
from tkinter.filedialog import askdirectory
import os
 
'''
1.加入e1绑定事件,b1='disable'
2. 03.15-使用self.flag判断当前下载任务是否完成
3.实现UI和爬虫分离,返回实时进度
'''
class App:
 def __init__(self):
 self.w=Tk()
 self.w.title('Music_Search-v1.0')
 self.w.resizable(0,0)
 self.flag=True
 width=400
 height=560
 left=(self.w.winfo_screenwidth()-width)/2
 top=(self.w.winfo_screenheight()-height)/2
 self.w.geometry('%dx%d+%d+%d'%(width,height,left,top))
 self.create_widget()
 self.set_widget()
 self.place_widget()
 self.w.mainloop()
 
 def create_widget(self):
 self.e2_var=StringVar()
 self.r_choice=IntVar()
 self.l3_var=StringVar()
 self.l1=ttk.Label(self.w,text='关键字:')
 self.e1=ttk.Entry(self.w)
 self.b1=ttk.Button(self.w,text='搜索')
 self.l4 = ttk.Label(self.w, text='存储路径:')
 self.e2 = ttk.Entry(self.w,textvariable=self.e2_var)
 self.b2 = ttk.Button(self.w, text='选择')
 self.l2=ttk.Label(self.w,text='下载品质:')
 self.r1=Radiobutton(self.w,text='标准',value=1)
 self.r2=Radiobutton(self.w,text='高品',value=2)
 self.r3=Radiobutton(self.w,text='无损',value=3)
 self.b3=ttk.Button(self.w,text='下载')
 self.listbox=Listbox(self.w)
 self.canvas = Canvas(self.w, bg="white")
 self.l3=ttk.Label(self.w)
 self.m=Menu(self.w)
 self.w['menu']=self.m
 self.s1=Menu(self.m,tearoff=False)
 self.s2=Menu(self.m,tearoff=False)
 self.s3=Menu(self.m,tearoff=False)
 
 def set_widget(self):
 self.b1.config(command=lambda:self.thread_it(self.search_music))
 self.e1.config(justify='center')
 self.b2.config(command=self.open_file_savepath)
 self.r1.config(variable=self.r_choice,command=self.show_size,state='disable')
 self.r2.config(variable=self.r_choice,command=self.show_size,state='disable')
 self.r3.config(variable=self.r_choice,command=self.show_size,state='disable')
 self.b3.config(command=lambda:self.thread_it(self.pre_download))
 self.canvas.config(width=380, height=20)
 self.w.bind('<<ListboxSelect>>',self.show_info)
 self.e1.bind('<Return>',self.do_search)
 self.w.protocol('WM_DELETE_WINDOW',self.quit_window)
 self.w.bind('<Escape>',self.do_escape)
 self.l3.config(textvariable=self.l3_var,background='lightblue',justify='center')
 self.l3_var.set('请先搜索')
 self.listbox.config(state='disable')
 self.abs_path = os.path.abspath('./')
 self.e2_var.set(self.abs_path)
 self.e2.config(state='readonly')
 self.b3.config(state='disable')
 self.m.add_cascade(label='文件',menu=self.s1)
 self.s1.add_command(label='打开文件夹',command=self.open_dir)
 self.s1.add_separator()
 self.s1.add_command(label='退出',command=self.quit_window)
 self.m.add_cascade(label='操作',menu=self.s2)
 self.s2.add_command(label='搜索',command=lambda:self.thread_it(self.search_music))
 self.s2.add_command(label='下载',command=lambda:self.thread_it(self.pre_download))
 self.s2.entryconfig("下载",state=DISABLED)
 self.m.add_cascade(label='关于',menu=self.s3)
 self.s3.add_command(label='说明',command=self.show_explian)
 
 def place_widget(self):
 self.l1.place(x=10,y=10)
 self.e1.place(x=80,y=10,width=200)
 self.b1.place(x=310,y=10,height=25,width=80)
 self.l2.place(x=10,y=80)
 self.r1.place(x=80,y=80)
 self.r2.place(x=160,y=80)
 self.r3.place(x=240,y=80)
 self.l4.place(x=10,y=50)
 self.e2.place(x=80,y=50,width=200)
 self.b2.place(x=310,y=45,height=25,width=80)
 self.b3.place(x=310,y=80,height=25,width=80)
 self.listbox.place(x=10,y=110,width=380,height=380)
 self.l3.place(x=0,y=520,width=400,height=35)
 self.canvas.place(x=10,y=492)
 
 def thread_it(self,func,*args):
 t=threading.Thread(target=func,args=args)
 t.setDaemon(True)
 t.start()
 
 def do_search(self,event):
 self.thread_it(self.search_music)
 
 def search_music(self):
 self.l3_var.set('')
 self.listbox.delete(0,END)
 spider=Spider()
 if self.e1.get():
 self.music_list=spider.Get_Music_List(self.e1.get())
 if self.music_list:
 self.listbox.config(state='normal')
 counter=1
 for data in self.music_list:
  song_name = data.get('song_name')
  self.listbox.insert(END,str(counter)+'、'+song_name)
  self.listbox.update()
  counter+=1
 self.l3_var.set(f'共检索到了{len(self.music_list)}首歌曲')
 self.s2.entryconfig("下载", state=NORMAL)
 self.b3.config(state='normal')
 else:
 messagebox.showinfo('提示','没有找到相关歌曲,请更换关键字!')
 self.l3_var.set('没有找到相关歌曲,请更换关键字!')
 self.l3.config(background='lightblue')
 else:
 messagebox.showerror('错误','请输入关键字!')
 self.l3_var.set('请输入关键字!')
 self.l3.config(background='red')
 
 def show_info(self, event):
 self.r1.config(state='normal')
 self.r2.config(state='normal')
 self.r3.config(state='normal')
 self.r_choice.set(0)
 try:
 listbox_index = self.listbox.curselection()[0]#获取选中歌曲索引
 data=self.music_list[listbox_index]
 if data['FileHash']==''and data['FileSize']==0:
 self.r1.config(state='disable')
 self.r1.config(state='disable')
 self.file_size=data['FileSize']
 if data['HQFileHash'] == ''and data['HQFileSize']==0:
 self.r2.config(state='disable')
 self.hq_size=data['HQFileSize']
 if data['SQFileHash'] == ''and data['SQFileSize']==0:
 self.r3.config(state='disable')
 self.sq_size=data['SQFileSize']
 self.l3_var.set('歌曲名称:'+data['song_name'])
 except (IndexError,TclError):
 pass
 
 def show_size(self):
 try:
 if self.r_choice.get() == 1:
 self.l3_var.set('标准格式文件大小:' + self.process_size(self.file_size))
 elif self.r_choice.get() == 2:
 self.l3_var.set('高品质格式文件大小:' + self.process_size(self.hq_size))
 elif self.r_choice.get() == 3:
 self.l3_var.set('无损格式文件大小:' + self.process_size(self.sq_size))
 except AttributeError:
 messagebox.showwarning('警告','请先选择歌曲')
 self.r_choice.set(0)
 
 def process_size(self,bytes):
 try:
 bytes=float(bytes)
 kb=bytes/1024
 except:
 return 'error'
 if kb>1024:
 mb=kb/1024
 if mb>1024:
 gb=mb/1024
 return '%.2fGB'%gb
 else:
 return '%.2fMB'%mb
 else:
 return '%.2fKB'%kb
 
 def open_file_savepath(self):
 self.file = askdirectory()
 if self.file:
 self.e2_var.set(self.file)
 
 def pre_download(self):
 listbox_index = self.listbox.curselection()[0] # 获取选中歌曲索引
 data = self.music_list[listbox_index]
 music_name=data['song_name']
 if self.r_choice.get()==1:
 FileHash=data['FileHash']
 real_link=Spider().get_music_link(FileHash)
 type='mp3'
 if real_link:
 self.download_music(real_link,music_name,type)
 else:
 messagebox.showwarning('警告','没有此音乐版权,正在争取!')
 elif self.r_choice.get()==2:
 HQFileHash=data['HQFileHash']
 type='mp3'
 real_link=Spider().get_music_link(HQFileHash)
 if real_link:
 self.download_music(real_link,music_name,type)
 else:
 messagebox.showwarning('警告','没有此音乐版权,正在争取!')
 elif self.r_choice.get()==3:
 SQFileHash=data['SQFileHash']
 type='flac'
 real_link=Spider().get_music_link(SQFileHash)
 if real_link:
 self.download_music(real_link,music_name,type)
 else:
 messagebox.showwarning('警告','没有此音乐版权,正在争取!')
 
 def download_music(self,music_link,music_name,music_type):
 if self.flag:
 self.flag=False
 file_path=self.e2_var.get()
 # 先清空进度条,再下载
 self.clean_progressbar()
 
 try:
 os.mkdir(file_path+'/My_Music/')
 except:
 pass
 file = file_path+f'/My_Music/{music_name}.{music_type}'
 fill_line = self.canvas.create_rectangle(1.5, 1.5, 0, 23, width=0, fill="green")
 self.l3_var.set(f'正在下载{music_name}......')
 for process in Spider().download_music(music_link,file_path=file):
 self.canvas.coords(fill_line, (0, 0, process, 60))
 self.w.update()
 self.l3_var.set(f'{music_name}.{music_type}下载完成!')
 messagebox.showinfo('提示',f'{music_name}.{music_type}下载完成!')
 self.flag=True
 else:
 messagebox.showwarning('警告','请等待当前任务完成!')
 
 def clean_progressbar(self):
 # 清空进度条
 fill_line = self.canvas.create_rectangle(1.5, 1.5, 0, 23, width=0, fill="white")
 x = 500 # 未知变量,可更改
 n = 380 / x # 465是矩形填充满的次数
 for t in range(x):
 n = n + 380 / x
 # 以矩形的长度作为变量值更新
 self.canvas.coords(fill_line, (0, 0, n, 60))
 self.w.update()
 
 def open_dir(self):
 file_path=self.e2_var.get()
 try:
 os.mkdir(file_path + '/My_Music/')
 except:
 pass
 os.startfile(file_path + '/My_Music/')
 
 def show_explian(self):
 messagebox.showwarning('敬告','本软件仅供学习交流!')
 
 def do_escape(self,event):
 self.quit_window()
 
 def quit_window(self):
 ret=messagebox.askyesno('退出','是否要退出?')
 if ret:
 self.w.destroy()
 
if __name__ == '__main__':
 a=App()

4.2 Music_Search_Engine.py

import requests
import re
import json
from urllib import parse
import hashlib
from requests.adapters import HTTPAdapter
 
class Spider(object):
 
 def clean_txt(self, title): # 清洗标题中不能用于命名文件的字符
 rstr = r"[\/\\\:\*\?\"\<\>\|]" # '/ \ : * ? " < > |'
 title = re.sub(rstr, "_", title) # 替换为下划线
 return title
 
 def get_one_page(self, url):
 headers = {
 'referer': 'https://www.kugou.com/song/',
 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
 }
 try:
 s = requests.Session() # 保持会话
 s.mount('http://', HTTPAdapter(max_retries=3)) # 最大重试
 s.mount('https://', HTTPAdapter(max_retries=3))
 r = s.get(url, headers=headers, timeout=15) # 超时设置
 r.raise_for_status() # 状态码 如果不是200则报错
 r.encoding = 'utf-8' # r.apparent_encoding#字符类型
 return r.text # 返回页面
 except:
 pass
 
 def Get_Music_List(self, key_word):
 result_list=[]
 search_url = 'http://songsearch.kugou.com/song_search_v2?keyword={}&page=1'.format(key_word)
 total = json.loads(self.get_one_page(search_url))['data']['total']
 #total值为0就是没有搜索到相关歌曲
 if total != 0:
 search_total_url = search_url + '&pagesize=%d' % total
 music_list = json.loads(self.get_one_page(search_total_url))['data']['lists'] # 歌曲列表
 for music in music_list:
 item = {}#防止字典值覆盖
 item['song_name']=self.clean_txt(music['FileName'].replace('<em>', '').replace('</em>', '')) # 歌手—歌曲
 item['FileHash']=music['FileHash']
 item['HQFileHash']=music['HQFileHash']
 item['SQFileHash']=music['SQFileHash']
 item['FileSize']=music['FileSize']
 item['HQFileSize']=music['HQFileSize']
 item['SQFileSize']=music['SQFileSize']
 result_list.append(item)
 return result_list
 else:
 return None
 
 def v2_md5(self, Hash): # 用于生成key,
 return hashlib.md5((Hash + 'kgcloudv2').encode('utf-8')).hexdigest()
 
 def get_music_link(self, hash):
 Hash = str.lower(hash) # 小写哈希值
 key_new = self.v2_md5(Hash) # 生成v2系统key
 Music_api_1 = 'http://trackercdnbj.kugou.com/i/v2/'
 params = {
 'cmd': 23,
 'pid': 1,
 'behavior': 'download',
 'hash': Hash,
 'key': key_new
 }
 try:
 real_music_link=json.loads(self.get_one_page(Music_api_1+'?'+parse.urlencode(params)))['url']
 return real_music_link
 except KeyError:
 return None
 
 #实时返回当前下载进度
 def download_music(self,music_link,file_path):
 headers = {
 'sec-fetch-dest': 'document',
 'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Mobile Safari/537.36'
 }
 r = requests.get(music_link, headers=headers, stream=True)
 chunk_size = 1024 # 每一块的大小,每次下载块的大小
 file_size = int(r.headers['Content-Length']) # 提取出来的文件大小为string格式,使用int()强制转化
 raise_data = 380 / (file_size / chunk_size) # 增量大小,380为进度条的长度
 _size = 0 # 已经下载文件的大小
 with open(file_path, "wb") as f:
 n = 0
 for data in r.iter_content(chunk_size): # inter_content:用于边下载边存硬盘,每次下载chunk_size大小的块
  f.write(data)
  n += raise_data
  yield n

本次使用TKinter制作一款无损音乐下载软件,工具打包好放在了蓝奏云,请自取。

Python 相关文章推荐
python在windows下实现备份程序实例
Jul 04 Python
Python文件操作类操作实例详解
Jul 11 Python
详细分析python3的reduce函数
Dec 05 Python
python实现kNN算法
Dec 20 Python
DataFrame中的object转换成float的方法
Apr 10 Python
pandas 按照特定顺序输出的实现代码
Jul 10 Python
python调用pyaudio使用麦克风录制wav声音文件的教程
Jun 26 Python
Python中typing模块与类型注解的使用方法
Aug 05 Python
python 多维高斯分布数据生成方式
Dec 09 Python
如何定义TensorFlow输入节点
Jan 23 Python
django admin 添加自定义链接方式
Mar 11 Python
Python之字典添加元素的几种方法
Sep 30 Python
Python requests库参数提交的注意事项总结
Python爬虫爬取全球疫情数据并存储到mysql数据库的步骤
Python爬虫数据的分类及json数据使用小结
Mar 29 #Python
python re模块和正则表达式
Mar 24 #Python
opencv实现图像几何变换
PyQt QMainWindow的使用示例
Mar 24 #Python
PyQt 如何创建自定义QWidget
Mar 24 #Python
You might like
PHP定时执行任务实现方法详解(Timer)
2015/07/30 PHP
php curl发送请求实例方法
2019/08/01 PHP
javascript实现的鼠标链接提示效果生成器代码
2007/06/28 Javascript
JavaScript 高级篇之函数 (四)
2012/04/07 Javascript
使表格的标题列可左右拉伸jquery插件封装
2014/11/24 Javascript
深入分析Javascript跨域问题
2015/04/17 Javascript
js实现上传图片及时预览
2016/05/07 Javascript
JavaScript中style.left与offsetLeft的使用及区别详解
2016/06/08 Javascript
Bootstrap CSS组件之下拉菜单(dropdown)
2016/12/17 Javascript
搭建简单的nodejs http服务器详解
2017/03/09 NodeJs
详解Angular 中 ngOnInit 和 constructor 使用场景
2017/06/22 Javascript
js简单的分页器插件代码实例
2019/09/11 Javascript
python中函数默认值使用注意点详解
2016/06/01 Python
详谈python http长连接客户端
2017/06/12 Python
Python简单计算给定某一年的某一天是星期几示例
2018/06/27 Python
Django基础知识与基本应用入门教程
2018/07/20 Python
pandas通过字典生成dataframe的方法步骤
2019/07/23 Python
基于python使用tibco ems代码实例
2019/12/20 Python
Python基于当前时间批量创建文件
2020/05/07 Python
python实现人像动漫化的示例代码
2020/05/17 Python
浅谈python 调用open()打开文件时路径出错的原因
2020/06/05 Python
Python 利用flask搭建一个共享服务器的步骤
2020/12/05 Python
Pycharm在指定目录下生成文件和删除文件的实现
2020/12/28 Python
详解HTML5中表单验证的8种方法介绍
2016/12/19 HTML / CSS
html5 Canvas实现图片旋转的示例
2018/01/15 HTML / CSS
HTML5 manifest离线缓存的示例代码
2018/08/08 HTML / CSS
戴森美国官网:Dyson美国
2016/09/11 全球购物
省优秀教师事迹材料
2014/01/30 职场文书
公益活动邀请函
2014/02/05 职场文书
优秀广告词大全
2014/03/19 职场文书
服务质量承诺书
2014/03/27 职场文书
最新离婚协议书范本
2014/08/19 职场文书
全国优秀教师事迹材料
2014/08/26 职场文书
政府领导干部个人对照检查材料思想汇报
2014/09/24 职场文书
法学专业求职信范文
2015/03/19 职场文书
Java图书管理系统,课程设计必用(源码+文档)
2021/06/30 Java/Android