python基於tkinter制作無損音樂下載工具(附源碼)

繼續寫GUI,本次依然使用Tkinter設計一款圖形界面,使用Tkinter做一款音樂下載軟件,聽起來聽平常的,但是我這款軟件能夠下載 無損音樂下載軟件,聽起來不錯吧,Let`s go!

一.準備工作

python Tkinter

二.預覽

1.搜索

2.下載

3.結果

無損音樂就這樣下載完瞭。

三.詳細設計

這裡僅展示我設計的整體思路。

四.源代碼

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基於tkinter制作無損音樂下載工具(附源碼)的詳細內容,更多關於python制作音樂下載工具的資料請關註WalkonNet其它相關文章!

推薦閱讀: