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使用线程封装的一个简单定时器类实例
May 16 Python
Python 两个列表的差集、并集和交集实现代码
Sep 21 Python
Python探索之爬取电商售卖信息代码示例
Oct 27 Python
python实现SOM算法
Feb 23 Python
Python实现删除排序数组中重复项的两种方法示例
Jan 31 Python
如何使用pyinstaller打包32位的exe程序
May 26 Python
python实现雪花飘落效果实例讲解
Jun 18 Python
Django框架中序列化和反序列化的例子
Aug 06 Python
django中上传图片分页三级联动效果的实现代码
Aug 30 Python
python实现指定ip端口扫描方式
Dec 17 Python
Python如何爬取b站热门视频并导入Excel
Aug 10 Python
python -v 报错问题的解决方法
Sep 15 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中的路径问题与set_include_path使用介绍
2014/02/11 PHP
PHP自毁程序(慎用)
2015/07/09 PHP
Thinkphp5.0框架的Db操作实例分析【连接、增删改查、链式操作等】
2019/10/11 PHP
Jquery乱码的一次解决过程 图解教程
2010/02/20 Javascript
用Javascript评估用户输入密码的强度(Knockout版)
2011/11/30 Javascript
javascript创建createXmlHttpRequest对象示例代码
2014/02/10 Javascript
JavaScript用JQuery呼叫Server端方法示例代码
2014/09/03 Javascript
JavaScript实现强制重定向至HTTPS页面
2015/06/10 Javascript
js弹出窗口返回值的简单实例
2016/05/28 Javascript
轻松实现jquery选项卡切换效果
2016/10/10 Javascript
JavaScript中关于for循环删除数组元素内容时出现的问题
2016/11/21 Javascript
div实现自适应高度的textarea实现angular双向绑定
2017/01/08 Javascript
深入理解AngularJS中的ng-bind-html指令
2017/03/27 Javascript
js实现扫雷小程序的示例代码
2017/09/27 Javascript
AngularJS监听ng-repeat渲染完成的两种方法
2018/01/16 Javascript
使用vue-router beforEach实现判断用户登录跳转路由筛选功能
2018/06/25 Javascript
详解用JS添加和删除class类名
2019/03/25 Javascript
JS实现随机点名器
2020/04/12 Javascript
[02:59]DOTA2完美大师赛主赛事第三日精彩集锦
2017/11/25 DOTA
[48:11]完美世界DOTA2联赛 Magma vs GXR 第二场 11.07
2020/11/10 DOTA
Python中用format函数格式化字符串的用法
2015/04/08 Python
Python输出\u编码将其转换成中文的实例
2018/12/15 Python
Python并发:多线程与多进程的详解
2019/01/24 Python
在python3中实现更新界面
2020/02/21 Python
关于python 跨域处理方式详解
2020/03/28 Python
Jupyter notebook快速入门教程(推荐)
2020/05/18 Python
python给视频添加背景音乐并改变音量的具体方法
2020/07/19 Python
如何让pre和textarea等HTML元素去掉滚动条自动换行自适应文本内容高度
2019/08/01 HTML / CSS
羽毛球社团活动总结
2014/06/27 职场文书
项目建议书
2015/02/04 职场文书
2016国庆节67周年寄语
2015/12/07 职场文书
煤矿安全生产管理协议书
2016/03/22 职场文书
Vue鼠标滚轮滚动切换路由效果的实现方法
2021/08/04 Vue.js
Python制作表白爱心合集
2022/01/22 Python
Vue监视数据的原理详解
2022/02/24 Vue.js
前端传参数进行Mybatis调用mysql存储过程执行返回值详解
2022/08/14 MySQL