最近一个项目需要用到python图形界面的快速开发,还要求在我们可以看到的代码上看不到类和对象
的使用痕迹,这里直接先把pyqt
排除了,pyqt臃肿且复杂,比较适合大型界面开发,小型界面开发再使用这个东西,学习成本极大(因为无法使用designer工具,这个designer生成的代码是有类
的)。然后这里考虑使用tkinter或者gooey,这里我先尝试了tkinter,感觉不是特别美观,那就直接再次尝试Gooey这个框架的使用。
开发的是一个python图形界面的网易云音乐下载播放器
,播放为直接运行(使用本地默认音乐播放器)。网易云的API集成则是在我的个人服务器上面。
这个框架是基于另一个框架wxpython的,属于阉割版本,相比于wxpython也许功能会少一些,但是我这里用的正好合适,勉强可以凑活着用。但是虽然组件有点少,但是正好使用其他框架的组件来代替也不是不可以。而且这个Gooey好像没有页面跳转,稍显生硬,目前没发现怎么将执行完程序后的界面进行改动,如果能改动也会更好一些,总之对于这个项目能用就行了。
开发周期太短,所以做出来的内容也很简单,只预算使用不到3个小时开发出来。
首先进行一个Gooey库的安装
pip install Gooey
搜索结果直接通过程序执行后出现的界面输出了,省了一点麻烦,但是用户使用的时候又多了一点麻烦,但是没办法,没找到list空间来放搜索结果。
左侧为command选择,相当于菜单项;右侧为具体的选项。
界面获得的数据会被存放在一个namespace空间内部,这个人namespace的数据使用方式为args.变量名
,直接就能把数据给点出来了。
因为这个框架似乎没有实现按钮功能,所以怎么与用户交互呢?因为程序执行时的控制台输出会被转到这个框架内显示出来,所以呈现给用户这个我们的响应还是很简单的,直接print
出数据即可。
如果程序运行出错了的话,程序就会直接结束运行,而不会给用户有提示,这里可以考虑使用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多行已经差不多是我的极限了,也确实是黔驴技穷了。