记录基于Python tkinter的音乐播放器的实现过程
**引言:**相信大家和我一样喜欢音乐。多年积累下来,我居然在电脑上下载了500多首歌曲。歌曲放在一个文件夹下,平时可以通过音乐软件或平台来播放。然而,作为编程学习者,何不自己开发一款软件对自己下载的音乐进行管理呢?出于兴趣爱好,我利用python tkinter实现了本地音乐播放的界面化。下面记录一下音乐播放器的整个实现过程。
一、播放器的设计概述
功能:
1. 播放功能:播放,暂停/继续,上/下一首,拖拽进度条,调节音量
2. 播放方式:顺序、随机、单曲播放
3. 搜索和播放:输入关键字,列出包含关键字的歌曲,可以选中播放
4. 歌曲显示:主列表列出所有的歌曲,浏览和选择播放歌曲
5. 歌词显示:播放时同步滚动歌词
6. 临时列表:从主列表、搜索列表中选择歌曲添加到临时列表,播放临时列表的歌曲
7. 收藏歌单:创建歌单,添加歌曲,更好分类播放
8. 听歌记录:记录什么时间听了什么歌曲,方便统计听歌次数,听歌时长
思路:
1.音乐播放引擎:选择的是pygame.mixer.music模块,它可以帮助我们实现播放器的基本功能。
pygame music模块功能学习
2.音乐播放器界面:使用python 自带的tkinter库,实现界面化。
3.本地文件夹:歌曲、歌词、歌单、程序。
4.按钮的command为线程封装的函数:pygame播放音乐时,需要设置它的播放时长,也就是延时,如果不使用线程,那么播放时界面上会出现卡死,根本点击不了其他按钮。
界面显示:
目前音乐播放器的功能基本实现,后续如有改进的地方再更新。
文末附带完整代码。
二、界面组件
这里把需要用到的组件列出来
Listbox 列表组件来容纳歌曲、歌词
Scrollbar 滑动块组件来跟踪浏览Listbox歌曲的位置
Button 按钮实现点击功能
Label 显示播放中的歌曲名称,显示歌曲总数,北京时间
Scale 实现进度条的拖拽,音量的调节
Progressbar 显示歌曲播放进度
Entry 实现关键字的输入
界面组件搭建代码如下:
class MusicPlayer():
def __init__(self):
# colors
self.GlobalColor = '#F1E9D2' # 羊皮纸颜色
self.LightYellow = '#FFE57D' # 浅黄色
self.LightPurple = '#C748FA' # 浅紫色
self.Silvery = '#CFCED6' # 亮银色
self.COLOR_SIZE = 16
self.MyColors = [self.GlobalColor]*self.COLOR_SIZE
self.WordStyle = '微软雅黑'
self.screen = tk.Tk()
self.screen.geometry('600x700')
self.screen.title('本地音乐播放器')
#滑动条
self.Scroll_songOrder = tk.Scrollbar(self.screen)
self.ListBox_songOrder = tk.Listbox(self.screen, width=61, height=10, bg=self.GlobalColor, fg='black', font=(self.WordStyle, 12),
yscrollcommand=self.Scroll_songOrder.set)
#歌曲名列表框
self.ListBox_songOrder.config(yscrollcommand=self.Scroll_songOrder.set)
self.Scroll_songOrder.config(command=self.ListBox_songOrder.yview)
#绑定滑动条
self.ListBox_songOrder.place(x=10, y=290)
self.Scroll_songOrder.pack(side='right', fill='both')
# 显示正在播放
self.Label_play = tk.Label(self.screen, height=1, width=10, text='正在播放:', bg=self.GlobalColor, fg='black', font=(self.WordStyle, 12))
self.Label_play.place(x=10, y=30)
#显示正在播放的歌曲
self.StrngVar_songName = tk.StringVar()
self.Label_songName = tk.Label(self.screen, width=45, height=1, fg='black', bg=self.GlobalColor, font=(self.WordStyle, 12),
textvariable=self.StrngVar_songName)
self.Label_songName.place(x=100, y=30)
#搜索框
self.Entry_find = tk.Entry(self.screen, width=65, bg=self.GlobalColor, font=(self.WordStyle, 12))
self.Entry_find.place(x=410, y=520, width=145, height=35)
self.Entry_find.delete(0, tk.END)
self.Entry_find.insert(0, "关键字搜索")
self.Entry_find.bind('<Return>', self.findSong)
#搜索的内容
self.Listbox_find = tk.Listbox(self.screen, width=61, height=5, bg=self.GlobalColor, fg='black', font=(self.WordStyle, 12))
self.Listbox_find.place(x=10, y=570)
#歌词的内容
self.Listbox_LRC = tk.Listbox(self.screen, width=61, height=3, bg=self.GlobalColor, fg='blue', font=(self.WordStyle, 12))
self.Listbox_LRC.place(x=10, y=60)
# 显示进度条
self.ProgressBar = ttk.Progressbar(self.screen, length=430, mode="determinate", orient=HORIZONTAL)
self.ProgressBar.place(x=130, y=140)
self.ProgressBar["maximum"] = 99
self.ProgressBar["value"] = 0
self.CreateToolTip(self.ProgressBar, "显示进度条")
# 更改进度条
self.ProgressScale = tk.Scale(self.screen, from_=0, to=100, bd=1, orient=tk.HORIZONTAL,
length=430, showvalue=False, resolution=1, command=self.songProgressAdjust) #tickinterval=20
self.ProgressScale.set(0)
self.ProgressScale.place(x=130, y=190)
self.CreateToolTip(self.ProgressScale, "拖拽进度条")
self.Label_songTime = tk.Label(self.screen, text="00:00", width=10,height=1, bg="white", fg='black', font=(self.WordStyle, 12))
self.Label_songTime.place(x=10, y=140)
self.Label_offset = tk.Label(self.screen, text="offset:", width=10,height=1, bg="white", fg='black', font=(self.WordStyle, 12))
self.Label_offset.place(x=10, y=190)
#self.screen.after(100, self.songProgressbar)
self.Button_lastSong = tk.Button(self.screen, text='◀', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), command=lambda: self.Thread_NULL(self.lastSong),
relief=tk.RAISED)
self.Button_lastSong.place(x=10, y=250)
self.CreateToolTip(self.Button_lastSong, "上一首")
self.Button_playSong = tk.Button(self.screen, text='♫', width=3, bg=self.MyColors[0], fg=self.LightPurple, font=(self.WordStyle, 12),
command=lambda: self.Thread_NULL(self.playSongByOrder) )
self.Button_playSong.place(x=160, y=250)
self.CreateToolTip(self.Button_playSong, "播放音乐")
self.Button_pause = tk.Button(self.screen, text='‖', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12),
command=lambda: self.Thread_NULL(self.playPause))
self.Button_pause.place(x=60, y=250)
self.CreateToolTip(self.Button_pause, "暂停与恢复")
self.Button_nextSong = tk.Button(self.screen, text='▶', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12),
command=lambda: self.Thread_NULL(self.nextSong))
self.Button_nextSong.place(x=110, y=250)
self.CreateToolTip(self.Button_nextSong, "下一首")
# 调节音量
self.Scale_Sound = tk.Scale(self.screen, from_=0, to=100, orient=tk.HORIZONTAL,
length=130, showvalue=False, resolution=10, command=self.playSetVolume) #tickinterval=20,self.playSetVolume
self.Scale_Sound.set(100)
self.Scale_Sound.place(x=420, y=250)
self.CreateToolTip(self.Scale_Sound, "调节音量")
# 东八区时间,即北京时间
self.Label_UTC8 = tk.Label(self.screen, text="%s%d" % (
datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S:'), datetime.datetime.now().microsecond // 100000), width=20,
height=1, bg=self.GlobalColor, fg='black', font=(self.WordStyle, 8))
self.Label_UTC8.place(x=410, y=0)
self.screen.after(100, self.uptime)
#乐库路径
self.Button_SongLibPath = tk.Button(self.screen, text='乐库路径', bg=self.GlobalColor, fg='black', font=(self.WordStyle, 8),
command=lambda: self.Thread_NULL(self.chooseSongsFloder))
self.Button_SongLibPath.place(x=10, y=0)
# 歌词LRC库路径
self.Button_songRlcPath = tk.Button(self.screen, text='歌词路径', bg=self.GlobalColor, fg='black', font=(self.WordStyle, 8),
command=lambda: self.Thread_NULL(self.chooseRlcPath))
self.Button_songRlcPath.place(x=80, y=0)
# 更新乐库
self.Button_updateLibrary = tk.Button(self.screen, text='更新乐库', bg=self.GlobalColor, fg='black', font=(self.WordStyle, 8),
command=lambda: self.Thread_NULL(self.updateSongs))
self.Button_updateLibrary.place(x=150, y=0)
# 随机播放
self.Button_randomPlay = tk.Button(self.screen, text='∞', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12),
command=lambda: self.Thread_NULL(self.randomSong))
self.Button_randomPlay.place(x=210, y=250)
self.CreateToolTip(self.Button_randomPlay, "随机播放")
# 显示歌曲数目
self.Label_numbers = tk.Label(self.screen, bg=self.GlobalColor, fg='black', font=(self.WordStyle, 12), width=8)
self.Label_numbers.place(x=475, y=480)
# 单曲播放
self.Button_cyclePlay = tk.Button(self.screen, text='①', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12),
command=lambda: self.Thread_NULL(self.cyclePlaySong))
self.Button_cyclePlay.place(x=260, y=250)
self.CreateToolTip(self.Button_cyclePlay, "单曲循环")
# 统计音乐播放次数
self.Button_CountSongs = tk.Button(self.screen, text='cnt', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12),
command=self.countSongInfo)
self.Button_CountSongs.place(x=310, y=250)
self.CreateToolTip(self.Button_CountSongs, "听歌汇")
# 听歌历史痕迹
self.Button_ListenHistory = tk.Button(self.screen, text='rec', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12),
command=self.listenHistory)
self.Button_ListenHistory.place(x=360, y=250)
self.CreateToolTip(self.Button_ListenHistory, "听歌历史")
# 从主歌单中添加至待播放列表中
self.Button_Add = tk.Button(self.screen, text='+', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.addSongs))
self.Button_Add.place(x=10, y=520)
self.CreateToolTip(self.Button_Add, "从主歌单中添加")
# 从搜索列表中添加至待播放列表中
self.Button_AddFind = tk.Button(self.screen, text='++', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.addFindSongs))
self.Button_AddFind.place(x=60, y=520)
self.CreateToolTip(self.Button_AddFind, "从搜索中添加")
# 清空待播放列表
self.Button_Clear = tk.Button(self.screen, text='∅', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.clearSongs))
self.Button_Clear.place(x=110, y=520)
self.CreateToolTip(self.Button_Clear, "清空添加歌单")
# 播放搜索列表中的歌曲
self.Button_FindPlay = tk.Button(self.screen, text='♪', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.playSong_find))
self.Button_FindPlay.place(x=160, y=520)
self.CreateToolTip(self.Button_FindPlay, "搜索播放")
# 移除待播放列表的选中歌曲
self.Button_RemoveSong = tk.Button(self.screen, text='×', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.removeSong))
self.Button_RemoveSong.place(x=210, y=520)
self.CreateToolTip(self.Button_RemoveSong, "删除添加歌曲")
# 显示待播放列表中的歌曲
self.Button_ShowSongs = tk.Button(self.screen, text='≡', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.showSongs))
self.Button_ShowSongs.place(x=260, y=520)
self.CreateToolTip(self.Button_ShowSongs, "列出添加歌曲")
# 收藏的歌单
self.Button_LoveOrders = tk.Button(self.screen, text='♥', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.loveSongs))
self.Button_LoveOrders.place(x=310, y=520)
self.CreateToolTip(self.Button_LoveOrders, "收藏歌单")
# 读取选择中的收藏歌单
self.Button_ReadOrders = tk.Button(self.screen, text='✔', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.readOrders))
self.Button_ReadOrders.place(x=360, y=520)
self.CreateToolTip(self.Button_ReadOrders, "读取歌单")
self.insertSongs()
self.screen.mainloop()
三、功能实现思路
3.1.播放功能
用 pygame.mixer.music.load()和pygame.mixer.music.play()来实现。
pygame.mixer.music.load() 传入歌曲的路径,如果路径有效pygame.mixer.music.play()就会播放。
play(loops=0, start=0.0) -> None
loops表示重复播放的次数,如play(3)重复播放3次,play(1)和play()表示播放1次,play(-1)表示无限循环播放。
start,表示播放的起始时间,这个参数在拖拽进度条时用到。
import pygame
pygame.mixer.init() # 初始化
pygame.mixer.music.load(music_path)
pygame.mixer.music.play()
3.2.歌曲信息
播放音乐时,需要计算音乐的时长传给延时,因此导入mutagen库的MP3可以帮助我们获取到歌曲的时长信息,从而实现指定延时。
from mutagen.mp3 import MP3
songInfo = MP3(music_path)
songTimelong = songInfo.info.length
time.sleep(songTimelong)
3.3.歌曲显示
通过tkinter的filedialog模块,打开歌曲文件夹,获取文件路径;
然后通过os模块,读取文件夹下面的文件,插入到主歌单Listbox中;
from tkinter import filedialog
import os
songDirPath = filedialog.askdiretory()
songList = os.listdir(songDirPath)
Listbox_songOrder.delete(0,tk.END)
for song in songList:
if song.endwith(".mp3"):
Listbox_songOrder.insert("end", song)
3.4.播放方式的实现思路
1) 选中歌曲,点击播放:可以通过Listbox.curselection()获取当前选中的歌曲,与文件夹路径拼接,就可以得到歌曲的绝对路径了。
2)通过获取当前歌曲的下标索引,然后对索引进行操作,可以先实现上一首,下一首,顺序播放了。
3)随机播放:通过Listbox.size()得到所有歌曲的数目,然后使用随机函数得到随机索引,song_id =random.randint(0,size-1),再通过Listbox.get(Listbox.index(song_id))得到歌曲,拼接为绝对路径。
4)单曲循环:获取选中歌曲的路径,使用pygame.mixer.music.play(-1),实现单曲循环。
3.5 信令与播放线程实现思路
个人理解线程:多线程好比不同时期或同时分布的多个任务,一个线程执行一个任务,如果没有信令去控制线程,那么这个线程直到任务执行完才结束。
在播放器里,如果没有信令控制线程,就会出现互相干扰的情况。比如,正在播放音乐,然后点了另一首歌曲播放,那么这两首歌曲都在播放,就会觉得很混乱,因为我想听的是最新点击的歌曲,前一首歌曲应该结束才对。因此当新点击一首歌曲时,需要信令去控制线程,让它结束前一首歌曲的播放。
因此,定义一个数字发生函数,每当点击歌曲播放时(实质是延时),就会产生一个新数字,姑且把这数字叫作信令,在这个新数字产生前播放的音乐权且叫作“旧歌曲”,相比“旧歌曲”,在最新点击的歌曲叫作“新歌曲”,那么“旧歌曲”的信令数字与最新的信令数字不匹配,那么我们就结束这个“旧歌曲”的播放任务。
# 播放线程切换最新标志
self.CHANGE_NEW = 0
# 播放线程切换标志
self.FLAGNO = 0
# 线程标志号,每点一次播放都会触发一个新的标志号,用于区分线程
def threadChangeFlag(self, *args):
if self.FLAGNO >= 100:
self.FLAGNO = 0
else:
self.FLAGNO += 1
self.CHANGE_NEW = self.FLAGNO
return self.FLAGNO
# 自定义的线程函数类
# 无参数传递的线程封装,按钮函数
def Thread_NULL(self, func, *args):
'''将函数放入线程中执行'''
# 创建线程
t = threading.Thread(target=func, args=args)
# 守护线程
t.setDaemon = True
# 启动线程
t.start()
# 等待结束
# t.join()
# 有两个参数传递的线程封装,传递时长、最新信令
def Thread_TWO(self, func, time, flagNo, *args):
'''将函数放入线程中执行'''
# 创建线程
t = threading.Thread(target=func, args=(time, flagNo,))
# 守护线程
t.setDaemon = True
# 启动线程
t.start()
# 等待结束
# t.join()
3.6 定时器
或者叫时间计数器比较合适,这个其实没有必要写的,因为定位歌曲播放了多少时间,我们可以通过pygame.mixer.music.get_pos()来获取。这个在显示播放了多少时长,进度条显示进度,和同步歌词滚动时需要用到。
那么如果不用get_pos(),怎么实现定时器呢?见代码:
'''
# 定时器,时间计数发生器
当开始播放时,启动计时,从零开始每秒加一;
当播放暂停时,停止计时;
当恢复播放时,恢复计时,每秒加一;
signal 播放的线程信令
playNo 播放的操作
'''
def timer(self, signal, playNo):
while(signal == self.CHANGE_NEW):
if(playNo == self.PUASE or self.PUASEFLAG == True):
self.TIME_CNT += 0
elif(playNo == self.PLAY and self.PUASEFLAG == False):
self.TIME_CNT += 1
time.sleep(1)
3.7 歌词同步思路
同步的原理:lrc的歌词每行都包含时间和歌词(空行),当歌曲播放时间与歌词的时间一致时就显示该歌词。如下面就是一段lrc歌词。
[ar:刘珂矣]
[ti:半壶纱]
[00:00.41]刘珂矣 - 半壶纱
[00:01.52]作词:刘珂矣、百慕三石
[00:02.88]作曲:刘珂矣、百慕三石
[00:04.45]编曲:百慕三石
[00:26.21]墨已入水渡一池青花
[00:32.19]揽五分红霞采竹回家
[00:38.22]悠悠风来 埋一地桑麻
[00:44.38]一身袈裟 把相思放下
[00:50.45]十里桃花待嫁的年华
[00:56.57]凤冠的珍珠 挽进头发
[01:02.70]檀香拂过玉镯弄轻纱
[01:08.78]空留一盏 芽色的清茶
[01:14.96]倘若我心中的山水
[01:18.75]你眼中都看到
[01:22.24]我便一步一莲花祈祷
[01:27.15]怎知那浮生一片草
[01:31.30]岁月催人老
当然,就算get_pos()也不能得到与lyc歌词时间的一致时间,毫秒级别的不够精确。所以在读取歌词时间时,将歌词时间取整秒,定时器的时间计数也是整秒,按整秒去匹配歌词时间,从而实现歌词同步匹配。另外注重歌词毫秒是没有意义的,因为在看歌词和听歌,根本分辨不了毫秒的时间差。
另外,歌词显示比播放时间早一两秒比较合适,这样提前看到歌词,方便跟着看或跟着唱。
歌词滚动的思路:界面比较小,显示歌词的Listbox只有3行,因此当插入的行数大于3时,就删除第1行。可以用Listbox.size()获取插入的行数。
3.8 进度条思路
假设进度条的范围是0至100,那么通过get_pos()获取到播放时间,与歌曲时长的百分比就是进度条了。实现随着歌曲播放,进度条慢慢增涨。
3.9 收藏歌单思路
在歌单文件夹下,建文本歌单,在里面写下喜欢的歌曲,或根据歌单将歌曲分类。
那么os.listdir()就可以把歌单文件夹下的歌单列出来了,然后读取歌单,得到歌曲。
3.10 听歌记录
每播放一首歌曲时就把当前时间及歌名、时长,保存到一个log文本文件中。
方便统计听歌次数,听歌时长。当听歌一段时间后,回过头来看,最喜欢的一首歌竟然是这首,或许会惊讶不已,也许是理所当然。
ps: 当点击歌曲记录按钮或统计按钮时,会将记录数据插入到主列表中;想要回到主列表是歌曲的情形,只需点击上方的更新乐库即可。
3.11 临时列表
将选中的歌曲添加至临时列表中,或者移除。临时列表是一个列表,使用列表的方法就实现添加,移除等操作。
四、功能代码展示
下面是这个播放器的完整代码;
# encoding=utf-8
import tkinter as tk
from tkinter import *
from tkinter import filedialog
from tkinter import ttk
import os
import pygame
import time
import random
import math
import datetime
from mutagen.mp3 import MP3
import threading
class MusicPlayer():
def __init__(self):
# colors
self.GlobalColor = '#F1E9D2' # 羊皮纸颜色
self.LightYellow = '#FFE57D' # 浅黄色
self.LightPurple = '#C748FA' # 浅紫色
self.COLOR_SIZE = 16 # 按钮数,点击时高亮
self.MyColors = [self.GlobalColor]*self.COLOR_SIZE
self.WordStyle = '微软雅黑'
self.SWITCH = True # 暂停/继续 的切换
self.START = 1 # 启动播放
self.PUASE = 2 # 暂停播放
self.PLAY = 3 # 恢复播放
self.STOP = 0
self.EXIT = 0
self.STEP = 0 # 拖拽进度条的时间步长
# 暂停标志
self.PUASEFLAG = False
# 单曲循环标志
self.CYCLEFLAG = False
# 播放线程切换最新标志
self.CHANGE_NEW = 0
# 播放线程切换标志
self.FLAGNO = 0
# 计时器
self.TIME_CNT = 0
# 线程的退出标志
# 0-上一首、1-下一首、2-顺序播放、3-随机播放、4-单曲循环、5-搜索播放、6-进度条播放
self.flagNum = 7
self.ExitFlag = [0]*self.flagNum
self.OrientPath = r'D:\\Music\\CloudMusic\\歌曲'
self.LrcPath = r'D:\\Music\\CloudMusic\\歌词'
self.RecordPath = r'D:\\Music\\CloudMusic\\程序\\songsLog.txt'
self.LoveSongsPath = r'D:\\Music\\CloudMusic\\歌单'
self.SONGID = 0
self.PATH = ''
# 临时列表
self.AddList = list()
#音乐初始化
pygame.mixer.init()
self.screen = tk.Tk()
self.screen.geometry('600x700')
self.screen.title('本地音乐播放器')
#滑动条
self.Scroll_songOrder = tk.Scrollbar(self.screen)
self.ListBox_songOrder = tk.Listbox(self.screen, width=61, height=10, bg=self.GlobalColor, fg='black', font=(self.WordStyle, 12),
yscrollcommand=self.Scroll_songOrder.set)
#歌曲名列表框
self.ListBox_songOrder.config(yscrollcommand=self.Scroll_songOrder.set)
self.Scroll_songOrder.config(command=self.ListBox_songOrder.yview)
#绑定滑动条
self.ListBox_songOrder.place(x=10, y=290)
self.Scroll_songOrder.pack(side='right', fill='both')
# 显示正在播放
self.Label_play = tk.Label(self.screen, height=1, width=10, text='正在播放:', bg=self.GlobalColor, fg='black', font=(self.WordStyle, 12))
self.Label_play.place(x=10, y=30)
#显示正在播放的歌曲
self.StrngVar_songName = tk.StringVar()
self.Label_songName = tk.Label(self.screen, width=45, height=1, fg='black', bg=self.GlobalColor, font=(self.WordStyle, 12),
textvariable=self.StrngVar_songName)
self.Label_songName.place(x=100, y=30)
#搜索框
self.Entry_find = tk.Entry(self.screen, width=65, bg=self.GlobalColor, font=(self.WordStyle, 12))
self.Entry_find.place(x=410, y=520, width=145, height=35)
self.Entry_find.delete(0, tk.END)
self.Entry_find.insert(0, "关键字搜索")
self.Entry_find.bind('<Return>', self.findSong)
#搜索的内容
self.Listbox_find = tk.Listbox(self.screen, width=61, height=5, bg=self.GlobalColor, fg='black', font=(self.WordStyle, 12))
self.Listbox_find.place(x=10, y=570)
#歌词的内容
self.Listbox_LRC = tk.Listbox(self.screen, width=61, height=3, bg=self.GlobalColor, fg='blue', font=(self.WordStyle, 12))
self.Listbox_LRC.place(x=10, y=60)
# 显示进度条
self.ProgressBar = ttk.Progressbar(self.screen, length=430, mode="determinate", orient=HORIZONTAL)
self.ProgressBar.place(x=130, y=140)
self.ProgressBar["maximum"] = 99
self.ProgressBar["value"] = 0
self.CreateToolTip(self.ProgressBar, "显示进度条")
# 更改进度条
self.ProgressScale = tk.Scale(self.screen, from_=0, to=100, bd=1, orient=tk.HORIZONTAL,
length=430, showvalue=False, resolution=1, command=self.songProgressAdjust) #tickinterval=20
self.ProgressScale.set(0)
self.ProgressScale.place(x=130, y=190)
self.CreateToolTip(self.ProgressScale, "拖拽进度条")
self.Label_songTime = tk.Label(self.screen, text="00:00", width=10,height=1, bg="white", fg='black', font=(self.WordStyle, 12))
self.Label_songTime.place(x=10, y=140)
self.Label_offset = tk.Label(self.screen, text="offset:", width=10,height=1, bg="white", fg='black', font=(self.WordStyle, 12))
self.Label_offset.place(x=10, y=190)
self.Button_lastSong = tk.Button(self.screen, text='◀', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), command=lambda: self.Thread_NULL(self.lastSong),
relief=tk.RAISED)
self.Button_lastSong.place(x=10, y=250)
self.CreateToolTip(self.Button_lastSong, "上一首")
self.Button_playSong = tk.Button(self.screen, text='♫', width=3, bg=self.MyColors[0], fg=self.LightPurple, font=(self.WordStyle, 12),
command=lambda: self.Thread_NULL(self.playSongByOrder) )
self.Button_playSong.place(x=160, y=250)
self.CreateToolTip(self.Button_playSong, "播放音乐")
self.Button_pause = tk.Button(self.screen, text='‖', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12),
command=lambda: self.Thread_NULL(self.playPause))
self.Button_pause.place(x=60, y=250)
self.CreateToolTip(self.Button_pause, "暂停与恢复")
self.Button_nextSong = tk.Button(self.screen, text='▶', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12),
command=lambda: self.Thread_NULL(self.nextSong))
self.Button_nextSong.place(x=110, y=250)
self.CreateToolTip(self.Button_nextSong, "下一首")
# 调节音量
self.Scale_Sound = tk.Scale(self.screen, from_=0, to=100, orient=tk.HORIZONTAL,
length=130, showvalue=False, resolution=10, command=self.playSetVolume) #tickinterval=20,self.playSetVolume
self.Scale_Sound.set(100)
self.Scale_Sound.place(x=420, y=250)
self.CreateToolTip(self.Scale_Sound, "调节音量")
# 东八区时间,即北京时间
self.Label_UTC8 = tk.Label(self.screen, text="%s%d" % (
datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S:'), datetime.datetime.now().microsecond // 100000), width=20,
height=1, bg=self.GlobalColor, fg='black', font=(self.WordStyle, 8))
self.Label_UTC8.place(x=410, y=0)
self.screen.after(100, self.uptime)
#乐库路径
self.Button_SongLibPath = tk.Button(self.screen, text='乐库路径', bg=self.GlobalColor, fg='black', font=(self.WordStyle, 8),
command=lambda: self.Thread_NULL(self.chooseSongsFloder))
self.Button_SongLibPath.place(x=10, y=0)
# 歌词LRC库路径
self.Button_songRlcPath = tk.Button(self.screen, text='歌词路径', bg=self.GlobalColor, fg='black', font=(self.WordStyle, 8),
command=lambda: self.Thread_NULL(self.chooseLrcPath))
self.Button_songRlcPath.place(x=80, y=0)
# 更新乐库
self.Button_updateLibrary = tk.Button(self.screen, text='更新乐库', bg=self.GlobalColor, fg='black', font=(self.WordStyle, 8),
command=lambda: self.Thread_NULL(self.updateSongs))
self.Button_updateLibrary.place(x=150, y=0)
# 随机播放
self.Button_randomPlay = tk.Button(self.screen, text='∞', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12),
command=lambda: self.Thread_NULL(self.randomSong))
self.Button_randomPlay.place(x=210, y=250)
self.CreateToolTip(self.Button_randomPlay, "随机播放")
# 显示歌曲数目
self.Label_numbers = tk.Label(self.screen, bg=self.GlobalColor, fg='black', font=(self.WordStyle, 12), width=8)
self.Label_numbers.place(x=475, y=480)
# 单曲播放
self.Button_cyclePlay = tk.Button(self.screen, text='①', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12),
command=lambda: self.Thread_NULL(self.cyclePlaySong))
self.Button_cyclePlay.place(x=260, y=250)
self.CreateToolTip(self.Button_cyclePlay, "单曲循环")
# 统计音乐播放次数
self.Button_CountSongs = tk.Button(self.screen, text='cnt', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12),
command=self.countSongInfo)
self.Button_CountSongs.place(x=310, y=250)
self.CreateToolTip(self.Button_CountSongs, "听歌汇")
# 听歌历史痕迹
self.Button_ListenHistory = tk.Button(self.screen, text='rec', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12),
command=self.listenHistory)
self.Button_ListenHistory.place(x=360, y=250)
self.CreateToolTip(self.Button_ListenHistory, "听歌历史")
# 从主歌单中添加至待播放列表中
self.Button_Add = tk.Button(self.screen, text='+', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.addSongs))
self.Button_Add.place(x=10, y=520)
self.CreateToolTip(self.Button_Add, "从主歌单中添加")
# 从搜索列表中添加至待播放列表中
self.Button_AddFind = tk.Button(self.screen, text='++', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.addFindSongs))
self.Button_AddFind.place(x=60, y=520)
self.CreateToolTip(self.Button_AddFind, "从搜索中添加")
# 清空待播放列表
self.Button_Clear = tk.Button(self.screen, text='∅', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.clearSongs))
self.Button_Clear.place(x=110, y=520)
self.CreateToolTip(self.Button_Clear, "清空添加歌单")
# 播放搜索列表中的歌曲
self.Button_FindPlay = tk.Button(self.screen, text='♪', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.playSong_find))
self.Button_FindPlay.place(x=160, y=520)
self.CreateToolTip(self.Button_FindPlay, "搜索播放")
# 移除待播放列表的选中歌曲
self.Button_RemoveSong = tk.Button(self.screen, text='×', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.removeSong))
self.Button_RemoveSong.place(x=210, y=520)
self.CreateToolTip(self.Button_RemoveSong, "删除添加歌曲")
# 显示待播放列表中的歌曲
self.Button_ShowSongs = tk.Button(self.screen, text='≡', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.showSongs))
self.Button_ShowSongs.place(x=260, y=520)
self.CreateToolTip(self.Button_ShowSongs, "列出添加歌曲")
# 收藏的歌单
self.Button_LoveOrders = tk.Button(self.screen, text='♥', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.loveSongs))
self.Button_LoveOrders.place(x=310, y=520)
self.CreateToolTip(self.Button_LoveOrders, "歌单")
# 读取选择中的收藏歌单
self.Button_ReadOrders = tk.Button(self.screen, text='✔', width=3, bg=self.GlobalColor, fg=self.LightPurple, font=(self.WordStyle, 12), relief=RAISED,
command=lambda: self.Thread_NULL(self.readOrders))
self.Button_ReadOrders.place(x=360, y=520)
self.CreateToolTip(self.Button_ReadOrders, "读取歌单")
self.insertSongs()
self.screen.mainloop()
# 线程标志号,每点一次播放都会触发一个新的标志号,用于区分线程
def threadChangeFlag(self, *args):
if self.FLAGNO >= 100:
self.FLAGNO = 0
else:
self.FLAGNO += 1
self.CHANGE_NEW = self.FLAGNO
return self.FLAGNO
# 自定义的线程函数类
# 无参数传递的线程封装,按钮函数
def Thread_NULL(self, func, *args):
'''将函数放入线程中执行'''
# 创建线程
t = threading.Thread(target=func, args=args)
# 守护线程
t.setDaemon = True
# 启动线程
t.start()
# 等待结束
# t.join()
# 有两个参数传递的线程封装,进度条、滚动歌词等函数
def Thread_TWO(self, func, time, flagNo, *args):
'''将函数放入线程中执行'''
# 创建线程
t = threading.Thread(target=func, args=(time, flagNo,))
# 守护线程
t.setDaemon = True
# 启动线程
t.start()
# 等待结束
# t.join()
'''
# 定时器,时间计数发生器
当开始播放时,启动计时,从零开始每秒加一;
当播放暂停时,停止计时;
当恢复播放时,恢复计时,每秒加一;
signal 播放的线程信号量
playNo 播放的操作
'''
def timer(self, signal, playNo):
while(signal == self.CHANGE_NEW):
if(playNo == self.PUASE or self.PUASEFLAG == True):
self.TIME_CNT += 0;
elif(playNo == self.PLAY and self.PUASEFLAG == False):
self.TIME_CNT += 1;
# print(f"TIME {self.TIME_CNT}")
time.sleep(1)
# 播放记录
def songPlayRecord(self, name, timelong, *args):
time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S ')
info = f"{time}播放 {name} 时长:{round(timelong,2)}秒.\n"
print(info)
with open(self.RecordPath,"a+") as f:
f.write(info)
f.close()
# 播放函数的封装
def playFunction(self, exitFlg):
if(exitFlg == self.ExitFlag[5]):
listBox = self.Listbox_find
else:
listBox = self.ListBox_songOrder
if(exitFlg == self.ExitFlag[2]) or (exitFlg == self.ExitFlag[5]):
self.SONGID = listBox.index(listBox.curselection())
self.PATH = self.OrientPath + '\\' + listBox.get(listBox.curselection()) + '.mp3'
self.StrngVar_songName.set(listBox.get(listBox.curselection()))
else:
self.PATH = self.OrientPath + '\\' + listBox.get(self.SONGID) + '.mp3'
self.StrngVar_songName.set(listBox.get(self.SONGID))
pygame.mixer.music.load(self.PATH)
pygame.mixer.music.set_volume(100)
pygame.mixer.music.play()
self.Label_numbers.config(text=str(self.SONGID + 1) + '/' + str(listBox.size()))
self.Button_pause.config(text='‖', bg = self.GlobalColor)
listBox.selection_clear(0, 'end')
listBox.selection_set(self.SONGID)
listBox.see(self.SONGID)
songInfo = MP3(self.PATH)
timelong = songInfo.info.length
self.ProgressScale.config(from_ = 0, to = timelong)
self.ProgressScale.set(0)
self.Label_offset['text'] = "00:00"
self.songPlayRecord(listBox.get(self.SONGID), timelong)
self.TIME_CNT = 0
self.Thread_TWO( self.timer, exitFlg, self.PLAY)
self.Thread_TWO( self.songProgressbar, timelong, exitFlg)
self.Thread_TWO( self.showLRC, timelong, exitFlg)
if( self.PUASEFLAG == True or self.CYCLEFLAG == True):
return self.EXIT
if(exitFlg != self.CHANGE_NEW):
return self.EXIT
time.sleep(int(timelong))
self.screen.update()
while(exitFlg == self.CHANGE_NEW):
try:
if ( self.PUASEFLAG == True or self.CYCLEFLAG == True):
return self.EXIT
if(exitFlg == self.ExitFlag[3]):
self.SONGID = random.randint(0, listBox.size() - 1)
else:
if(self.SONGID < listBox.size()-1):
self.SONGID += 1
else:
self.SONGID = 0
self.PATH = self.OrientPath + '\\' + listBox.get(self.SONGID) + '.mp3'
self.StrngVar_songName.set(listBox.get(self.SONGID))
pygame.mixer.music.load(self.PATH)
pygame.mixer.music.set_volume(100)
pygame.mixer.music.play()
self.Label_numbers.config(text=str(self.SONGID + 1) + '/' + str(listBox.size()))
self.Button_pause.config(text='‖', bg = self.GlobalColor)
listBox.selection_clear(0, 'end')
listBox.selection_set(self.SONGID)
listBox.see(self.SONGID)
songInfo = MP3(self.PATH)
timelong = songInfo.info.length
self.songPlayRecord(listBox.get(self.SONGID), timelong)
self.STEP = 0
self.ProgressScale.config(from_ = 0, to = timelong)
self.ProgressScale.set(0)
self.Label_offset['text'] = "00:00"
self.Thread_TWO( self.songProgressbar, timelong, exitFlg)
self.Thread_TWO( self.showLRC, timelong, exitFlg)
self.TIME_CNT = 0
time.sleep(int(timelong))
self.screen.update()
except:
break
# 顺序播放
def playSongByOrder(self, *args):
self.SWITCH = True
self.PUASEFLAG = False
self.CYCLEFLAG = False
self.STEP = 0
self.setColor(3)
self.ExitFlag[2] = self.threadChangeFlag()
exitFlg = self.ExitFlag[2]
if(exitFlg != self.CHANGE_NEW):
return self.EXIT
self.playFunction(exitFlg)
# 单曲循环播放
def cyclePlaySong(self, *args):
self.SWITCH = True
self.PUASEFLAG = False
self.CYCLEFLAG = True
self.STEP = 0
self.setColor(5)
self.PATH = self.OrientPath + '\\' + self.ListBox_songOrder.get(self.ListBox_songOrder.curselection()) + '.mp3'
self.StrngVar_songName.set(self.ListBox_songOrder.get(self.ListBox_songOrder.curselection()))
self.ExitFlag[4] = self.threadChangeFlag()
if(self.ExitFlag[4] != self.CHANGE_NEW):
return self.EXIT
pygame.mixer.music.load(self.PATH)
pygame.mixer.music.set_volume(100)
pygame.mixer.music.play(-1)
self.SONGID = self.ListBox_songOrder.index(self.ListBox_songOrder.curselection())
self.Label_numbers.config(text=str(self.SONGID + 1) + '/' + str(self.ListBox_songOrder.size()))
self.Button_pause.config(text='‖', bg = self.GlobalColor)
self.ListBox_songOrder.selection_clear(0, 'end')
self.ListBox_songOrder.selection_set(self.SONGID)
self.ListBox_songOrder.see(self.SONGID)
songInfo = MP3(self.PATH)
timelong = songInfo.info.length
self.songPlayRecord(self.ListBox_songOrder.get(self.ListBox_songOrder.curselection()), timelong)
self.ProgressScale.config(from_ = 0, to = timelong)
self.ProgressScale.set(0)
self.Label_offset['text'] = "00:00"
self.TIME_CNT = 0
self.Thread_TWO( self.timer, self.ExitFlag[4], self.PLAY)
self.Thread_TWO( self.songProgressbar, timelong, self.ExitFlag[4])
self.Thread_TWO( self.showLRC, timelong, self.ExitFlag[4])
if( self.PUASEFLAG == True or self.CYCLEFLAG == True):
return self.EXIT
# 下一首歌曲
def nextSong(self, *args):
self.SWITCH = True
self.PUASEFLAG = False
self.CYCLEFLAG = False
self.setColor(2)
self.ExitFlag[1] = self.threadChangeFlag()
exitFlg = self.ExitFlag[1]
if(exitFlg != self.CHANGE_NEW):
return self.EXIT
if self.SONGID < self.ListBox_songOrder.size()-1:
self.SONGID += 1
else:
self.SONGID = 0
self.playFunction(exitFlg)
# 上一首歌曲
def lastSong(self, *args):
self.SWITCH = True
self.PUASEFLAG = False
self.setColor(0)
self.ExitFlag[0] = self.threadChangeFlag()
exitFlg = self.ExitFlag[0]
if(exitFlg != self.CHANGE_NEW):
return self.EXIT
if self.SONGID > 0:
self.SONGID -= 1
else:
self.SONGID = self.ListBox_songOrder.size()-1
self.playFunction(exitFlg)
# 暂停播放
def playPause(self, *args):
self.CYCLEFLAG = False
self.setColor(1)
if self.SWITCH:
self.SWITCH = False
self.PUASEFLAG = True
self.Button_pause.config(text='■', bg = self.LightYellow)
pygame.mixer.music.pause()
else:
self.SWITCH = True
self.PUASEFLAG = False
self.Button_pause.config(text='‖', bg = self.GlobalColor)
pygame.mixer.music.unpause()
# 随机播放函数
def randomSong(self, *args):
self.SWITCH = True
self.PUASEFLAG = False
self.CYCLEFLAG = False
self.setColor(4)
self.ExitFlag[3] = self.threadChangeFlag()
exitFlg = self.ExitFlag[3]
if(exitFlg != self.CHANGE_NEW):
return self.EXIT
self.SONGID = random.randint(0, self.ListBox_songOrder.size() - 1)
self.playFunction(exitFlg)
# 关键字搜索歌曲
def findSong(self, *args):
self.SWITCH = True
flag = False
find = self.Entry_find.get()
songList1 = os.listdir(self.OrientPath)
songList2 = list()
self.Listbox_find.delete(0, tk.END)
for song in songList1:
if song[-4:] == '.mp3' and (find.casefold() in song.casefold()):
songList2.append(song)
flag = True
if(flag):
for one in songList2:
self.Listbox_find.insert('end', one[:-4])
# 播放搜索框音乐
def playSong_find(self, *args):
self.SWITCH = True
self.PUASEFLAG = False
self.CYCLEFLAG = False
self.setColor(11)
self.ExitFlag[5] = self.threadChangeFlag() # 播放方式之间的线程的区分
exitFlg = self.ExitFlag[5] # 同种播放方式,多首音乐之间的区分
if(exitFlg != self.CHANGE_NEW):
return self.EXIT
self.playFunction(exitFlg)
# 从主歌单列表从添加喜欢的歌曲
def addSongs(self, *args):
self.setColor(8)
try:
self.AddList.append(self.ListBox_songOrder.get(self.ListBox_songOrder.curselection()))
except:
print("anchor error!")
self.Listbox_find.delete(0, tk.END)
for song in self.AddList:
self.Listbox_find.insert("end", song)
# 从搜索列表中添加歌曲
def addFindSongs(self, *args):
self.setColor(9)
try:
self.AddList.append(self.Listbox_find.get(self.Listbox_find.curselection()))
except:
print("anchor error!")
self.Listbox_find.delete(0, tk.END)
for song in self.AddList:
self.Listbox_find.insert("end", song)
# 清空添加歌曲的列表
def clearSongs(self, *args):
self.AddList = []
self.setColor(10)
self.Listbox_find.delete(0, tk.END)
# 移除添加歌曲的列表
def removeSong(self, *args):
self.setColor(12)
self.AddList.remove(self.Listbox_find.get(self.Listbox_find.curselection()))
self.Listbox_find.delete(0, tk.END)
for song in self.AddList:
self.Listbox_find.insert("end", song)
# 列出添加歌曲
def showSongs(self, *args):
self.setColor(13)
self.Listbox_find.delete(0, tk.END)
for song in self.AddList:
self.Listbox_find.insert("end", song)
# 列出喜欢的歌单
def loveSongs(self, *args):
self.setColor(14)
try:
songOrders = os.listdir(self.LoveSongsPath)
self.Listbox_find.delete(0,tk.END)
for order in songOrders:
order = order[:-4].strip()
if(len(order)>0):
self.Listbox_find.insert("end", order)
except:
pass
#读取收藏的歌单
def readOrders(self, *args):
self.setColor(15)
try:
choosrOrderPath = self.LoveSongsPath + '\\' + self.Listbox_find.get(self.Listbox_find.curselection()) + '.txt'
self.Listbox_find.delete(0,tk.END)
with open(choosrOrderPath,"r+") as f:
lines = f.readlines()
for line in lines:
line = line.strip()
if len(line) > 0:
self.Listbox_find.insert("end", line)
f.close()
except:
pass
# 调节音量大小
def playSetVolume(self, voice):
voice = float(voice)/100.0
pygame.mixer.music.set_volume(float(voice))
# 从歌曲文件夹中读取歌曲
def insertSongs(self):
songList1 = os.listdir(self.OrientPath)
songList2 = list()
self.ListBox_songOrder.delete(0, tk.END)
for song in songList1:
if song[-4:] == '.mp3':
songList2.append(song)
songList2.sort()
for one in songList2:
self.ListBox_songOrder.insert('end', one[:-4])
self.Label_numbers.config(text=self.ListBox_songOrder.size())
# 选中乐库路径
def chooseSongsFloder(self, *args):
self.SWITCH = True
try:
self.OrientPath = filedialog.askdirectory()
self.insertSongs()
except:
self.OrientPath = r'D:\\Music\\CloudMusic\\歌曲'
# 更新乐库
def updateSongs(self, *args):
self.SWITCH = True
songList1 = os.listdir(self.OrientPath)
songList2 = list()
self.ListBox_songOrder.delete(0, tk.END)
for song in songList1:
if song[-4:] == '.mp3':
songList2.append(song)
songList2.sort()
for one in songList2:
self.ListBox_songOrder.insert('end', one[:-4])
self.Label_numbers.config(text=self.ListBox_songOrder.size())
# 获取每行记录中的歌名
def get_song(self, string):
start = string.index("播放")
end = string.index("时长")
name = string[start+2:end-1]
return name
# 获取每行记录中的时长
def get_time(self, string):
start = string.index("时长")
end = len(string) - string[::-1].index("秒")
time = string[start+3:end-1]
return time
# 读取听歌记录
def readtxt(self):
songList = []
timeList = []
filetxt = "songsLog.txt"
with open(filetxt, "r+") as f:
lines = f.readlines()
for line in lines:
songName = self.get_song(line)
songList.append(songName)
songTime = float(self.get_time(line))
timeList.append(songTime)
return songList,timeList
# 将听歌记录的所有歌曲去重,以字典的键值对返回,初始化听歌次数为0
def countInit(self):
songDict = dict()
songs,times = self.readtxt()
songTuple = tuple(songs)
for v in songTuple:
songDict[v] = 0
return songDict
# 统计听歌记录里的歌曲听歌次数,及总时长
def countSongs(self):
songDict = self.countInit()
songList,timeList = self.readtxt()
totalTime = sum(timeList)
for song_L in songList:
for song_D,song_N in songDict.items():
if song_L == song_D:
song_N += 1
songDict[song_D] = song_N
return songDict,totalTime
# 听歌汇总
def countSongInfo(self, *args):
self.setColor(6)
songDict,totalTime = self.countSongs()
sortDict = dict(sorted(songDict.items(), key = lambda x:x[1], reverse = True))
total = sum(songDict.values())
totalTime = int(totalTime)
h = totalTime // 3600
m = (totalTime - h*3600) // 60
s = totalTime - h*3600 - m*60
self.ListBox_songOrder.delete(0, tk.END)
info1 = f" "*20 + "听歌汇报"
info2 = " 听歌时长 {} 秒,等于 {} 小时 {:.2f} 分钟 {:.2f} 秒.".format(totalTime,h,m,s)
self.ListBox_songOrder.insert('end', info1)
self.ListBox_songOrder.insert('end', info2)
self.ListBox_songOrder.insert('end', " ")
for k,v in sortDict.items():
rate = (v/total)*100
rate = round(rate,2)
info = f"{k} 播放了 {v} 次"
self.ListBox_songOrder.insert('end', info)
self.Label_numbers.config(text='')
self.Label_numbers.pack_forget()
# 听歌历史
def listenHistory(self, *args):
self.setColor(7)
try:
songList = []
filetxt = "songsLog.txt"
self.ListBox_songOrder.delete(0, tk.END)
with open(filetxt, "r+") as f:
lines = f.readlines()
lines.reverse()
for line in lines:
self.ListBox_songOrder.insert('end', line)
self.Label_numbers.config(text='')
self.Label_numbers.pack_forget()
except:
pass
# 显示北京时间
def showtime(self, *args):
self.Label_UTC8.config(text=time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
# 显示时间
def uptime(self):
self.Label_UTC8["text"] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S:') + "%d" % (
datetime.datetime.now().microsecond // 100000)
self.screen.after(100, self.uptime)
# 颜色变换函数,button的背景颜色
def setColorWay(self, way):
for i in range(self.COLOR_SIZE):
if way == i:
self.MyColors[i] = self.LightYellow
else:
self.MyColors[i] = self.GlobalColor
# button颜色变换封装
def buttonColor(self):
self.Button_lastSong.config( bg=self.MyColors[0])
self.Button_pause.config( bg=self.MyColors[1])
self.Button_nextSong.config( bg=self.MyColors[2])
self.Button_playSong.config( bg=self.MyColors[3])
self.Button_randomPlay.config( bg=self.MyColors[4])
self.Button_cyclePlay.config( bg=self.MyColors[5])
self.Button_CountSongs.config( bg=self.MyColors[6])
self.Button_ListenHistory.config( bg=self.MyColors[7])
self.Button_Add.config( bg=self.MyColors[8])
self.Button_AddFind.config( bg=self.MyColors[9])
self.Button_Clear.config( bg=self.MyColors[10])
self.Button_FindPlay.config( bg=self.MyColors[11])
self.Button_RemoveSong.config( bg=self.MyColors[12])
self.Button_ShowSongs.config( bg=self.MyColors[13])
self.Button_LoveOrders.config( bg=self.MyColors[14])
self.Button_ReadOrders.config( bg=self.MyColors[15])
# 按钮被点击时变色,封装为一个函数
def setColor(self, num):
self.setColorWay(num)
self.buttonColor()
# 秒数转分钟数
def SecondToMinute(self, seconds):
round(seconds)
m = int(seconds // 60)
s = int(seconds % 60)
if(m<10):
m = "0"+str(m)
if(s<10):
s = "0"+str(s)
return m,s
# 进度条函数
def songProgressbar(self, timelong, flagNo):
time = 0
rate = 0
m2,s2 = self.SecondToMinute(timelong)
# flagNo == self.CHANGE_NEW 确保点击播放歌曲时的线程不会冲突
# rate <= 98 确保上一首歌曲播放完,退出进度条
while (flagNo == self.CHANGE_NEW and rate <= 98):
try:
time = (pygame.mixer.music.get_pos()) / 1000 + self.STEP
time %= timelong
rate = round((time / timelong),4)*100
self.ProgressBar["value"] = rate
self.screen.update()
m1,s1 = self.SecondToMinute(time)
self.Label_songTime["text"] = "{}:{}/{}:{}".format(m1, s1, m2, s2)
self.screen.after(100)
except:
break
# self.CYCLEFLAG == True 确保单曲循环时,可以重复启动进度条
while (flagNo == self.CHANGE_NEW and self.CYCLEFLAG == True):
try:
time = (pygame.mixer.music.get_pos()) / 1000 + self.STEP
time %= timelong
rate = round((time / timelong),4)*100
self.ProgressBar["value"] = rate
self.screen.update()
m1,s1 = self.SecondToMinute(time)
self.Label_songTime["text"] = "{}:{}/{}:{}".format(m1, s1, m2, s2)
self.screen.after(100)
except:
break
def progressNextAdjust(self, seconds, flagNo, *args):
self.SWITCH = True
songInfo = MP3(self.PATH)
timelong = int(songInfo.info.length)
time.sleep(timelong - seconds)
while(flagNo == self.CHANGE_NEW ):
try:
if ( self.PUASEFLAG == True or self.CYCLEFLAG == True):
return self.EXIT
if(self.SONGID < self.ListBox_songOrder.size()-1):
self.SONGID += 1
else:
self.SONGID = 0
self.PATH = self.OrientPath + '\\' + self.ListBox_songOrder.get(self.SONGID) + '.mp3'
self.StrngVar_songName.set(self.ListBox_songOrder.get(self.SONGID))
pygame.mixer.music.load(self.PATH)
pygame.mixer.music.set_volume(100)
pygame.mixer.music.play()
self.Label_numbers.config(text=str(self.SONGID + 1) + '/' + str(self.ListBox_songOrder.size()))
self.Button_pause.config(text='‖', bg = self.GlobalColor)
songInfo = MP3(self.PATH)
timelong = songInfo.info.length
self.songPlayRecord(self.ListBox_songOrder.get(self.SONGID), timelong)
self.STEP = 0
self.ProgressScale.config(from_ = 0, to = timelong)
self.ProgressScale.set(0)
self.Label_offset['text'] = "00:00"
self.Thread_TWO( self.songProgressbar, timelong, flagNo)
self.Thread_TWO( self.showLRC, timelong, flagNo)
time.sleep(int(timelong))
self.screen.update()
except:
break
def songProgressAdjust(self, seconds, *args):
self.SWITCH = True
seconds = int(seconds)
self.STEP = seconds
self.ExitFlag[6] = self.threadChangeFlag()
clearFlag = self.ExitFlag[6]
songInfo = MP3(self.PATH)
timelong = int(songInfo.info.length)
self.ProgressScale.config(from_ = 0, to = timelong)
m1,s1 = self.SecondToMinute(seconds)
self.Label_offset["text"] = "{}:{}".format(m1, s1)
self.TIME_CNT = 0
self.Thread_TWO( self.timer, self.ExitFlag[6], self.PLAY)
self.Thread_TWO( self.songProgressbar, timelong, self.ExitFlag[6])
self.Thread_TWO( self.showLRC, timelong, self.ExitFlag[6])
pygame.mixer.music.play(1, seconds)
self.Thread_TWO(self.progressNextAdjust, seconds, self.ExitFlag[6])
def SongLrcProgress(self, filePath, timelong):
timeList = []
wordList = []
with open(filePath,"r+", encoding = "utf-8") as f:
linelists = f.readlines()
for line in linelists:
if("ar:" in line or "ti:" in line):
continue
if("]" in line):
index = line.index("]")
stime = line[1:index]
word = line[index+1:-1]
keytime = stime.split(":")
realtime = int(float(keytime[0])*60 + float(keytime[1]))
if(realtime < timelong):
timeList.append(realtime)
wordList.append(word)
return timeList,wordList
def chooseLrcPath(self, *args):
try:
self.LrcPath = filedialog.askdirectory()
except:
self.LrcPath = r'D:\\Music\\CloudMusic\\歌词'
def showLRC(self, timelong, flagNo, *args):
self.TIME_CNT = 0
songList = []
timeList = []
wordList = []
word = " "
oldword = " "
insertFlg = 1
# 本地歌曲在酷狗音乐软件播放后会在酷狗目录下生成krc文件,手动通过krc2lrc软件转换成lrc文件
#self.LrcPath = r'D:\\Music\\CloudMusic\\Lyric'
self.Listbox_LRC.delete(0, tk.END)
songOrderPath = os.listdir(self.LrcPath)
for file in songOrderPath:
if file.endswith(".lrc") or file.endswith(".txt"):
songList.append(file)
songName = self.Label_songName["text"]
for song in songList:
if( songName == song[:-4]):
songtxtPath = self.LrcPath + '\\' + str(song)
timeList,wordList = self.SongLrcProgress(songtxtPath, timelong)
if(len(timeList) == 0):
self.Listbox_LRC.insert("end", " ")
self.Listbox_LRC.insert("end", " "*5 + "纯音乐,请欣赏!")
self.Listbox_LRC.insert("end", " ")
return self.EXIT
songTime = self.TIME_CNT
while(flagNo == self.CHANGE_NEW and self.CYCLEFLAG == False
and songTime < (timeList[-1]-1) and songTime < timelong):
songTime = self.TIME_CNT
for i in range(len(timeList)):
if(songTime+1 == timeList[i]):
word = wordList[i]
if(oldword != word):
insertFlg = 1
if(insertFlg == 1):
oldword = word
self.Listbox_LRC.insert("end", " "*4+word)
if (self.Listbox_LRC.size() > 3):
self.Listbox_LRC.delete(0)
insertFlg = 0
time.sleep(1)
#单曲循环
while(flagNo == self.CHANGE_NEW and self.CYCLEFLAG == True
and songTime < (timeList[-1]-1) and songTime < timelong ):
songTime = self.TIME_CNT
songTime %= int(timelong)
for i in range(len(timeList)):
if(songTime+1 == timeList[i]):
word = wordList[i]
if(oldword != word):
insertFlg = 1
if(insertFlg == 1):
oldword = word
self.Listbox_LRC.insert("end", " "*4+word)
if (self.Listbox_LRC.size() > 3):
self.Listbox_LRC.delete(0)
insertFlg = 0
time.sleep(1)
#创建该控件的函数
"""
第一个参数:是定义的控件的名称
第二个参数,是要显示的文字信息
"""
def CreateToolTip(self, widget, text):
toolTip = ToolTip(widget)
def enter(event):
toolTip.showtip(text)
def leave(event):
toolTip.hidetip()
widget.bind('<Enter>', enter)
widget.bind('<Leave>', leave)
#对提示信息控件的定义
class ToolTip(object):
def __init__(self, widget):
self.widget = widget
self.tipwindow = None
self.id = None
self.x = self.y = 0
#当光标移动指定控件是显示消息
def showtip(self, text):
"Display text in tooltip window"
self.text = text
if self.tipwindow or not self.text:
return
x, y, cx, cy = self.widget.bbox("insert")
x = x + self.widget.winfo_rootx()+30
y = y + cy + self.widget.winfo_rooty()+30
self.tipwindow = tw = Toplevel(self.widget)
tw.wm_overrideredirect(True)
tw.wm_geometry("+%d+%d" % (x, y))
label = Label(tw, text=self.text,justify=LEFT,
background="white", relief=SOLID, borderwidth=1,
font=("雅黑", "10"))
label.pack(side=BOTTOM)
#当光标移开时提示消息隐藏
def hidetip(self):
tw = self.tipwindow
self.tipwindow = None
if tw:
tw.destroy()
if __name__=='__main__':
musicApp = MusicPlayer()