有界面的python音乐播放器(可下载音乐)

tkinter+pygame+spider实现音乐播放器

1.确定页面

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()

在这里插入图片描述

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页