当前位置: 首页 > 工具软件 > Gooey > 使用案例 >

python之Gooey图形界面使用

广乐邦
2023-12-01

楔子、前言

最近一个项目需要用到python图形界面的快速开发,还要求在我们可以看到的代码上看不到类和对象的使用痕迹,这里直接先把pyqt排除了,pyqt臃肿且复杂,比较适合大型界面开发,小型界面开发再使用这个东西,学习成本极大(因为无法使用designer工具,这个designer生成的代码是有的)。然后这里考虑使用tkinter或者gooey,这里我先尝试了tkinter,感觉不是特别美观,那就直接再次尝试Gooey这个框架的使用。

开发的是一个python图形界面的网易云音乐下载播放器,播放为直接运行(使用本地默认音乐播放器)。网易云的API集成则是在我的个人服务器上面。

局限性

这个框架是基于另一个框架wxpython的,属于阉割版本,相比于wxpython也许功能会少一些,但是我这里用的正好合适,勉强可以凑活着用。但是虽然组件有点少,但是正好使用其他框架的组件来代替也不是不可以。而且这个Gooey好像没有页面跳转,稍显生硬,目前没发现怎么将执行完程序后的界面进行改动,如果能改动也会更好一些,总之对于这个项目能用就行了。

一、实现功能

开发周期太短,所以做出来的内容也很简单,只预算使用不到3个小时开发出来。

  1. 一个搜索框
  2. 两个搜索按钮(一个歌单、一个单曲)
  3. 一个list控件(放搜索结果)
  4. 一个下载按钮
  5. 一个播放按钮

二、图形界面开发

首先进行一个Gooey库的安装

pip install Gooey

搜索结果直接通过程序执行后出现的界面输出了,省了一点麻烦,但是用户使用的时候又多了一点麻烦,但是没办法,没找到list空间来放搜索结果。
左侧为command选择,相当于菜单项;右侧为具体的选项。

三、遇到的问题

1.界面获取的数据如何使用

界面获得的数据会被存放在一个namespace空间内部,这个人namespace的数据使用方式为args.变量名,直接就能把数据给点出来了。

2.与用户交互

因为这个框架似乎没有实现按钮功能,所以怎么与用户交互呢?因为程序执行时的控制台输出会被转到这个框架内显示出来,所以呈现给用户这个我们的响应还是很简单的,直接print出数据即可。

3.程序出错

如果程序运行出错了的话,程序就会直接结束运行,而不会给用户有提示,这里可以考虑使用python的异常处理其他框架的弹出交互框

四、代码如下

我的代码如下:

# WindowUi.py
import time

from gooey import Gooey, GooeyParser
import CloudMusicDownloaderAndPlayer


@Gooey(
    richtext_controls=True,  # 打开终端对颜色支持
    program_name="网易云下载播放器",  # 程序名称
    encoding="utf-8",  # 设置编码格式,打包的时候遇到问题
    progress_regex=r"^progress: (\d+)%$"  # 正则,用于模式化运行时进度信息
)
def main():
    settings_msg = '本程序提供对网易云音乐的歌单、单曲的搜索;单曲的下载与播放。\n使用网易云官方公开的API,API服务部署在我的个人服务器上(将于2022年3月份过期)'
    parser = GooeyParser(description=settings_msg)  # 添加上方的应用信息

    subs = parser.add_subparsers(help='commands', dest='command')

    """
    搜索界面
    """
    Searchparser = subs.add_parser('搜索')
    Searchparser.add_argument("SearchKeyWords", metavar='搜索关键词', default='牵丝戏')

    verbosity = Searchparser.add_mutually_exclusive_group("搜索类型选择")
    verbosity.add_argument('-t', '--verbozze', dest='单曲',
                           action="store_true", help="搜索类型:单曲")
    verbosity.add_argument('-q', '--quiet', dest='歌单',
                           action="store_true", help="搜索类型:歌单")

    """
    获取歌单内单曲界面
    """
    Leftparser = subs.add_parser('歌单详情')
    Leftparser.add_argument("GotPlayListId", metavar='歌单id', help="请输入上面搜索后获得的歌单id", default='30352891')

    """
    下载播放界面
    """
    Leftparser = subs.add_parser('下载播放')
    Leftparser.add_argument("GotMusicId", metavar='歌曲id', help="请输入你想下载播放的歌曲id", default='30352891')

    """
    播放界面
    """
    Leftparser = subs.add_parser('播放')

    """
    程序功能实现
    """
    args = parser.parse_args()

    """界面获得的数据进行处理"""
    global GlobalMusicId, GlobalMusicName
    if args.command == "搜索":
        inputSearchKeywords = args.SearchKeyWords
        if args.单曲 == True:

            searchType, searchKeywords = CloudMusicDownloaderAndPlayer.search("单曲:" + inputSearchKeywords)
            songIds, songNames, singers = CloudMusicDownloaderAndPlayer.getSingleMusicSearchResult(searchType,
                                                                                                   searchKeywords)

        else:
            searchType, searchKeywords = CloudMusicDownloaderAndPlayer.search("歌单:" + inputSearchKeywords)
            CloudMusicDownloaderAndPlayer.getPlayListSearchResult(searchType, searchKeywords)
    elif args.command == "歌单详情":
        songIds, songNames, singers = CloudMusicDownloaderAndPlayer.getMusicsDetail(args.GotPlayListId)
        for i in range(len(songIds)):
            print(songIds[i], songNames[i], singers[i])
    elif args.command == "下载播放":
        try:
            tmp = args.GotMusicId
            if tmp != "":
                GlobalMusicId = int(tmp)

            songUrl, songType = CloudMusicDownloaderAndPlayer.getMusicUrl(GlobalMusicId)
            GlobalMusicName = CloudMusicDownloaderAndPlayer.getMusicName(GlobalMusicId)
            songFileName = CloudMusicDownloaderAndPlayer.downloadMusic(GlobalMusicName, songUrl, songType)
            print("歌曲下载成功,地址为:", songFileName)
        except:
            print("歌曲下载错误,请检查您的输入是否正确!")
            pass
    elif args.command == "播放":
        print("请选择你想要播放的歌曲")
        time.sleep(1)
        CloudMusicDownloaderAndPlayer.playMusic()
    else:
        print("非常感谢您的使用,期待您的下次使用。再见!")
        time.sleep(1)
        return
    # print(args, flush=True)  # 坑点:flush=True在打包的时候会用到


if __name__ == '__main__':
    main()

另一个文件内容为:

"""
打包:pyinstaller -F --icon=icon.ico CloudMusicDownloaderAndPlayer.py
传递参数的时候带着格式,参数格式如下:
name: 前两个字为格式,用来判断搜索的是歌曲还是歌单
"""
import os
import requests
import tkinter
from tkinter import filedialog
import time

baseUrl = "http://m4xlmum.top:3000"

help = """《小工具使用指南》
  单曲搜索:
    1. 搜索单曲(歌单)->搜索单曲->输入你想搜索的歌曲关键词->根据搜索到的歌曲id的输出输入你想下载播放的歌曲->下载(后播放)歌曲->输入你想下载的歌曲的id。
  歌单搜索:
    2. 搜索单曲(歌单)->搜索歌单->输入你想搜索的歌单关键词->根据搜索到的歌单id的输出输入你想具体查看的歌单的详细信息(包括歌单内所包含的歌曲额详细信息)>之后的流程就跟单曲的操作相同了。
"""

mainMenu = """*******************
  1. 搜索单曲(歌单)
  2. 下载(后播放)歌曲
  3. 播放歌曲
  0. 退出
*******************
[+]请输入你的选择:"""

searchMenu = """*******************
  1. 搜索单曲
  2. 搜索歌单
  3. 返回上级选择
*******************
[+]请输入你的选择:"""

# 存放用户经过搜索处理之后选择的歌曲id
GlobalMusicId = 30352891  # 牵丝戏 ['银临', 'Aki阿杰']
GlobalMusicName = "牵丝戏"


# P1搜索处理,返回信息为{搜索类型、搜索关键词}
def search(name):
    """
    type: 搜索类型;默认为 1 即单曲 , 取值意义 : 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频, 1018:综合
    """
    searchType = name[:2]
    searchKeywords = name[3:]
    typeDict = {"单曲": 1, "歌单": 1000}
    return typeDict[searchType], searchKeywords


# 有songs格式的数据获得`songIds, songNames, singers`这几个数据
def getSongsInfo(songs):
    songIds, songNames, singers = [], [], []
    for i in range(len(songs)):
        song = songs[i]
        songId = song["id"]
        songName = song["name"]

        # 设置歌手名的格式
        singer = "歌手:"
        artists = "artists" if "artists" in song.keys() else "ar"
        for artist in song[artists]:
            singer = singer + artist["name"] + "、"

        print(songId, songName, singer)
        # 获取的数据,加入list
        songIds.append(songId)
        songNames.append(songName)
        singers.append(singer)
    return songIds, songNames, singers


# P2单曲处理,返回信息包括{歌曲id、歌曲名、歌手}
def getSingleMusicSearchResult(searchType, searchKeywords):
    searchUrl = baseUrl + "/search"
    """
        type:    搜索类型
        limit:   限制返回个数
        keywords:搜索关键词
    """
    params = {
        "type": searchType,
        "limit": 10,
        "keywords": searchKeywords
    }
    resp = requests.get(url=searchUrl, params=params).json()
    songs = resp["result"]["songs"]
    songIds, songNames, singers = getSongsInfo(songs)
    return songIds, songNames, singers


# P2歌单处理,返回值为{歌单包含歌曲id}   注:歌单是为了区别歌曲类型,所以这里搜索歌单只返回一个歌单值,歌单内包含很多歌曲
def getPlayListSearchResult(searchType, searchKeywords):
    searchUrl = baseUrl + "/search"
    params = {
        "type": searchType,
        "limit": 10,
        "keywords": searchKeywords
    }
    resp = requests.get(url=searchUrl, params=params).json()
    playLists = resp["result"]["playlists"]
    playListIds, playListNames, creators, descriptions = [], [], [], []
    for i in range(len(playLists)):
        playList = playLists[i]
        id, name, creator = playList["id"], playList["name"], playList["creator"]["nickname"] if playList[
                                                                                                     "creator"] is not None else "未找到Creator"

        print("[-] id:{0}\tname:{1}\tcreator:{2}".format(id, name, creator))
    playListId = playLists[0]["id"]  # 获取搜索到的第一个歌单的id
    return playListId


def getPlayListDetail(playListId):

    playListDetailUrl = baseUrl + "/playlist/detail"
    params = {
        "id": playListId
    }
    playListDetail = requests.get(url=playListDetailUrl, params=params).json()  # 获取歌单详情
    print("该歌单的描述为: " + playListDetail["playlist"]["description"] if playListDetail["playlist"][
                                                                         "description"] is not None else "该歌单无描述" + "\n")
    playListMusics = playListDetail["playlist"]["trackIds"]  # 获取歌单内的所有音乐id,此时获得的还不是id,只是一个包含id的待处理数据集
    playListMusicIds = []  # 获取歌单内所有音乐的id
    # print(len(playListMusics))
    for i in range(len(playListMusics)):
        playListMusicIds.append(playListMusics[i]["id"])
    # print(playListMusicIds)
    return playListMusicIds


# P2扩展,获取歌单内的歌曲信息
def getMusicsDetail(playListId):
    playListMusicIds = getPlayListDetail(playListId)
    playListDetailUrl = baseUrl + "/song/detail"
    params = {
        "ids": str(playListMusicIds)[1:-1]
    }
    resp = requests.get(url=playListDetailUrl, params=params).json()
    songs = resp["songs"]
    songIds, songNames, singers = getSongsInfo(songs)
    return songIds, songNames, singers


def getMusicUrl(songId):
    musicUrl = baseUrl + "/song/url"
    params = {
        "id": songId
    }
    resp = requests.get(url=musicUrl, params=params).json()
    songUrl = resp["data"][0]["url"]
    songType = resp["data"][0]["type"]
    return songUrl, songType


# 歌曲的命名格式应为"牵丝戏 歌手:银临、Aki阿杰"
def getMusicName(songId):
    musicUrl = baseUrl + "/song/detail"
    params = {
        "ids": songId
    }
    resp = requests.get(url=musicUrl, params=params).json()
    song = resp["songs"][0]
    songName = song["name"]

    # 加上歌手名
    songName += " 歌手:"
    for i in range(len(song["ar"])):
        songName = songName + song["ar"][i]["name"] + "、"
    songName = songName[:-3]
    return songName


def downloadMusic(songName, songUrl, songType="mp3"):
    # 设置文件的保存路径
    root = tkinter.Tk()
    root.withdraw()
    folder = filedialog.askdirectory()
    songFileName = "{0}.{1}".format(songName, songType)

    Filepath = r"{0}/{1}".format(folder, songFileName)
    with open(Filepath, "wb") as file:
        musicContent = requests.get(url=songUrl).content
        file.write(musicContent)
    os.system('"' + Filepath + '"')
    return Filepath


def playMusic():
    root = tkinter.Tk()
    root.withdraw()
    # 设置文件的保存路径
    Filepath = filedialog.askopenfilename()  # 获得选择好的文件
    # 使用本机默认播放器播放音频
    os.system('"' + Filepath + '"')


def playerMenu():
    global GlobalMusicId, GlobalMusicName
    while True:
        inputChoice = input(mainMenu)
        if inputChoice == "1":
            inputSearchChoice = input(searchMenu)
            if inputSearchChoice == "1":
                inputSearchKeywords = input("[+] 请输入您搜索的关键词:")
                searchType, searchKeywords = search("单曲:" + inputSearchKeywords)
                songIds, songNames, singers = getSingleMusicSearchResult(searchType, searchKeywords)
                tmp = input("请输入你想要进行后续播放下载的歌曲id(默认歌曲id为30352891):")
                try:
                    GlobalMusicId = int(tmp)
                    GlobalMusicName = songNames[songIds.index(GlobalMusicId)] + " " + singers[
                        songIds.index(GlobalMusicId)]
                except:
                    pass
            elif inputSearchChoice == "2":
                inputSearchKeywords = input("[+] 请输入您搜索的关键词:")
                searchType, searchKeywords = search("歌单:" + inputSearchKeywords)
                songIds, songNames, singers = getMusicsDetail(searchType, searchKeywords)
                for i in range(len(songIds)):
                    print(songIds[i], songNames[i], singers[i])
            else:
                pass
        elif inputChoice == "2":
            try:
                tmp = input("请输入要下载的歌曲的id(此处不输入,则默认使用搜索处输入的id):")
                if tmp != "":
                    GlobalMusicId = int(tmp)

                songUrl, songType = getMusicUrl(GlobalMusicId)
                GlobalMusicName = getMusicName(GlobalMusicId)
                songFileName = downloadMusic(GlobalMusicName, songUrl, songType)
                print("歌曲下载成功,地址为:", songFileName)
            except:
                print("歌曲下载错误,请检查您的输入是否正确!")
                pass
        elif inputChoice == "3":
            playMusic()
        else:
            print("非常感谢您的使用,期待您的下次使用。再见!")
            time.sleep(1)
            return


"""
if __name__ == '__main__':
    print(help)
    playerMenu()
"""

五、结束

这个软件工程的大作业到这里应该就告一段落了,写的代码还是太少了,但是也确实是工期太短了,而且我没多少时间用在这个东西上面,毕竟还有其他作业,其他项目,其他实验。

总之这300多行已经差不多是我的极限了,也确实是黔驴技穷了。

 类似资料: