You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

879 lines
39 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import os
import re
import threading
import time
import wx
import wx.adv
import wx.lib.mixins.listctrl
from pygame import mixer
import ruijia
user_music_folder = os.path.expanduser(r'~\Music')
DEFAULT_SETTING = { # 默认设置
# 用户可设置
'bg_color': '#88fefe',
'font_size': 13,
'text_color': '#000000',
'lyrics_fg_color': '#ff1122',
'lyrics_bg_color': '#000000',
'volume': 0.3, # 自动保存
'font': '宋体',
'underline': False,
'music_folder_type': 0, # 0表示默认目录1表示用户音乐路径2表示自定义路径
'music_folder_list': ['music_folder', user_music_folder, ''],
# 用户不可设置
'max_music_name_len': 30,
'app_title': 'music_player',
}
FONT_LIST = ['黑体', '楷体', '宋体', '仿宋', 'Arial', '等线', '华文琥珀', '华文行楷', '华文新魏'] # 字体列表
FONT_LIST = [font for font in FONT_LIST if wx.Font().SetFaceName(font)] # 字体可能会不存在
def get_lyrics(lyric_path: str) -> list:
"""
从歌词文件中读取歌词返回列表
:param lyric_path:歌词路径str
:return:歌词列表(时间,文字)元组
"""
lyric_row_reg = '\[[0-9]{2}:[0-9]{2}.[0-9]{2,}\]'
if os.path.exists(lyric_path):
with open(lyric_path, 'r', encoding="utf-8") as f:
content_list = [line.strip() for line in f.readlines()] # 去除每行的\n
lyrics_list = []
for content in content_list:
while re.match(lyric_row_reg, content): # 一句歌词可能有多个时间
lyric_time = float(content[1:3]) * 60 + float(content[4:6]) + float(content[7:9]) / 100
lyric = content[content.rindex(']') + 1:]
lyrics_list.append((lyric_time, lyric))
content = content[content.index(']') + 1:]
lyrics_list.sort(key=lambda x: x[0]) # 按时间排序
return lyrics_list
else:
return [(0, '纯音乐或无歌词')]
def get_setting() -> dict:
"""
从设置文件中读取设置,如不存在则使用默认设置
:return: 设置项目的字典
"""
if os.path.exists('setting.txt'):
with open('setting.txt', 'r', encoding='utf-8') as f:
setting_dic = eval(f.read())
else:
setting_dic = DEFAULT_SETTING
return setting_dic
class RankListNotebook(wx.Notebook):
def __init__(self, parent):
wx.Notebook.__init__(self, parent)
self.SetFont(parent.user_font)
self.text_color = parent.setting_dic['text_color']
url_dic = {
'酷狗飙升榜': 'http://www.kugou.com/yy/rank/home/{}-6666.html',
'酷狗TOP500': 'http://www.kugou.com/yy/rank/home/{}-8888.html',
'华语新歌榜': 'http://www.kugou.com/yy/rank/home/{}-31308.html',
'酷狗雷达榜': 'https://www.kugou.com/yy/rank/home/{}-37361.html'
}
# 进度dialog初始化
max_ = 200*len(url_dic)
dlg = wx.ProgressDialog("处理中", "获取数据中", maximum=max_, parent=self,
style=wx.PD_AUTO_HIDE | wx.PD_CAN_ABORT | wx.PD_APP_MODAL)
count = 0
for name in url_dic: # 每个排行榜为一页
page = wx.ScrolledWindow(self)
self.AddPage(page, name) # TODO 页面描述文字不可设置字体颜色
page.SetScrollbars(10, 10, 1, 1)
sizer = wx.BoxSizer(wx.HORIZONTAL)
page.SetSizer(sizer)
sizer.Add((200, -1), 0) # 设置榜单左方空白区域
column_sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(column_sizer, proportion=1)
column_sizer.Add((0, 50), 0) # 榜单上方空白
for i in range(50): # 进度条更新
dlg.Update(count := count + 1)
if dlg.WasCancelled(): # 当dialog取消时退出循环
break
ranking_list = ruijia.main(url_dic[name]) # 从ruijia.py获取榜单
for line in range(100):
song = str(line+1) + ranking_list[line]['song'] # 排名加上歌名——歌手
st = wx.StaticText(page, -1, song)
st.SetFont(parent.user_font)
st.SetForegroundColour(self.text_color)
column_sizer.Add(st, 1, flag=wx.TOP, border=5)
dlg.Update(count := count + 1) # 进度条更新
column_sizer.Add((0, 50), 0) # 榜单下方空白
for i in range(50): # 进度条更新
dlg.Update(count := count + 1)
if dlg.WasCancelled(): # 当dialog取消时退出循环
break
dlg.Destroy()
class SettingPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.parent = parent
self.present_setting = parent.setting_dic # 当前(未修改)的设置
self.unsaved_setting = parent.unsaved_setting # 修改后未保存的设置
self.bg_color = self.present_setting['bg_color']
self.text_color = self.present_setting['text_color']
# 使设置panel居中显示
setting_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.SetSizer(setting_sizer)
setting_sizer.AddStretchSpacer(1)
self.main_panel = wx.Panel(self, size=(800, 550))
self.main_panel.SetFont(parent.user_font)
self.main_panel.SetForegroundColour(self.text_color)
setting_sizer.Add(self.main_panel, 0, wx.ALIGN_CENTRE)
# 标题----------------------------------------------------------------------------------------------------------
title = wx.StaticText(self.main_panel, -1, '设置(重启后生效)')
title.SetFont(wx.Font(pointSize=23, family=wx.FONTFAMILY_DEFAULT, style=wx.FONTSTYLE_NORMAL,
weight=wx.FONTWEIGHT_NORMAL, faceName='黑体')) # title字体及颜色不可更改
title.SetForegroundColour('#000')
# 重置按钮
reset_button = wx.Button(self.main_panel, -1, '重置', pos=(300, 5))
reset_button.SetForegroundColour(self.text_color)
reset_button.SetBackgroundColour(self.bg_color)
reset_button.Bind(wx.EVT_LEFT_DOWN, self.reset)
# 歌词前景背景颜色 ------------------------------------------------------------------------------------------------
self.box1 = wx.StaticBox(self.main_panel, -1, "歌词设置", pos=(50, 50), size=(700, 70))
# 歌词前景色文字及按钮
text1 = f"歌词前景色:{self.present_setting['lyrics_fg_color']}"
self.lyrics_fg_color_text = wx.StaticText(self.box1, -1, text1, pos=(30, 30))
self.lyrics_fg_color_text.SetForegroundColour(self.present_setting['lyrics_fg_color'])
self.lyrics_fg_color_button = wx.Button(self.box1, -1, '修改', pos=(230, 25))
self.lyrics_fg_color_button.SetForegroundColour(self.text_color)
self.lyrics_fg_color_button.SetBackgroundColour(self.bg_color)
self.lyrics_fg_color_button.Bind(wx.EVT_LEFT_DOWN, self.change_lyrics_fg_color)
# 歌词背景色文字及按钮
text2 = f"歌词背景色:{self.present_setting['lyrics_bg_color']}"
self.lyrics_bg_color_text = wx.StaticText(self.box1, -1, text2, pos=(400, 30))
self.lyrics_bg_color_text.SetForegroundColour(self.present_setting['lyrics_bg_color'])
self.lyrics_bg_color_button = wx.Button(self.box1, -1, '修改', pos=(600, 25))
self.lyrics_bg_color_button.SetForegroundColour(self.text_color)
self.lyrics_bg_color_button.SetBackgroundColour(self.bg_color)
self.lyrics_bg_color_button.Bind(wx.EVT_LEFT_DOWN, self.change_lyrics_bg_color)
# 界面颜色,字体设置 ----------------------------------------------------------------------------------------------
self.box2 = wx.StaticBox(self.main_panel, -1, "界面设置", pos=(50, 145), size=(700, 150))
# 页面背景颜色文字及按钮
text3 = f"页面背景色:{self.bg_color}"
self.bg_color_text = wx.StaticText(self.box2, -1, text3, pos=(30, 30))
self.bg_color_text.SetForegroundColour(self.text_color)
self.bg_color_button = wx.Button(self.box2, -1, '修改', pos=(230, 25))
self.bg_color_button.SetForegroundColour(self.text_color)
self.bg_color_button.SetBackgroundColour(self.bg_color)
self.bg_color_button.Bind(wx.EVT_LEFT_DOWN, self.change_bg_color)
# 文字颜色文字及按钮
text4 = f" 文字颜色 {self.text_color}"
self.text_color_text = wx.StaticText(self.box2, -1, text4, pos=(400, 30))
self.text_color_text.SetForegroundColour(self.text_color)
self.text_color_button = wx.Button(self.box2, -1, '修改', pos=(600, 25))
self.text_color_button.SetForegroundColour(self.text_color)
self.text_color_button.SetBackgroundColour(self.bg_color)
self.text_color_button.Bind(wx.EVT_LEFT_DOWN, self.change_text_color)
# 文字大小文字及text ctrl和spin button
self.font_size_text = wx.StaticText(self.box2, -1, '文字大小:', pos=(30, 90))
font_size = self.present_setting['font_size']
self.font_size = wx.TextCtrl(self.box2, -1, str(font_size), pos=(120, 85),
size=(40, -1), style=wx.TE_READONLY | wx.TE_CENTRE)
self.font_size.SetBackgroundColour(self.bg_color)
self.font_size.SetForegroundColour(self.text_color)
font_size_width = self.font_size.GetSize()[1]
self.font_size_spin = wx.SpinButton(self.box2, -1, pos=(160, 85), size=(15, font_size_width))
self.font_size_spin.SetRange(9, 15)
self.font_size_spin.SetValue(font_size)
self.font_size_spin.Bind(wx.EVT_SPIN, self.change_font_size)
# 文字字体combobox
self.font_name_text = wx.StaticText(self.box2, -1, '当前字体:', pos=(280, 90))
self.font_choice = wx.ComboBox(self.box2, -1, pos=(380, 85),
size=(150, -1), choices=FONT_LIST)
self.font_choice.SetBackgroundColour(self.bg_color)
self.font_choice.SetForegroundColour(self.text_color)
self.font_choice.SetSelection(FONT_LIST.index(self.present_setting['font']))
self.font_choice.Bind(wx.EVT_TEXT, self.change_font)
# 是否显示下划线的checkbox
self.underline = wx.CheckBox(self.box2, -1, '下划线', pos=(600, 88))
self.underline.SetValue(self.present_setting['underline'])
self.underline.Bind(wx.EVT_CHECKBOX, self.is_underline)
# 播放路径设置 ---------------------------------------------------------------------------------------------------
folder_type = self.present_setting['music_folder_type']
self.box3_text = f"歌曲路径选择({['默认路径','用户音乐目录','自定义路径'][folder_type]}"
self.box3 = wx.StaticBox(self.main_panel, -1, self.box3_text,
pos=(50, 320), size=(700, 100))
wx.StaticText(self.box3, -1, '当前路径:', pos=(10, 70))
# 路径textctrl
path = os.path.abspath(self.present_setting['music_folder_list'][folder_type])
self.path_text_ctrl = wx.TextCtrl(self.box3, -1, path, pos=(100, 65), size=(480, -1))
self.path_text_ctrl.SetBackgroundColour(self.bg_color)
self.path_text_ctrl.SetForegroundColour(self.text_color)
# 默认路径按钮
self.folder_button1 = wx.Button(self.box3, -1, label="默认路径", pos=(100, 27))
self.folder_button1.SetBackgroundColour(self.bg_color)
self.folder_button1.SetForegroundColour(self.text_color)
self.folder_button1.Bind(wx.EVT_LEFT_DOWN, self.select_path_type1)
# 用户音乐目录按钮
self.folder_button2 = wx.Button(self.box3, -1, label="用户音乐目录", pos=(233, 27))
self.folder_button2.SetBackgroundColour(self.bg_color)
self.folder_button2.SetForegroundColour(self.text_color)
self.folder_button2.Bind(wx.EVT_LEFT_DOWN, self.select_path_type2)
# 自定义路径按钮
self.button3 = wx.Button(self.box3, -1, label="自定义路径", pos=(400, 27))
self.button3.SetBackgroundColour(self.bg_color)
self.button3.SetForegroundColour(self.text_color)
self.button3.Bind(wx.EVT_LEFT_DOWN, self.select_path_type3)
# 路径选择按钮(自定义路径时显示)
self.path_select_button = wx.Button(self.box3, label="选择路径", pos=(580, 27))
self.path_select_button.SetBackgroundColour(self.bg_color)
self.path_select_button.SetForegroundColour(self.text_color)
self.path_select_button.Bind(wx.EVT_LEFT_DOWN, self.select_path)
# 确认路径按钮(自定义路径时显示)
self.folder_confirm_button = wx.Button(self.box3, label="确认", pos=(607, 65))
self.folder_confirm_button.SetBackgroundColour(self.bg_color)
self.folder_confirm_button.SetForegroundColour(self.text_color)
self.folder_confirm_button.Bind(wx.EVT_LEFT_DOWN, self.folder_confirm)
self.hide_show_button(folder_type == 2) # 是否显示路径选择按钮和确认路径按钮
setting_sizer.AddStretchSpacer(1)
def reset(self, evt):
"""
重置为默认设置
"""
for i in DEFAULT_SETTING:
self.unsaved_setting[i] = DEFAULT_SETTING[i]
dlg = wx.MessageDialog(self, r'成功重置所有设置,重启后生效', '成功', wx.OK)
dlg.ShowModal()
dlg.Destroy()
def change_lyrics_fg_color(self, evt):
"""
修改歌词前景色
"""
dlg = wx.ColourDialog(self)
if dlg.ShowModal() == wx.ID_OK:
color = dlg.GetColourData().GetColour().GetAsString(flags=wx.C2S_HTML_SYNTAX)
self.unsaved_setting['lyrics_fg_color'] = color
self.lyrics_fg_color_text.SetForegroundColour(color)
self.lyrics_fg_color_text.SetLabel(f'歌词前景色:{color}')
dlg.Destroy()
def change_lyrics_bg_color(self, evt):
"""
修改歌词背景色
"""
dlg = wx.ColourDialog(self)
if dlg.ShowModal() == wx.ID_OK:
color = dlg.GetColourData().GetColour().GetAsString(flags=wx.C2S_HTML_SYNTAX)
self.unsaved_setting['lyrics_bg_color'] = color
self.lyrics_bg_color_text.SetForegroundColour(color)
self.lyrics_bg_color_text.SetLabel(f'歌词背景色:{color}')
dlg.Destroy()
def change_bg_color(self, evt):
"""
修改界面背景色
"""
dlg = wx.ColourDialog(self)
if dlg.ShowModal() == wx.ID_OK:
color = dlg.GetColourData().GetColour().GetAsString(flags=wx.C2S_HTML_SYNTAX)
self.unsaved_setting['bg_color'] = color
self.bg_color_text.SetBackgroundColour(color)
self.bg_color_text.SetLabel(f'页面背景色:{color}')
dlg.Destroy()
def change_text_color(self, evt):
"""
修改文字颜色(不包括歌词)
"""
dlg = wx.ColourDialog(self)
if dlg.ShowModal() == wx.ID_OK:
color = dlg.GetColourData().GetColour().GetAsString(flags=wx.C2S_HTML_SYNTAX)
self.unsaved_setting['text_color'] = color
self.text_color_text.SetForegroundColour(color)
self.text_color_text.SetLabel(f' 文字颜色 {color}')
dlg.Destroy()
def change_font_size(self, evt):
"""
修改文字大小
"""
size = self.font_size_spin.GetValue()
self.font_size.SetLabel(str(size))
self.unsaved_setting['font_size'] = size
def change_font(self, evt):
"""
修改文字字体
"""
font_name = evt.GetEventObject().GetValue()
if font_name in FONT_LIST:
self.unsaved_setting['font'] = font_name
# font = self.parent.user_font 此方式会在后续设置中更改父类中的原字体
font = wx.Font(self.parent.user_font)
font.SetFaceName(font_name)
self.font_name_text.SetFont(font)
self.font_choice.SetFont(font)
def is_underline(self, evt):
"""
修改是否显示下划线
"""
underline = self.underline.GetValue()
# font = self.parent.user_font 此方式会在后续设置中更改父类中的原字体
font = wx.Font(self.parent.user_font)
font.SetUnderlined(underline)
self.underline.SetFont(font)
self.unsaved_setting['underline'] = underline
def select_path_type1(self, evt):
"""
修改路径类型(默认路径)
"""
self.hide_show_button(0)
self.box3.SetLabel('歌曲路径选择(默认路径)')
self.box3.Refresh()
self.unsaved_setting['music_folder_type'] = 0
folder_path = os.path.abspath('music_folder')
self.path_text_ctrl.SetLabel(folder_path)
dlg = wx.MessageDialog(self, r'成功设置路径:默认路径(.\music_folder)', '成功', wx.OK)
dlg.ShowModal()
dlg.Destroy()
def select_path_type2(self, evt):
"""
修改路径类型(用户音乐目录)
"""
self.hide_show_button(0)
self.box3.SetLabel('歌曲路径选择(用户音乐目录)')
self.box3.Refresh()
self.unsaved_setting['music_folder_type'] = 1
folder_path = os.path.expanduser('~\Music')
self.path_text_ctrl.SetLabel(folder_path)
dlg = wx.MessageDialog(self, rf'成功设置路径:用户音乐目录({folder_path})', '成功', wx.OK)
dlg.ShowModal()
dlg.Destroy()
def select_path_type3(self, evt):
"""
修改路径类型(自定义路径)并显示选择路径及确认按钮
"""
self.hide_show_button(1)
self.box3.SetLabel('歌曲路径选择(自定义路径)')
self.box3.Refresh()
def hide_show_button(self, op: int):
"""
是否显示选择路径及确认按钮及路径textctrl是否可修改
"""
if op == 0:
self.path_select_button.Hide()
self.folder_confirm_button.Hide()
self.path_text_ctrl.SetEditable(False)
elif op == 1:
self.path_select_button.Show()
self.folder_confirm_button.Show()
self.path_text_ctrl.SetEditable(True)
def select_path(self, evt):
"""
选择路径将其显示在路径textctrl中
"""
dlg = wx.DirDialog(self, "选择音乐文件夹", defaultPath=os.getcwd(), style=wx.DD_DEFAULT_STYLE)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
self.path_text_ctrl.SetLabel(path)
dlg.Destroy()
def folder_confirm(self, evt):
"""
确认路径
"""
path = self.path_text_ctrl.GetValue()
if path == 'fly': # fly
dlg = wx.Dialog(self, size=(333, 280), title='fly')
wx.adv.AnimationCtrl(dlg, -1, wx.adv.Animation(r'icon\fly')).Play()
elif os.path.exists(path): # 路径存在
self.unsaved_setting['music_folder_list'][2] = path
dlg = wx.MessageDialog(self, '成功设置路径:'+path, '成功', wx.OK)
self.unsaved_setting['music_folder_type'] = 2
else: # 路径不存在
dlg = wx.MessageDialog(self, '请输入正确的路径', '错误', wx.OK | wx.ICON_ERROR)
dlg.ShowModal()
dlg.Destroy()
class AutoWidthListCtrl(wx.ListCtrl, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin):
def __init__(self, parent, *args, **kw):
# 设置歌曲列表list ctrl的列随sizer的改变而自动改变宽度
wx.ListCtrl.__init__(self, parent, *args, **kw)
wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self)
class MainFrame(wx.Frame):
def __init__(self):
self.setting_dic = get_setting()
wx.Frame.__init__(self, None, -1, self.setting_dic['app_title'], pos=(50, 50))
self.SetSize(1280, 720)
self.SetSizeHints((1080, 600)) # 设置页面最小大小
self.bg_color = self.setting_dic['bg_color']
self.font_size = self.setting_dic['font_size']
self.SetBackgroundColour(self.bg_color)
self.play_png = wx.Image("icon/tiny-start.png", wx.BITMAP_TYPE_PNG).\
Rescale(30, 30).ConvertToBitmap()
self.stop_png = wx.Image("icon/tiny-pause.png", wx.BITMAP_TYPE_PNG).\
Rescale(30, 30).ConvertToBitmap()
# 字体设置
self.user_font = wx.Font(pointSize=self.font_size, family=wx.FONTFAMILY_DEFAULT,
style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_NORMAL)
self.user_font.SetFaceName(self.setting_dic['font'])
self.user_font.SetUnderlined(self.setting_dic['underline'])
# 绑定窗口关闭事件
self.is_running = True
self.Bind(wx.EVT_CLOSE, self.close_window)
# 从设置中读取歌曲路径并读取歌曲
folder_type = self.setting_dic['music_folder_type']
self.local_music_folder = self.setting_dic['music_folder_list'][folder_type]
self.local_music_name_list = []
self.get_local_music_list()
self.lyrics_static_text_list = []
# 音乐播放初始化
mixer.init()
self.volume = self.setting_dic['volume']
self.music = mixer.music
self.music.set_volume(self.volume)
self.current_music_state = 0 # 0表示未播放1表示播放中2表示暂停中
self.current_music_index = 0
# base_sizer将界面分为左右两部分左侧为导航栏sizer右侧初始为主界面
self.base_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.unsaved_setting = self.setting_dic.copy()
navi_sizer = self.draw_navigator()
self.base_sizer.Add(navi_sizer, 0, wx.EXPAND)
self.main_interface_panel = None
self.music_list_ctrl = None
self.lyrics_panel = None
self.setting_panel = None
self.ranking_notebook = None
self.draw_main_interface()
self.SetSizer(self.base_sizer)
self.Show()
def draw_navigator(self):
"""
绘制导航栏,按钮切换界面
:return: 导航栏sizer
"""
navi_sizer = wx.BoxSizer(wx.VERTICAL)
local_music_button = wx.Button(self, label="本地音乐", size=(200, 100))
navi_sizer.Add(local_music_button)
local_music_button.SetFont(self.user_font)
local_music_button.SetForegroundColour(self.setting_dic['text_color'])
local_music_button.SetBackgroundColour(self.bg_color)
local_music_button.Bind(wx.EVT_LEFT_DOWN, self.main_interface_button_evt)
navi_sizer.Add((0, 0), 1)
ranking_button = wx.Button(self, label="排行榜", size=(200, 100))
navi_sizer.Add(ranking_button)
ranking_button.SetFont(self.user_font)
ranking_button.SetForegroundColour(self.setting_dic['text_color'])
ranking_button.SetBackgroundColour(self.bg_color)
ranking_button.Bind(wx.EVT_LEFT_DOWN, self.ranking_button_evt)
navi_sizer.Add((0, 0), 1)
setting_button = wx.Button(self, label="设置", size=(200, 100))
navi_sizer.Add(setting_button)
setting_button.SetFont(self.user_font)
setting_button.SetForegroundColour(self.setting_dic['text_color'])
setting_button.SetBackgroundColour(self.bg_color)
setting_button.Bind(wx.EVT_LEFT_DOWN, self.setting_button_evt)
navi_sizer.Add((0, 0), 3)
return navi_sizer
def main_interface_button_evt(self, evt):
"""
当设置界面或排行榜界面存在时将其隐藏,在主界面未显示时显示主界面
"""
if self.setting_panel and self.setting_panel.IsShown():
self.setting_panel.Hide()
if self.ranking_notebook and self.ranking_notebook.IsShown():
self.ranking_notebook.Hide()
if not self.main_interface_panel.IsShown():
self.main_interface_panel.Show()
def draw_main_interface(self):
"""
绘制主界面
"""
# 主界面panel设置了main_interface_sizermain_interface_sizer将主界面分为上下两部分下面部分为控制区域
# 高度始终不变上面部分为sizer1大小可变包含歌曲列表及歌词面板部分。
self.main_interface_panel = wx.Panel(self)
self.base_sizer.Add(self.main_interface_panel, 1, wx.EXPAND)
main_interface_sizer = wx.BoxSizer(wx.VERTICAL)
self.main_interface_panel.SetSizer(main_interface_sizer)
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
main_interface_sizer.Add(sizer1, 1, wx.EXPAND)
self.get_local_music_list()
# 歌曲列表list ctrl----------------------------------------------------------------------------------------------
self.music_list_ctrl = AutoWidthListCtrl(self.main_interface_panel, -1,
style=wx.LC_REPORT
# | wx.LC_NO_HEADER
)
self.music_list_ctrl.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.list_ctrl_evt)
self.music_list_ctrl.SetBackgroundColour(self.bg_color)
self.music_list_ctrl.SetFont(self.user_font)
self.music_list_ctrl.SetForegroundColour(self.setting_dic['text_color'])
# TODO: header背景颜色无法设置若使用no header的风格则list ctrl宽度有问题
self.music_list_ctrl.InsertColumn(0, '歌曲列表')
self.music_list_ctrl.SetHeaderAttr(wx.ItemAttr(self.setting_dic['text_color'], None, self.user_font))
sizer1.Add(self.music_list_ctrl, 2, wx.EXPAND)
for music in self.local_music_name_list:
music_full_name = music.replace(".mp3", "") # 删除后缀
self.music_list_ctrl.InsertItem(0, music_full_name, 0)
# 歌词面板-------------------------------------------------------------------------------------------------------
self.lyrics_panel = wx.Panel(self.main_interface_panel)
sizer1.Add(self.lyrics_panel, 9, wx.EXPAND)
# 控制区域-------------------------------------------------------------------------------------------------------
control_panel = wx.Panel(self.main_interface_panel)
control_sizer = wx.BoxSizer(wx.HORIZONTAL)
control_panel.SetSizer(control_sizer)
control_panel.Fit()
main_interface_sizer.Add(control_panel, 0, wx.EXPAND)
# 上一首按钮
last_music_png = wx.Image("icon/media-skip-backward.png", wx.BITMAP_TYPE_PNG).ConvertToBitmap()
next_music_png = wx.Image("icon/media-skip-forward.png", wx.BITMAP_TYPE_PNG).ConvertToBitmap()
last_button = wx.BitmapButton(control_panel, -1, last_music_png, size=(30, 30))
last_button.SetToolTip('上一首音乐')
last_button.Bind(wx.EVT_LEFT_DOWN, self.play_last_music)
# 播放暂停按钮
if self.current_music_state == 1:
current_png = self.stop_png
else:
current_png = self.play_png
self.play_stop_button = wx.BitmapButton(control_panel, -1, current_png, size=(30, 30))
self.play_stop_button.SetToolTip('播放/暂停音乐')
self.play_stop_button.Bind(wx.EVT_LEFT_DOWN, self.play_stop_music)
# 下一首按钮
next_button = wx.BitmapButton(control_panel, -1, next_music_png, size=(30, 30))
next_button.SetToolTip('下一首音乐')
next_button.Bind(wx.EVT_LEFT_DOWN, self.play_next_music)
control_sizer.Add(last_button, 0, wx.EXPAND | wx.ALL, border=10)
control_sizer.Add(self.play_stop_button, 0, wx.EXPAND | wx.ALL, border=10)
control_sizer.Add(next_button, 0, wx.EXPAND | wx.ALL, border=10)
# 正在播放的歌曲名
self.current_music_static_text = wx.StaticText(control_panel, -1, '未选择')
self.current_music_static_text.SetFont(self.user_font)
self.current_music_static_text.SetForegroundColour(self.setting_dic['text_color'])
control_sizer.Add(self.current_music_static_text, 0, wx.ALIGN_CENTER_VERTICAL, border=10)
control_sizer.Add(0, 1, wx.EXPAND) # 增加空白
# 音量滑动条及当前音量大小文字
volume_slider = wx.Slider(control_panel, -1, int(self.volume * 100), 0, 100)
volume_slider.Bind(wx.EVT_SLIDER, self.change_volume)
self.volume_text = wx.StaticText(control_panel, -1, f'音量{int(self.volume * 100)}%')
self.volume_text.SetFont(self.user_font)
self.volume_text.SetForegroundColour(self.setting_dic['text_color'])
control_sizer.Add(self.volume_text, 0, wx.ALIGN_CENTER_VERTICAL, border=10)
control_sizer.Add(volume_slider, 0, wx.EXPAND | wx.ALL, border=10)
self.base_sizer.Layout()
def get_local_music_list(self):
"""
获取歌词列表
"""
self.local_music_name_list.clear()
for local_music_file_name in os.listdir(self.local_music_folder):
if local_music_file_name.endswith(".mp3"):
self.local_music_name_list.append(local_music_file_name)
def list_ctrl_evt(self, evt):
"""
在歌曲列表双击时播放歌曲
"""
self.current_music_name = evt.GetItem().GetText()
self.play_music()
def play_stop_music(self, evt):
"""
播放暂停按钮事件
"""
if 1 == self.current_music_state:
self.music.pause()
self.current_music_state = 2
self.play_stop_button.SetBitmap(self.play_png)
elif 2 == self.current_music_state:
self.music.unpause()
self.current_music_state = 1
self.play_stop_button.SetBitmap(self.stop_png)
elif 0 == self.current_music_state:
self.current_music_name = self.local_music_name_list[0].replace('.mp3', '')
self.play_music()
def play_music(self):
"""
播放歌曲
"""
self.lyrics_panel.DestroyChildren()
full_path = os.path.join(self.local_music_folder, self.current_music_name) + '.mp3'
self.music.load(full_path)
self.music.play()
self.current_music_state = 1
self.current_music_index = self.local_music_name_list.index(self.current_music_name + '.mp3')
self.play_stop_button.SetBitmap(self.stop_png)
lyric_path = os.path.join(self.local_music_folder, self.current_music_name) + '.lrc'
self.lyrics_list = get_lyrics(lyric_path)
self.draw_music_lyrics_panel()
# 歌词刷新线程
lyric_refresh_thread = threading.Thread(target=self.refresh_lyrics)
lyric_refresh_thread.start()
# 当前播放音乐名(控制区域)
current_music_name = self.local_music_name_list[self.current_music_index].replace(".mp3", "")
max_music_name_len = self.setting_dic['max_music_name_len']
if len(current_music_name) > max_music_name_len:
current_music_name = self.current_music_name[0:max_music_name_len] + "..."
self.current_music_static_text.SetLabelText('正在播放:' + current_music_name)
self.current_music_static_text.SetToolTip(self.current_music_name)
def draw_music_lyrics_panel(self):
"""
绘制歌词面板
"""
# 清空歌词static text列表
self.lyrics_static_text_list.clear()
length, width = self.lyrics_panel.GetSize()
total_lyrics_row = int(1080 / 30) # 页面最多歌词行数仅支持1080p及以下屏幕)
max_lyrics_row = int(width / 30)
for lyric_index in range(total_lyrics_row):
if lyric_index < max_lyrics_row and lyric_index < len(self.lyrics_list):
time_lyric_tuple = self.lyrics_list[lyric_index]
else:
time_lyric_tuple = (-1, '') # 时间为-1表示空行
lyric_row = wx.StaticText(self.lyrics_panel, -1, time_lyric_tuple[1],
pos=(int((length - 400) * 0.8 / 2), 30 * lyric_index + 10),
size=(400, 30), style=wx.ALIGN_CENTER_HORIZONTAL)
lyric_row.SetForegroundColour(self.setting_dic['lyrics_bg_color'])
lyric_row.SetFont(self.user_font)
self.lyrics_static_text_list.append((time_lyric_tuple[0], lyric_row))
def refresh_lyrics(self):
"""
刷新歌词线程
"""
music_index = self.current_music_index
length, width = self.lyrics_panel.GetSize()
# 当前显示的歌词行数
current_shown_lyrics_num = len(list(filter(lambda x: x != '',
(i[1].GetLabel() for i in self.lyrics_static_text_list))))
current_start_lyrics_index = 0
while music_index == self.current_music_index and self.is_running:
# 刷新歌词颜色
lyrics_fg_color = self.setting_dic['lyrics_fg_color']
for lyric_time, lyric_row in self.lyrics_static_text_list:
# 当歌词时间大于0-1为空行小于当前时间且颜色不为前景色时将其设置为前景色
if 0 <= lyric_time <= self.music.get_pos()/1000 and \
lyric_row.ForegroundColour != wx.Colour(lyrics_fg_color):
lyric_row.SetForegroundColour(lyrics_fg_color)
lyric_row.Refresh()
# 刷新歌词位置
if length != self.lyrics_panel.GetSize()[0]:
length = self.lyrics_panel.GetSize()[0]
for ind, (_, row) in enumerate(self.lyrics_static_text_list):
row.SetPosition((int((length - 400) * 0.8 / 2), 30 * ind + 10))
# 刷新歌词行数
if width != self.lyrics_panel.GetSize()[1]:
width = self.lyrics_panel.GetSize()[1]
max_lyrics_row = int(width / 30)
# 可显示行数小于已显示行数,将多余行设为空行
if max_lyrics_row < current_shown_lyrics_num:
for ind, (_, lyric_row) in enumerate(self.lyrics_static_text_list[max_lyrics_row:]):
lyric_row.SetLabel('')
self.lyrics_static_text_list[max_lyrics_row + ind] = (-1, lyric_row)
# 可显示行数大于已显示行数,显示不足行
else:
lst = self.lyrics_static_text_list[current_shown_lyrics_num:max_lyrics_row] # 需增加的歌词
for ind, (_, lyric_row) in enumerate(lst):
start_index = current_start_lyrics_index + current_shown_lyrics_num # 需增加的歌词在歌词列表中的起始位置
if start_index + ind < len(self.lyrics_list):
lyric_time, lyric_text = self.lyrics_list[start_index + ind]
lyric_row.SetLabel(lyric_text)
# 当设置完歌曲最后一行歌词后将其余行设置为空
else:
lyric_time = -1
lyric_row.SetLabel('')
self.lyrics_static_text_list[current_shown_lyrics_num+ind] = (lyric_time, lyric_row)
current_shown_lyrics_num = max_lyrics_row # 当前显示的歌词行数
# 歌词到底后刷新下一页
new_page_index = int(width / 30) + current_start_lyrics_index # 下一页起始索引
# 当下一页起始歌词索引小于歌词总行数且离下一页第一句歌词时间小于0.5s时绘制下一页
if new_page_index < len(self.lyrics_list) and \
abs(self.lyrics_list[new_page_index][0] - self.music.get_pos()/1000) < 0.5:
current_start_lyrics_index = new_page_index
max_lyrics_row = int(width / 30)
for ind, (_, lyric_row) in enumerate(self.lyrics_static_text_list):
lyric_row.SetForegroundColour(self.setting_dic['lyrics_bg_color'])
if ind < max_lyrics_row and new_page_index + ind < len(self.lyrics_list):
lyric_time, lyric_text = self.lyrics_list[new_page_index + ind]
else:
lyric_time, lyric_text = (-1, '')
lyric_row.SetLabel(lyric_text)
self.lyrics_static_text_list[ind] = (lyric_time, lyric_row)
time.sleep(0.5)
def play_last_music(self, evt):
"""
上一首按钮事件
"""
if self.current_music_state == 0: # 未开始播放时按钮无效
return
elif self.current_music_index > 1:
self.current_music_index -= 1
else:
self.current_music_index = len(self.local_music_name_list) - 1
self.current_music_name = self.local_music_name_list[self.current_music_index].replace('.mp3', '')
self.play_music()
def play_next_music(self, evt):
"""
下一首按钮事件
"""
if self.current_music_state == 0: # 未开始播放时按钮无效
return
elif self.current_music_index < len(self.local_music_name_list) - 1:
self.current_music_index += 1
else:
self.current_music_index = 0
self.current_music_name = self.local_music_name_list[self.current_music_index].replace('.mp3', '')
self.play_music()
def change_volume(self, evt):
"""
音量滑动条事件
"""
obj = evt.GetEventObject()
val = obj.GetValue()
self.volume = float(val / 100)
self.music.set_volume(self.volume)
self.volume_text.SetLabel(f'音量{int(self.volume * 100)}%')
self.unsaved_setting['volume'] = self.volume
def setting_button_evt(self, evt):
"""
当主界面或排行榜界面显示时将其隐藏,在设置界面不存在时绘制界面,存在时显示设置界面
"""
if self.main_interface_panel.IsShown():
self.main_interface_panel.Hide()
if self.ranking_notebook and self.ranking_notebook.IsShown():
self.ranking_notebook.Hide()
if not self.setting_panel:
self.draw_setting_interface()
if not self.setting_panel.IsShown():
self.setting_panel.Show()
def ranking_button_evt(self, evt):
"""
当主界面或设置界面显示时将其隐藏,在排行榜界面不存在时绘制界面,存在时显示排行榜界面
"""
if self.main_interface_panel.IsShown():
self.main_interface_panel.Hide()
if self.setting_panel and self.setting_panel.IsShown():
self.setting_panel.Hide()
if not self.ranking_notebook:
self.draw_ranking_notebook()
if not self.ranking_notebook.IsShown():
self.ranking_notebook.Show()
def draw_setting_interface(self):
"""
绘制设置界面
"""
self.setting_panel = SettingPanel(self)
self.base_sizer.Add(self.setting_panel, 1, wx.EXPAND)
self.base_sizer.Layout()
def draw_ranking_notebook(self):
"""
绘制排行榜界面
"""
self.ranking_notebook = RankListNotebook(self)
self.base_sizer.Add(self.ranking_notebook, 1, wx.EXPAND)
self.base_sizer.Layout()
def close_window(self, evt):
"""
关闭界面事件
"""
self.is_running = False # 刷新歌词线程终止的条件之一
# 退出时保存设置
with open('setting.txt', 'w', encoding='utf-8') as f:
f.writelines(str(self.unsaved_setting))
evt.Skip() # 继续关闭事件
if __name__ == "__main__":
app = wx.App()
frame = MainFrame()
app.MainLoop()