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.
python/music.py

337 lines
14 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 wx
import urllib.request
import pygame
import os
import re
import time
from threading import Thread
import math
import datetime
APP_TITLE = u'音乐播放器'
MAX_LYRIC_ROW = 15
LYRIC_ROW_REG = '\[[0-9]{2}:[0-9]{2}.[0-9]{2,}\]'
MAX_MUSIC_NAME_LEN = 18
class mainFrame(wx.Frame):
'''程序主窗口类继承自wx.Frame'''
def __init__(self):
'''构造函数'''
# 播放器的整体属性
self.width = 1280
self.height = 720
self.volume = 0
self.local_music_folder = "music_folder"
wx.Frame.__init__(self, None, -1, APP_TITLE)
self.SetSize(self.width, self.height)
self.SetBackgroundColour((200,200,200)) # 设置界面的背景颜色
# 音乐列表有关
self.local_music_name_list = [] # 当前音乐名字列表
self.lyrcis_static_text = [] # 当前播放的音乐的歌词列表
self.play_stop_button = None # 播放、暂停按钮
self.current_music_state = 0 # 是否有音乐在播放0表示否
self.current_music_index = 0 # 当前音乐的索引
# 初始化本地歌曲列表
self.get_local_music_list()
self.current_music_static_text = None # 当前播放的音乐的名字
# 按钮使用的图片
self.play_bmp = wx.Image("resource/播放.png", wx.BITMAP_TYPE_PNG).Rescale(30, 30).ConvertToBitmap()
self.stop_bmp = wx.Image("resource/暂停.png", wx.BITMAP_TYPE_PNG).Rescale(30, 30).ConvertToBitmap()
# 导航栏所在的panel
self.navi_panel = None
self.draw_navi_panel()
# 歌曲列表所在的panel
self.music_list_panel = None
self.draw_music_list_panel()
# 播放部分所在的panel
self.play_music_panel = None
self.draw_play_music_panel()
# 歌词部分所在的panel
self.music_lyric_panel = None
self.draw_music_lyric_panel()
# 下载音乐面板
self.down_music_panel = None
self.input_url_text_ctrl = None # 输入的下载路径
self.down_button = None # 下载按钮
self.draw_down_music_panel()
pygame.mixer.init()
self.music = pygame.mixer.music
self.SONG_FINISHED = pygame.USEREVENT + 1
def get_path_by_name(self, file_name):
return os.path.join(self.local_music_folder, file_name)
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 draw_navi_panel(self):
# 导航栏所在的panel
self.navi_panel = wx.Panel(self, id=-1, pos=(0, 0), size=(100, self.height - 100))
# 本地音乐
local_music_text = wx.StaticText(self.navi_panel, -1, "本地音乐", pos=(20, 20), style=wx.ALIGN_LEFT)
local_music_text.SetOwnForegroundColour((0, 0, 0))
def draw_music_list_panel(self):
# 重新计算本地音乐列表
self.get_local_music_list()
# 绘制面板整体
if self.music_list_panel is not None:
self.music_list_panel.Destroy()
self.music_list_panel = wx.Panel(self, id=-1, pos=(100, 0), size=(300, self.height - 100))
# 音乐列表
local_music_num = len(self.local_music_name_list)
for music_index in range(local_music_num):
music_full_name = self.local_music_name_list[music_index].replace(".mp3", "")
if len(music_full_name) > MAX_MUSIC_NAME_LEN:
music_full_name = music_full_name[0:MAX_MUSIC_NAME_LEN] + "..."
music_text = wx.StaticText(self.music_list_panel, -1, music_full_name,
pos=(0, music_index * 40 + 20), size=(270, 30), style=wx.ALIGN_RIGHT)
music_text.SetOwnForegroundColour((0,0,0))
music_text.Refresh() # 这句话不能少
play_button = wx.BitmapButton(self.music_list_panel, -1, self.play_bmp, pos=(280, music_index * 40 + 20),
size=(20, 20))
play_button.Bind(wx.EVT_LEFT_DOWN, lambda e, index=music_index: self.play_index_music(index))
def draw_play_music_panel(self):
# 播放音乐所在的panel
self.play_music_panel = wx.Panel(self, id=-1, pos=(0, self.height - 100), size=(self.width, 100))
# 歌的名字
self.current_music_static_text = wx.StaticText(self.play_music_panel, -1, "请选择歌曲",
pos=(100, 15), size=(200, 30), style=wx.ALIGN_RIGHT)
self.current_music_static_text.SetOwnForegroundColour((0,0,0))
last_music_bpm = wx.Image("resource/上一首.png", wx.BITMAP_TYPE_PNG).Rescale(30, 30).ConvertToBitmap()
next_music_bpm = wx.Image("resource/下一首.png", wx.BITMAP_TYPE_PNG).Rescale(30, 30).ConvertToBitmap()
last_music_button = wx.BitmapButton(self.play_music_panel, -1, last_music_bpm, pos=(340, 15), size=(30, 30))
self.play_stop_button = wx.BitmapButton(self.play_music_panel, -1, self.play_bmp, pos=(380, 15), size=(30, 30))
next_music_button = wx.BitmapButton(self.play_music_panel, -1, next_music_bpm, pos=(420, 15), size=(30, 30))
# 调节音量的按钮
volume_slider = wx.Slider(self.play_music_panel, -1, 50, 0, 100, pos=(500, 15), size=(570, -1), style=wx.SL_HORIZONTAL)
# 上述按钮的监听器
last_music_button.Bind(wx.EVT_LEFT_DOWN, self.play_last_music)
self.play_stop_button.Bind(wx.EVT_LEFT_DOWN, self.play_stop_music)
next_music_button.Bind(wx.EVT_LEFT_DOWN, self.play_next_music)
volume_slider.Bind(wx.EVT_SLIDER, self.change_volume)
def draw_down_music_panel(self):
'''
下载音乐所在的面板
:return:
'''
self.down_music_panel = wx.Panel(self, id=-1, pos=(400, 0), size=(self.width - 400, 60))
# 下载地址输入框
self.input_url_text_ctrl = wx.TextCtrl(self.down_music_panel, -1, "请输入下载链接", pos=(100, 20), size=(600, 30))
#self.input_url_text_ctrl.SetOwnBackgroundColour((63, 63, 63))
# 绘制下载图标
down_bmp = wx.Image("resource/下载.png", wx.BITMAP_TYPE_PNG).Rescale(30, 30).ConvertToBitmap()
self.down_button = wx.BitmapButton(self.down_music_panel, -1, down_bmp, pos=(700, 20), size=(30, 30))
# 监听下载图标按钮的鼠标点击事件
self.down_button.Bind(wx.EVT_LEFT_DOWN, self.download_music)
def play_music(self):
'''
重新载入,播放音乐
:return:
'''
current_music_path = self.get_path_by_name(self.local_music_name_list[self.current_music_index])
self.music.load(current_music_path)
pygame.mixer.init()#pygame模块初始化
my_music = pygame.mixer.music
playing_time =my_music.get_pos()
print(playing_time)
# step1播放音乐
self.music.play(loops=1, start=0.0)
# 更改当前播放的音乐的名字
current_music_name = self.local_music_name_list[self.current_music_index].replace(".mp3", "")
if len(current_music_name) > MAX_MUSIC_NAME_LEN:
current_music_name = current_music_name[0:MAX_MUSIC_NAME_LEN] + "..."
self.current_music_static_text.SetLabelText(current_music_name)
# step6开启新线程追踪歌词
self.display_lyric()
def play_index_music(self, music_index):
'''
播放指定索引的音乐
:return:
'''
self.current_music_index = music_index
# 载入音乐
self.play_music()
def play_stop_music(self, evt):
if self.music.get_busy() or self.IsPaused: # 有音乐在播放,需要暂停,或者音乐暂停中
if 1 == self.current_music_state:
print("有音乐在播放,需要暂停")
self.music.pause()
self.current_music_state = 0
self.IsPaused = True
self.play_stop_button.SetBitmap(self.play_bmp)
else: # 恢复暂停的音乐
self.music.unpause()
self.current_music_state = 1
self.IsPaused = False
self.play_stop_button.SetBitmap(self.stop_bmp)
else: # 重新载入音乐
self.play_music()
def play_last_music(self, evt):
# 计算上一首音乐的名字和路径
if self.current_music_index > 0:
self.play_index_music(self.current_music_index - 1)
else:
self.play_index_music(0)
def play_next_music(self, evt):
# 计算下一首音乐的名字和路径
if self.current_music_index < len(self.local_music_name_list) - 1:
self.play_index_music(self.current_music_index + 1)
else:
self.play_index_music(len(self.local_music_name_list) - 1)
def change_volume(self, evt):
'''
修改音量
:param evt:
:return:
'''
obj = evt.GetEventObject()
val = obj.GetValue()
self.volume = float(val / 100)
self.music.set_volume(self.volume)
def download_music(self, evt):
'''
下载音乐
:param evt:
:return:
'''
# 获取文本框中输入的下载地址
music_down_url = self.input_url_text_ctrl.GetValue()
# 给下载的音乐在本地确定一个存储地址
down_music_path = os.path.join(self.local_music_folder, datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S') + ".mp3")
before_music_name = self.local_music_name_list[self.current_music_index]
# 开始下载
urllib.request.urlretrieve(music_down_url, down_music_path)
# 下载完成后,重新绘制音乐列表面板
self.draw_music_list_panel()
# 调整当前音乐的索引
for index in range(len(self.local_music_name_list)):
if before_music_name == self.local_music_name_list[index]:
self.current_music_index = index
def get_lyrics(self):
'''
读取歌词,不带时间标记
:param lyrics_file_path:
:return:
'''
# 获取当前播放的音乐的歌词的完整路径
current_lyric_path = self.get_lyric_path()
if current_lyric_path is None or not os.path.exists(current_lyric_path):
return ["纯音乐或暂无歌词"]
# 打开歌词文件,读取其中的内容
with open(current_lyric_path, 'r', encoding="utf-8") as file_pointer:
content_list = file_pointer.readlines()
# 用一个list记录歌词
lyrics_list = []
# 我们只要歌词部分,不要开头的歌名和作者介绍,因为歌名和作者介绍不带时间
# 不方便我们进行歌词的追踪
for content in content_list:
# 用一个正则表达式过滤出歌词部分
if re.match(LYRIC_ROW_REG, content):
lyric_clause = content.replace('\n', '')[10:]
lyrics_list.append(lyric_clause)
return lyrics_list
def display_lyric(self):
lyric_refersh_thread = Thread(target=self.refersh_lyrics)
lyric_refersh_thread.start()
def parse_lyrics(self):
current_lyric_path = self.get_lyric_path()
if current_lyric_path is None:
content_list = ["[00:00.00]暂无歌词"]
else:
# 读文件内容
with open(current_lyric_path, 'r', encoding="utf-8") as file_pointer:
content_list = file_pointer.readlines()
lyrics_list = []
for content in content_list:
if re.match(LYRIC_ROW_REG, content):
time_lyric = dict()
start_time = float(content[1:3]) * 60 + float(content[4:6]) + float(content[7:9]) / 100
index_of_right_blank = content.index(']')
time_lyric[start_time] = content.replace('\n', '')[index_of_right_blank + 1:]
lyrics_list.append(time_lyric)
return lyrics_list
def refersh_lyrics(self):
'''
刷新歌词子线程
:return:
'''
lyrics_time_dict_list = self.parse_lyrics()
relative_start_index = 0 # 相对起始歌词索引
while self.music.get_busy(): # 播放中
current_time = float(self.music.get_pos() / 1000)
for lyric_index, lyrics_time_dict in enumerate(lyrics_time_dict_list):
lyric_time = list(lyrics_time_dict.keys())[0]
if math.fabs(lyric_time - current_time) < 0.7:
# 当歌词已经超过底部了,则刷新歌词面板,展示第二页的歌词
if lyric_index > 0 and lyric_index % MAX_LYRIC_ROW == 0:
relative_start_index = lyric_index
self.redraw_music_lyric_panel(start_index=relative_start_index)
self.lyrcis_static_text[lyric_index - relative_start_index].SetOwnForegroundColour((227, 62, 51))
# 这句话千万不能少少了颜色不会刷新来自调试了4个小时的忠告
self.lyrcis_static_text[lyric_index - relative_start_index].Refresh()
break
time.sleep(1)
def draw_music_lyric_panel(self):
'''
歌词所在的面板的控制
:return:
'''
self.music_lyric_panel = wx.Panel(self, id=-1, pos=(400, 60), size=(self.width - 400, self.height - 160))
# 获取歌词
lyric_list = self.get_lyrics()
# 展示歌词
for lyric_index in range(MAX_LYRIC_ROW):
if lyric_index < len(lyric_list):
lyric = lyric_list[lyric_index]
else:
lyric = ""
lyric_row = wx.StaticText(self.music_lyric_panel, -1, lyric, pos=(300, 30 * lyric_index + 10),
size=(200, -1), style=wx.ALIGN_CENTER_HORIZONTAL)
lyric_row.SetOwnForegroundColour((255, 255, 255))
self.lyrcis_static_text.append(lyric_row)
def get_lyric_path(self):
current_music_path = self.get_path_by_name(self.local_music_name_list[self.current_music_index])
current_lyric_path = current_music_path.replace(".mp3", ".lrc")
if os.path.exists(current_lyric_path):
return current_lyric_path
else:
return None
if __name__ == "__main__":
app = wx.App()
frame = mainFrame()
frame.Show()
app.MainLoop()
wx.Exit()