python音乐播放器 python实现可下载音乐的音乐播放器
Minions__ 人气:01.确定页面
SongSheet ------ 显示歌单
MusicCtrl ------显示音乐一些控件(播放,跳转,音量调节)
SearchWindows ------搜索栏(搜索歌曲默认显示20条,可下载)
songSheet.py
#!/usr/bin/env python # -*- coding:utf-8 -*- # @Author: Minions # @Date: 2019-11-24 19:51:16 # @Last Modified by: Minions # @Last Modified time: 2019-12-17 10:01:53 import tkinter import os from tkinter import ttk import time class SongSheet(tkinter.Frame): def __init__(self, master): self.frame = tkinter.Frame(master, height=230, width=300, bd=1, bg="SkyBlue") self.frame.place(x=0, y=0) self.filePath = "C:\Musics" self.music = "" # 点击歌曲获得更新的路径 self.count = 0 # 计数,共多少歌曲 def run(self): # 搜索按钮 searchBtn = tkinter.Button(self.frame, text="更新", bg="SkyBlue", command=self.showSheet, width=10, height=1) searchBtn.place(x=0, y=200) # 显示歌单 def showSheet(self): self.count = 0 musics = os.listdir(self.filePath) tree = ttk.Treeview(self.frame) # 定义列 tree["columns"] = ("song") # 设置列,列还不显示 tree.column("song", width=95) # 设置表头 和上面一一对应 tree.heading("song", text="song") # 添加数据 往第0行添加 for music in musics: # 去除空格 music = "".join(music.split(" ")) tree.insert("", 0, text=self.count, values=(music)) self.count += 1 # 鼠标选中一行回调 def selectTree(event): for item in tree.selection(): item_text = tree.item(item, "values") self.music = "".join(item_text) # print(self.music) # 选中行 tree.bind('<<TreeviewSelect>>', selectTree) tree.place(width=300, height=200, x=0, y=0) # 添加滚动条 sy = tkinter.Scrollbar(tree) sy.pack(side=tkinter.RIGHT, fill=tkinter.Y) sy.config(command=tree.yview) tree.config(yscrollcommand=sy.set)
2.写出音乐控件
musicCtrl.py
#!/usr/bin/env python # -*- coding:utf-8 -*- # @Author: Minions # @Date: 2019-11-24 16:28:18 # @Last Modified by: Minions # @Last Modified time: 2019-12-17 10:25:31 import tkinter from tkinter import ttk import os import time import pygame from mutagen.mp3 import MP3 import random from songSheet import SongSheet class MusicCtrl(object): def __init__(self, master): self.frame = tkinter.Frame(master,height=150, width=700, bd=1, bg="MediumSeaGreen") self.frame.place(height=150, width=700, x=0, y=250) self.nowPaly = True # 是否正在播放音乐 self.filePath = r"C:\Musics" # 从该文件夹读取 self.musicPath = "" # 用于拼接音乐的路径 self.songSheet = SongSheet(master) self.songSheet.run() self.music = os.path.join(self.filePath,self.musicPath) # 音乐的路径 # 整合功能 def run(self): self.playMusic() self.refreshName() self.pauseMusic() self.volume() try: self.songPos() except: print("暂无歌曲载入!") # 播放音乐按钮 def playMusic(self): playBtn = tkinter.Button(self.frame, text="播放", command=self.playFunc, width=10,height=2) playBtn.place(x=300,y=10) # 实现播放功能 def playFunc(self): pygame.mixer.init() track = pygame.mixer.music.load(self.music) # 载入一个音乐文件用于播放 pygame.mixer.music.play() # 开始播放音乐流 # 暂停播放按钮 def pauseMusic(self): pauseBtn = tkinter.Button(self.frame, text="暂停/继续", command=self.pauseFunc, width=10, height=2) pauseBtn.place(x=400, y=10) # 暂停播放功能 def pauseFunc(self): # pygame.mixer.music.get_busy() # 检测是否正在播放音乐 if self.nowPaly: pygame.mixer.music.pause() self.nowPaly = False else: pygame.mixer.music.unpause() # 恢复音乐播放 self.nowPaly = True # 显示歌曲名称以及歌手 def showName(self): songName = tkinter.Label(self.frame, fg="white",font=("华文行楷", 10),bg="MediumSeaGreen", width=25, height=1) songName['text'] = self.songSheet.music.split('.')[0] songName.place(x=35,y=15) self.music = os.path.join(self.filePath,self.songSheet.music) # 更换音乐后应该继续播放,并且更换音乐时长 self.playFunc() self.songPos() # 音量调节 def volume(self): volumeNum = tkinter.Label(self.frame, text="volume", fg="Aquamarine", font=("华文行楷", 10), bg="MediumSeaGreen", width=5, height=1) volumeNum.place(x=500, y=70) volume = tkinter.Scale(self.frame, from_=0, to=100, orient=tkinter.HORIZONTAL) volume.place(x=550,y=50) def showNum(): pygame.mixer.music.set_volume(volume.get()*0.01) # 参数值范围为 0.0~1.0 tkinter.Button(self.frame, text="设置", command=showNum, bg="Aqua").place( x=550, y=100) # 音乐绝对定位 def songPos(self): # print(self.music.info.length) pos = tkinter.Scale(self.frame, from_=0, to=round( MP3(self.music).info.length), orient=tkinter.HORIZONTAL, tickinterval=50, length=300) pos.place(x=180, y=60) def showNum(): # 为了对一个 MP3 文件的进行绝对定位,建议首先调用 rewind()函数,不然会一直往后走 pygame.mixer.music.rewind() if pygame.mixer.music.get_busy(): self.curDuration = pos.get() pygame.mixer.music.set_pos(self.curDuration) else: print("请先播放音乐!") tkinter.Button(self.frame, text="设置", command=showNum, bg="Aqua").place( x=490, y=90) # 点击歌单的歌更新名称 def refreshName(self): refreshNameBtn = tkinter.Button(self.frame, text="update",command=self.showName, width=10, height=2) refreshNameBtn.place(x=45, y=50)
3.核心爬取音乐
music.py
# -*- coding:utf-8 -*- import requests, hashlib, sys, click, re, base64, binascii, json, os from Cryptodome.Cipher import AES from http import cookiejar class Encrypyed(): """ 解密算法 """ def __init__(self): self.modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7' self.nonce = '0CoJUm6Qyw8W8jud' self.pub_key = '010001' # 登录加密算法, 基于https://github.com/stkevintan/nw_musicbox脚本实现 def encrypted_request(self, text): text = json.dumps(text) sec_key = self.create_secret_key(16) enc_text = self.aes_encrypt(self.aes_encrypt(text, self.nonce), sec_key.decode('utf-8')) enc_sec_key = self.rsa_encrpt(sec_key, self.pub_key, self.modulus) data = {'params': enc_text, 'encSecKey': enc_sec_key} return data def aes_encrypt(self, text, secKey): pad = 16 - len(text) % 16 text = text + chr(pad) * pad encryptor = AES.new(secKey.encode('utf-8'), AES.MODE_CBC, b'0102030405060708') ciphertext = encryptor.encrypt(text.encode('utf-8')) ciphertext = base64.b64encode(ciphertext).decode('utf-8') return ciphertext def rsa_encrpt(self, text, pubKey, modulus): text = text[::-1] rs = pow(int(binascii.hexlify(text), 16), int(pubKey, 16), int(modulus, 16)) return format(rs, 'x').zfill(256) def create_secret_key(self, size): return binascii.hexlify(os.urandom(size))[:16] class Song(): """ 歌曲对象,用于存储歌曲的信息 """ def __init__(self, song_id, song_name, song_num, picUrl, singer_name, song_url=None): self.song_id = song_id self.song_name = song_name self.song_num = song_num self.singer_name = singer_name self.picUrl = picUrl self.song_url = '' if song_url is None else song_url class Crawler(): """ 网易云爬取API """ def __init__(self, timeout=60, cookie_path='.'): self.headers = { 'Accept': '*/*', 'Accept-Encoding': 'gzip,deflate,sdch', 'Accept-Language': 'zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4', 'Connection': 'keep-alive', 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'music.163.com', 'Referer': 'http://music.163.com/search/', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36' } self.session = requests.Session() self.session.headers.update(self.headers) self.session.cookies = cookiejar.LWPCookieJar(cookie_path) self.download_session = requests.Session() self.timeout = timeout self.ep = Encrypyed() self.result =[] def post_request(self, url, params): """ Post请求 :return: 字典 """ data = self.ep.encrypted_request(params) resp = self.session.post(url, data=data, timeout=self.timeout) result = resp.json() if result['code'] != 200: click.echo('post_request error') else: return result def search(self, search_content, search_type, limit=9): """ 搜索API :params search_content: 搜索内容 :params search_type: 搜索类型 :params limit: 返回结果数量 :return: 字典. """ url = 'http://music.163.com/weapi/cloudsearch/get/web?csrf_token=' params = {'s': search_content, 'type': search_type, 'offset': 0, 'sub': 'false', 'limit': limit} result = self.post_request(url, params) # print(result['result']['songs'][3]['ar'][0]['name']) return result def search_song(self, song_name, song_num, quiet=True, limit=20): """ 根据音乐名搜索 :params song_name: 音乐名 :params song_num: 下载的歌曲数 :params quiet: 自动选择匹配最优结果 :params limit: 返回结果数量 :return: Song独享 """ result = self.search(song_name, search_type=1, limit=limit) if result['result']['songCount'] <= 0: click.echo('Song {} not existed.'.format(song_name)) else: songs = result['result']['songs'] if quiet: self.result = [] # 更新result for song in songs: singers = [] # """ picUrl = song['al']['picUrl'] # """ for name in song['ar']: singers.append(name['name']) song_id, song_name = song['id'], song['name'] singer_name = "_".join(singers) song = Song(song_id=song_id, song_name=song_name, song_num=song_num, singer_name=singer_name,picUrl=picUrl) self.result.append(song) picUrl = songs[0]['al']['picUrl'] # """ song_id, song_name = songs[0]['id'], songs[0]['name'] song = Song(song_id=song_id, song_name=song_name, song_num=song_num, singer_name=self.result[0].singer_name, picUrl=picUrl) return song def get_song_url(self, song_id, bit_rate=320000): """ 获得歌曲的下载地址 :params song_id: 音乐ID<int>. :params bit_rate: {'MD 128k': 128000, 'HD 320k': 320000} :return: 歌曲下载地址 """ url = 'http://music.163.com/weapi/song/enhance/player/url?csrf_token=' csrf = '' params = {'ids': [song_id], 'br': bit_rate, 'csrf_token': csrf} result = self.post_request(url, params) # 歌曲下载地址 song_url = result['data'][0]['url'] # 歌曲不存在 if song_url is None: click.echo('Song {} is not available due to copyright issue.'.format(song_id)) else: return song_url def get_song_by_url(self, song_url, song_name, song_num, singer_name, folder): """ 下载歌曲到本地 :params song_url: 歌曲下载地址 :params song_name: 歌曲名字 :params song_num: 下载的歌曲数 :params folder: 保存路径 """ # for res in self.result: # print(res.song_name, res.song_id, res.singer_name) # print("--------") # print(song_url, song_name, singer_name) class Netease(): """ 网易云音乐下载 """ def __init__(self, timeout, folder, quiet, cookie_path): self.crawler = Crawler(timeout, cookie_path) self.folder = '.' if folder is None else folder self.quiet = quiet self.url = '' self.pic = '' def download_song_by_search(self, song_name): """ 根据歌曲名进行搜索 :params song_name: 歌曲名字 :params song_num: 下载的歌曲数 """ try: song = self.crawler.search_song(song_name, self.quiet) except: click.echo('download_song_by_serach error') # 如果找到了音乐, 则下载 if song != None: self.download_song_by_id(song.song_id, song.song_name, song.song_num, song.singer_name, self.folder) self.pic = song.picUrl def download_song_by_id(self, song_id, song_name, song_num, singer_name, folder='.'): """ 通过歌曲的ID下载 :params song_id: 歌曲ID :params song_name: 歌曲名 :params song_num: 下载的歌曲数 :params folder: 保存地址 """ try: url = self.crawler.get_song_url(song_id) # 去掉非法字符 song_name = song_name.replace('/', '') song_name = song_name.replace('.', '') self.crawler.get_song_by_url(url, song_name, song_num, singer_name, folder) except: click.echo('download_song_by_id error')
4.将爬取音乐搜索栏整合
searchWindows.py
#!/usr/bin/env python # -*- coding:utf-8 -*- # @Author: Minions # @Date: 2019-11-25 10:31:56 # @Last Modified by: Minions # @Last Modified time: 2019-12-17 12:40:31 import tkinter from tkinter import ttk import os from urllib import request from music import Netease,Crawler import requests class SearchWindows(tkinter.Frame): def __init__(self, master): self.frame = tkinter.Frame(master, height=240, width=500, bd=1, bg="Purple") self.songs = None # 搜索到的所有歌曲(20)的信息 self.frame.place(x=300,y=0) self.info = None # 当前歌曲的信息 self.fileName = "C:\Musics\\" timeout = 60 output = 'Musics' quiet = True cookie_path = 'Cookie' self.netease = Netease(timeout, output, quiet, cookie_path) def run(self): self.searchBar() self.download() # 搜索框 def searchBar(self): entry = tkinter.Entry(self.frame) entry.place(width=200, height=30, x=50, y=10) def getValue(): self.netease.download_song_by_search(entry.get()) self.songs = self.netease.crawler.result self.showSong() searchBtn = tkinter.Button(self.frame, text="搜索", bg="DarkOrchid", command=getValue, width=10, height=1) searchBtn.place(x=270, y=10) # 显示搜索到的歌曲 def showSong(self): tree = ttk.Treeview(self.frame) # 定义列 tree["columns"] = ("song", "singer", "url") # 设置列,列还不显示 tree.column("song", width=50) tree.column("singer", width=50) tree.column("url", width=50) # 设置表头 和上面一一对应 tree.heading("song", text="song") tree.heading("singer", text="singer") tree.heading("url", text="url") count = len(self.songs) for song in reversed(self.songs): url = self.netease.crawler.get_song_url(song.song_id) tree.insert("", 0, text=count, values=(song.song_name, song.singer_name, url)) count -= 1 # 鼠标选中一行回调 def selectTree(event): for item in tree.selection(): item_text = tree.item(item, "values") self.info = item_text # 滚动条 sy = tkinter.Scrollbar(tree) sy.pack(side=tkinter.RIGHT, fill=tkinter.Y) sy.config(command=tree.yview) tree.config(yscrollcommand=sy.set) # 选中行 tree.bind('<<TreeviewSelect>>', selectTree) tree.place(width=300, height=200, x=50, y=50) # 下载选中的歌曲 def download(self): def downloadSong(): if self.info is None: print("该歌曲下载失败") else: request.urlretrieve(self.info[2], self.fileName+self.info[1]+'-'+self.info[0]+'.mp3') print("%s-%s下载成功" %(self.info[1], self.info[0])) # 下载按钮 downloadBtn = tkinter.Button(self.frame, text="下载", bg="DarkOrchid", command=downloadSong, width=6, height=1) downloadBtn.place(x=345, y=200)
5.整合所有部分
main.py
#!/usr/bin/env python # -*- coding:utf-8 -*- # @Author: Minions # @Date: 2019-11-24 20:10:15 # @Last Modified by: Minions # @Last Modified time: 2019-12-17 9:55:31 import tkinter from searchWindows import SearchWindows from musicCtrl import MusicCtrl from songSheet import SongSheet import os win = tkinter.Tk() win.title("Minions音乐播放器") win.geometry("700x400") if os.path.exists("C:/Musics"): print("xxx") else: os.mkdir("C:/Musics") searchWin = SearchWindows(win) searchWin.run() songSheetWin = SongSheet(win) songSheetWin.run() musicWin = MusicCtrl(win) musicWin.run() win.mainloop()
加载全部内容