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_sizer,main_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()