并没有怎么学习过Python,所以程序看起来写的很杂乱。但是也能大体上完成我期望的功能,最后的结果也蛮让我满意的。另外,软件并没有经历各种的测试,有问题还请包涵。先看效果:
一、界面显示
在界面设计中使用的是Python的Tkinter模块,该模块python自带,无需自己安装,并且体积小,使用起来也十分方便。
if __name__ == "__main__":
# 第1步,实例化object,建立窗口window
window = tk.Tk()
# 第2步,给窗口的可视化起名字
window.title('My Music')
# 第3步,设定窗口的大小(长 * 宽)
window.geometry('500x150') # 这里的乘是小x
window1 = tk.Frame(window) # 主窗口创建框架1
window1.pack()
window_one(window1) # 框架1具体配置
# 音乐名需要变化
musicName = tk.StringVar()
musicName.set("null")
# 播放按键需要变化
startText = tk.StringVar()
startText.set("播放")
# 进度需要变化
jindu = tk.StringVar()
jindu.set("00:00")
# 播放模式设置
mode = tk.StringVar()
mode.set("顺序")
window2 = tk.Frame(window) # 主窗口创建框架2
window_two(window2) # 框架2具体配置
# 禁止用户调整窗口大小
window.resizable(False, False)
# 第6步,主窗口循环显示
window.mainloop()
# 注意,loop因为是循环的意思,window.mainloop就会让window不断的刷新,如果没有mainloop,就是一个静态的window,传入进去的值就不会有循环,mainloop就相当于一个很大的while循环,有个while,每点击一次就会更新一次,所以我们必须要有循环
# 所有的窗口文件都必须有类似的mainloop函数,mainloop是窗口文件的关键的关键。
接下来是对主窗口的框架1具体配置信息,框架1主要用于选择歌曲文件夹,选择文件夹后会框架1就会被销毁,继而打开框架2。
def window_one(window1): # 加载音乐
var = tk.StringVar() # 将label标签的内容设置为字符类型,用var来接收hit_me函数的传出内容用以显示在标签上
var.set('请选择音乐所在文件夹')
# 第4步,在图形界面上设定标签
l = tk.Label(window1, textvariable=var, font=('Arial', 12), width=500, height=2)
# 说明: bg为背景,font为字体,width为长,height为高,这里的长和高是字符的长和高,比如height=2,就是标签有2个字符这么高
# 第5步,放置标签
l.pack(expand='True',pady='15') # Label内容content区域放置位置,自动调节尺寸
# 放置lable的方法有:1)l.pack(); 2)l.place();
# 第5步,在窗口界面设置放置Button按键
b = tk.Button(window1, text='选择文件夹', font=('Arial', 12), width=10, height=1, command=hit_me)
b.pack(expand='True')
下图是框架1界面图:
框架2用于歌曲的播放:
def window_two(window2):
global pro1
musicLabel = tk.Label(window2, textvariable=musicName, font=('Arial', 12), width=500, height=2)
musicLabel.pack(expand='True')
# 进度条显示部分
jinduLabel = tk.Label(window2, textvariable=jindu, font=('Arial', 10), width=10, height=2)
pro1 = ttk.Progressbar(window2, length=450, cursor='spider', mode="determinate", orient=tk.HORIZONTAL)
pro1.pack(padx='2')
jinduLabel.pack()
# 按键部分
start = tk.Button(window2, textvariable=startText, font=('Arial', 12), width=12, height=1, command=Tostart)
next = tk.Button(window2, text='下一曲', font=('Arial', 12), width=12, height=1, command=Tonext)
last = tk.Button(window2, text='上一曲', font=('Arial', 12), width=12, height=1, command=Tobefore)
setloop = tk.Button(window2, textvariable=mode, font=('Arial', 12), width=12, height=1, command=Toloop)
start.pack(side='left',padx='1',expand='True')
last.pack(side='left', padx='1',expand='True')
next.pack(side='left', padx='1', expand='True')
setloop.pack(side='left', padx='1',expand='True')
下图是框架2界面图:
二、功能模块
2.1 循环设置
设置循环模式就是每次设置好循环模式后将对应歌曲下标存储在数组中,每次在点击下一曲或者上一曲时只需要访问对应下标歌曲即可。
def Toloop():
global setMode,randomList,musicList
randomList = []
if setMode == 1: # 随机模式时
setMode = 2
mode.set("随机")
while True:
k = random.randint(0,len(musicNameList)-1) # 获取一个随机值
if k not in randomList: # 判断当前随机值没有在列表中
randomList.append(k)
if len(randomList) == len(musicList):
break
elif setMode == 2: # 单曲模式时
setMode = 3
mode.set("单曲")
randomList.append(musicNum)
else: # 顺序模式时
setMode = 1
mode.set("顺序")
for i in range(0,len(musicList)):
randomList.append(i)
print(randomList)
2.2 播放与暂停
播放与暂停只需要改变对应标志位即可,歌曲播放创建的线程一直判断对应标志位即可。
def Tostart():
global startFlag,threadFlag
thmusic = threading.Thread(target=startMusic, args=())
thmusic.setDaemon(True) # 守护线程
if not startFlag:
startFlag = True
startText.set("暂停")
if threadFlag == 0: # 线程运行时不能再次打开线程
thmusic.start()
else:
startFlag = False
startText.set("播放")
播放线程代码如下:
def startMusic():
global musicList,musicNameList,musicNum,randomList,nextFlag,lock,startFlag,threadFlag
pygame.mixer.init()
fflag = 0 # 标记是否为正常下一曲,即当前歌曲播放完成
threadFlag = 1 # 线程开始运行标记位
# 加载音乐
while True:
# 初始化标志位
fflag = 0
pro1['value'] = 0 # 进度条初始化
str_time = "00:00" # 播放时间显示初始化
jindu.set(str_time)
audio = MP3(musicList[randomList[musicNum]]) # 获取音乐时长
musiclen = int(audio.info.length) # 音乐长度
one = 100/musiclen # 刻度每次需要移动大小
pygame.mixer.music.load(musicList[randomList[musicNum]])
# 准备开始播放
pygame.mixer.music.play(start=0.0)
# 播放时长,没有此设置,音乐不会播放,会一次性加载完
for i in range(1,musiclen+1):
time.sleep(1)
lock.acquire()
flag = nextFlag
nextFlag = 0
staflag = startFlag # 播放标志位
lock.release()
if not staflag:
pygame.mixer.music.pause() # 暂停
print("zanting")
while True:
if startFlag: # 重新播放
pygame.mixer.music.unpause()
break
if nextFlag != 0: # 点击了上一曲或者下一曲
flag = nextFlag
nextFlag = 0
startFlag = True
break
if flag != 0: # 点击了下一曲或者上一曲 直接切换歌曲
print(musicNum)
fflag = 1 # 设置为异常切歌标志位
break
# 显示对应时间 并且能将进度条推进
m,s = to_time(int(pygame.mixer.music.get_pos()/1000))
str_time = m + ':' + s;
jindu.set(str_time)
pro1['value'] += one
pygame.mixer.music.stop()
if fflag == 0 : # 当前歌曲播放完毕 自动切换下一曲
musicNum += 1
if musicNum >= len(randomList):
musicNum = 0
2.3 上一曲与下一曲
上一曲下一曲与播放暂停类似,同样是设置标志位,播放线程循环时对标志位判断即可(播放线程代码见上部分)。
def Tonext():
global musicNum,musicNameList,randomList,nextFlag,lock
musicNum += 1
if musicNum >= len(randomList): # 超出歌曲数
musicNum=0
musicName.set(musicNameList[randomList[musicNum]]) # 显示歌名
lock.acquire()
nextFlag = 1
lock.release()
def Tobefore():
global musicNum, musicNameList, randomList,nextFlag
musicNum -= 1
if musicNum < 0: # 超出歌曲数
musicNum = len(randomList)-1
musicName.set(musicNameList[randomList[musicNum]]) # 显示歌名
lock.acquire()
nextFlag = -1
lock.release()
三、总结及全部代码
一直想着做一个音乐播放器,但是总是只是想想,没有真的去做。最近闲来无事,就从最简单的尝试了一下。后续会继续对这个播放器进行优化!
全部代码:
import random
from os import environ
environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'
import pygame
import time
from mutagen.mp3 import MP3
import os
import tkinter as tk # 使用Tkinter前需要先导入
from tkinter import filedialog
import threading
from tkinter import ttk
lock = threading.Lock()
musicList = [] # 音乐路径
musicNameList = [] # 音乐名
startFlag = False # True为播放 False为暂停
musicNum = 0 # 目前播放音乐下标
setMode = 1 # 设置播放模式 1为顺序 2为随机 3为单曲
randomList = [] # 随机数列表
pro1 = None # 进度条
nextFlag = 0 # 上一曲 下一曲标记
threadFlag = 0 # 标志线程是否已经在执行
def to_time(a):
m = int(a / 60)
s = int(a % 60)
if s < 10:
str_s = '0' + str(s);
else:
str_s = str(s);
if m < 10:
str_m = '0' + str(m)
else:
str_m = str(m)
return str_m,str_s
def getMusicList(filepath):
global musicList, musicNameList,musicList
list = os.listdir(filepath)
for music in list:
if "lrc" not in music:
musicpath = filepath + '\\' + music
if os.path.isfile(musicpath):
musicList.append(musicpath)
musicNameList.append(music)
musicName.set(musicNameList[0]) # 显示歌名
for i in range(0, len(musicList)):
randomList.append(i)
print(randomList)
def hit_me():
Folderpath = filedialog.askdirectory() # 获得选择好的文件夹
if len(Folderpath)!=0:
window1.destroy()
window2.pack()
getMusicList(Folderpath)
def window_one(window1): # 加载音乐
var = tk.StringVar() # 将label标签的内容设置为字符类型,用var来接收hit_me函数的传出内容用以显示在标签上
var.set('请选择音乐所在文件夹')
# 第4步,在图形界面上设定标签
l = tk.Label(window1, textvariable=var, font=('Arial', 12), width=500, height=2)
# 说明: bg为背景,font为字体,width为长,height为高,这里的长和高是字符的长和高,比如height=2,就是标签有2个字符这么高
# 第5步,放置标签
l.pack(expand='True',pady='15') # Label内容content区域放置位置,自动调节尺寸
# 放置lable的方法有:1)l.pack(); 2)l.place();
# 第5步,在窗口界面设置放置Button按键
b = tk.Button(window1, text='选择文件夹', font=('Arial', 12), width=10, height=1, command=hit_me)
b.pack(expand='True')
def startMusic():
global musicList,musicNameList,musicNum,randomList,nextFlag,lock,startFlag,threadFlag
pygame.mixer.init()
fflag = 0 # 标记是否为正常下一曲
threadFlag = 1
# 加载音乐
while True:
# 初始化标志位
fflag = 0
pro1['value'] = 0
str_time = "00:00"
jindu.set(str_time)
print("xiabiao:",end="")
print(randomList[musicNum])
audio = MP3(musicList[randomList[musicNum]])
musiclen = int(audio.info.length) # 音乐长度
one = 100/musiclen # 刻度每次需要移动大小
pygame.mixer.music.load(musicList[randomList[musicNum]])
# 准备开始播放
pygame.mixer.music.play(start=0.0)
# 播放时长,没有此设置,音乐不会播放,会一次性加载完
for i in range(1,musiclen+1):
time.sleep(1)
lock.acquire()
flag = nextFlag
nextFlag = 0
staflag = startFlag # 播放标志位
lock.release()
if not staflag:
pygame.mixer.music.pause() # 暂停
print("zanting")
while True:
if startFlag: # 重新播放
pygame.mixer.music.unpause()
break
if nextFlag != 0: # 点击了上一曲或者下一曲
flag = nextFlag
nextFlag = 0
startFlag = True
break
if flag != 0: # 点击了下一曲或者上一曲 直接切换歌曲
print(musicNum)
fflag = 1 # 设置为异常切歌标志位
break
# 显示对应时间 并且能将进度条推进
m,s = to_time(int(pygame.mixer.music.get_pos()/1000))
str_time = m + ':' + s;
jindu.set(str_time)
pro1['value'] += one
pygame.mixer.music.stop()
if fflag == 0 : # 当前歌曲播放完毕 自动切换下一曲
musicNum += 1
if musicNum >= len(randomList):
musicNum = 0
def Tostart():
global startFlag,threadFlag
thmusic = threading.Thread(target=startMusic, args=())
thmusic.setDaemon(True) # 守护线程
if not startFlag:
startFlag = True
startText.set("暂停")
if threadFlag == 0: # 线程运行时不能再次打开线程
thmusic.start()
else:
startFlag = False
startText.set("播放")
def Tonext():
global musicNum,musicNameList,randomList,nextFlag,lock
musicNum += 1
if musicNum >= len(randomList): # 超出歌曲数
musicNum=0
musicName.set(musicNameList[randomList[musicNum]]) # 显示歌名
lock.acquire()
nextFlag = 1
lock.release()
def Tobefore():
global musicNum, musicNameList, randomList,nextFlag
musicNum -= 1
if musicNum < 0: # 超出歌曲数
musicNum = len(randomList)-1
musicName.set(musicNameList[randomList[musicNum]]) # 显示歌名
lock.acquire()
nextFlag = -1
lock.release()
def Toloop():
global setMode,randomList,musicList
randomList = []
if setMode == 1:
setMode = 2
mode.set("随机")
while True:
k = random.randint(0,len(musicNameList)-1)
if k not in randomList:
randomList.append(k)
if len(randomList) == len(musicList):
break
elif setMode == 2:
setMode = 3
mode.set("单曲")
randomList.append(musicNum)
else:
setMode = 1
mode.set("顺序")
for i in range(0,len(musicList)):
randomList.append(i)
print(randomList)
def window_two(window2):
global pro1
musicLabel = tk.Label(window2, textvariable=musicName, font=('Arial', 12), width=500, height=2)
musicLabel.pack(expand='True')
# 进度条显示部分
jinduLabel = tk.Label(window2, textvariable=jindu, font=('Arial', 10), width=10, height=2)
pro1 = ttk.Progressbar(window2, length=450, cursor='spider', mode="determinate", orient=tk.HORIZONTAL)
pro1.pack(padx='2')
jinduLabel.pack()
start = tk.Button(window2, textvariable=startText, font=('Arial', 12), width=12, height=1, command=Tostart)
next = tk.Button(window2, text='下一曲', font=('Arial', 12), width=12, height=1, command=Tonext)
last = tk.Button(window2, text='上一曲', font=('Arial', 12), width=12, height=1, command=Tobefore)
setloop = tk.Button(window2, textvariable=mode, font=('Arial', 12), width=12, height=1, command=Toloop)
start.pack(side='left',padx='1',expand='True')
last.pack(side='left', padx='1',expand='True')
next.pack(side='left', padx='1', expand='True')
setloop.pack(side='left', padx='1',expand='True')
if __name__ == "__main__":
# 第1步,实例化object,建立窗口window
window = tk.Tk()
# 第2步,给窗口的可视化起名字
window.title('My Music')
# 第3步,设定窗口的大小(长 * 宽)
window.geometry('500x150') # 这里的乘是小x
window1 = tk.Frame(window)
window1.pack()
window_one(window1)
# 音乐名需要变化
musicName = tk.StringVar()
musicName.set("null")
# 播放按键需要变化
startText = tk.StringVar()
startText.set("播放")
# 进度需要变化
jindu = tk.StringVar()
jindu.set("00:00")
# 播放模式设置
mode = tk.StringVar()
mode.set("顺序")
window2 = tk.Frame(window)
window_two(window2)
# 禁止用户调整窗口大小
window.resizable(False, False)
# 第6步,主窗口循环显示
window.mainloop()
# 注意,loop因为是循环的意思,window.mainloop就会让window不断的刷新,如果没有mainloop,就是一个静态的window,传入进去的值就不会有循环,mainloop就相当于一个很大的while循环,有个while,每点击一次就会更新一次,所以我们必须要有循环
# 所有的窗口文件都必须有类似的mainloop函数,mainloop是窗口文件的关键的关键。