制作缘由
本来我是通过活动白嫖了某云音乐的黑胶VIP的,当时我下了很多VIP才能听整首的歌。但是VIP过期后,这音乐就播放不了了,一方面是它VIP歌曲存储为.ncm文件,另一方面是我将这ncm文件解码成MP3文件后,并将其导入我的云盘里,这软件依旧只给我播放歌的一小部分。(岂可修!!)
所以,我决定自己写个播放器,再见了某云。
图片预览及用法
如下图
ICON文件夹: 存储程序所需要的图片
list_dic文件夹: 存储我的歌单
见下图
各个按钮部件是干什么的大家都懂
左边的加号是创建新歌单
右边的加号是给歌单加歌
选定某一歌单时会显示歌单内歌曲信息,并可以播放歌曲
新建歌单窗口
部分界面代码和思路
首先创建主部件
并向内添加上,左,右部件
上部件:控制窗口最小化和关闭
左部件: 创建和选择歌单
右部件: 播放歌曲和对歌单进行查看及添加歌曲
self.setFixedSize(960, 700)
self.main_widget = QtWidgets.QWidget() # 创建窗口主部件
self.main_layout = QtWidgets.QGridLayout() # 创建主部件的网格布局
self.main_widget.setLayout(self.main_layout) # 设置窗口部件为网格
# self.setWindowOpacity(1) # 设置窗口透明度
self.setAttribute(QtCore.Qt.WA_TranslucentBackground) # 设置窗口背景透明度
self.setWindowFlag(QtCore.Qt.FramelessWindowHint) # 隐藏边框
self.main_layout.setSpacing(0)
self.top_widget = QtWidgets.QWidget() # 创建上侧部件
self.top_widget.setObjectName('top_widget')
self.top_layout = QtWidgets.QGridLayout()
self.top_widget.setLayout(self.top_layout)
self.left_widget = QtWidgets.QWidget() # 创建左侧部件
self.left_widget.setObjectName('left_widget')
self.left_layout = QtWidgets.QGridLayout()
self.left_widget.setLayout(self.left_layout)
self.right_widget = QtWidgets.QWidget() # 创建右侧部件
self.right_widget.setObjectName('right_widget')
self.right_layout = QtWidgets.QGridLayout()
self.right_widget.setLayout(self.right_layout)
# 部件部署
self.main_layout.addWidget(self.top_widget, 0, 0, 1, 12)
self.main_layout.addWidget(self.left_widget, 1, 0, 12, 2)
self.main_layout.addWidget(self.right_widget, 1, 2, 12, 10)
self.setCentralWidget(self.main_widget)
上侧控件的定义和部署
# 上侧控件定义
self.top_close = QtWidgets.QPushButton(qtawesome.icon('fa.times', color='#FFFFFF'), '')
self.top_mini = QtWidgets.QPushButton(qtawesome.icon('fa.window-minimize', color='#FFFFFF'), '')
self.top_label = QtWidgets.QLabel(' 本地音乐播放器')
self.top_close.clicked.connect(self.event_close)
self.top_mini.clicked.connect(self.showMinimized)
self.top_close.setFixedWidth(50)
self.top_mini.setFixedWidth(50)
# 上侧控件部署
self.top_layout.addWidget(self.top_label, 0, 0, 1, 7)
self.top_layout.addWidget(self.top_close, 0, 11, 1, 1)
self.top_layout.addWidget(self.top_mini, 0, 9, 1, 1)
左侧控件定义和部署
# 左侧控件定义
self.left_add = QtWidgets.QPushButton(qtawesome.icon('fa.plus', color='#FFFFFF'), '') # 添加按钮
self.left_label_1 = QtWidgets.QLabel(" 我的歌单 ")
self.left_list = QtWidgets.QListWidget()
self.left_add.clicked.connect(self.add_songlist)
self.left_list.itemDoubleClicked.connect(self.open_songlist)
self.left_add.setFixedSize(20,20)
for i in os.listdir('./list_dic'):
if i.endswith('.list'):
self.left_list.addItem(i.rstrip('.list'))
# 左侧控件部署
self.left_layout.addWidget(self.left_add, 0, 2, 1, 1)
self.left_layout.addWidget(self.left_label_1, 0, 0, 1, 2)
self.left_layout.addWidget(self.left_list, 1, 0, 11, 3)
右侧控件的定义和部署
# 右侧控件定义
self.right_layout.setSpacing(0)
# 歌单控件
self.right_songlist_widget = QtWidgets.QWidget()
self.right_songlist_layout = QtWidgets.QGridLayout()
self.right_songlist_widget.setLayout(self.right_songlist_layout)
self.songlist_add = QtWidgets.QPushButton('+') # 添加按钮
self.songlist_add.clicked.connect(self.add_song)
self.songlist_label = QtWidgets.QLabel(" 正在播放: 无")
self.songlist_table = QtWidgets.QTableWidget(100, 3)
self.songlist_table.itemDoubleClicked.connect(self.now_play)
self.songlist_add.setFixedSize(20,20)
self.songlist_table.setHorizontalHeaderLabels(['音乐标题', '歌手', '时长'])
self.songlist_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) # 表格禁止编辑
self.songlist_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) # 设置整行选中
self.songlist_table.setColumnWidth(0, 300)
self.songlist_table.setColumnWidth(1, 150)
self.songlist_table.setColumnWidth(2, 100)
# 进度条控件
self.right_process_widget = QtWidgets.QWidget()
self.right_process_layout = QtWidgets.QGridLayout()
self.right_process_widget.setLayout(self.right_process_layout)
self.process_label = QtWidgets.QLabel("00:00/05:00")
self.right_process_bar = progressSlider(QtCore.Qt.Horizontal)
self.right_process_bar.clicked.connect(self.click_process_slider)
self.right_process_bar.setFixedHeight(30)
self.right_process_bar.setMinimum(0)
self.right_process_bar.setMaximum(self.process_max)
self.right_process_bar.setSingleStep(1)
self.right_process_bar.setValue(0)
self.right_process_bar.setFixedHeight(12)
# 播放控件
self.right_playconsole_widget = QtWidgets.QWidget()
self.right_playconsole_layout = QtWidgets.QGridLayout()
self.right_playconsole_widget.setLayout(self.right_playconsole_layout)
self.console_button_1 = QtWidgets.QPushButton(qtawesome.icon('fa.step-backward', color='#F76677'), '')
self.console_button_2 = QtWidgets.QPushButton(qtawesome.icon('fa.step-forward', color='#F76677'), '')
self.console_button_3 = QtWidgets.QPushButton(qtawesome.icon('fa.play', color='#F76677', font=18), '')
self.console_button_3.setIconSize(QtCore.QSize(30,30))
self.console_button_4 = QtWidgets.QPushButton(qtawesome.icon('fa.volume-up'), '')
self.console_button_5 = QtWidgets.QPushButton(qtawesome.icon('fa.random', color='#F76677'), '')
self.console_button_1.clicked.connect(self.prev_song)
self.console_button_2.clicked.connect(self.next_song)
self.console_button_3.clicked.connect(self.play_song)
self.console_button_4.clicked.connect(self.play_volume)
self.console_button_5.clicked.connect(self.play_mode)
self.console_slide = progressSlider(QtCore.Qt.Horizontal)
self.console_slide.clicked.connect(self.click_console_slider)
self.console_slide.setFixedSize(100, 30)
self.console_slide.setMaximum(100)
self.console_slide.setMinimum(0)
self.console_slide.setSingleStep(1)
self.console_slide.setValue(self.Volume)
# 右侧控件部署
self.right_songlist_layout.addWidget(self.songlist_label, 0, 0, 1, 2)
self.right_songlist_layout.addWidget(self.songlist_add, 0, 8, 1, 1)
self.right_songlist_layout.addWidget(self.songlist_table, 1, 0, 7, 9)
self.right_playconsole_layout.addWidget(self.console_button_1, 0, 1, 1, 1)
self.right_playconsole_layout.addWidget(self.console_button_2, 0, 3, 1, 1)
self.right_playconsole_layout.addWidget(self.console_button_3, 0, 2, 1, 1)
self.right_playconsole_layout.addWidget(self.console_button_4, 0, 5, 1, 1)
self.right_playconsole_layout.addWidget(self.console_button_5, 0, 0, 1, 1)
self.right_playconsole_layout.addWidget(self.console_slide, 0, 4, 1, 1)
# self.right_playconsole_layout.setAlignment(QtCore.Qt.AlignCenter) # 设置布局内部件居中显示
self.right_process_layout.addWidget(self.right_process_bar, 0, 0, 1, 7)
self.right_process_layout.addWidget(self.process_label, 0, 8, 1, 1)
self.right_layout.addWidget(self.right_process_widget, 9, 0, 1, 9)
self.right_layout.addWidget(self.right_songlist_widget, 0, 0, 9, 9)
self.right_layout.addWidget(self.right_playconsole_widget, 10, 0, 1, 9)
为了让界面更好看一点,所以下面还有QSS样式代码
上侧控件QSS
self.top_widget.setStyleSheet('''
QWidget#top_widget{
background:#EC4141;
border-top:1px solid white;
border-left:1px solid white;
border-right:1px solid white;
border-top-left-radius:10px;
border-top-right-radius:10px;
}
QPushButton{
background:#EC4141;
border-radius:5px;
color:white;
}
QLabel{
border:none;
color:white;
font-size:24px;
font-family:STHUPO;
}
''')
左侧控件QSS
self.left_widget.setStyleSheet('''
QWidget#left_widget{
background:gray;
border-bottom:1px solid white;
border-left:1px solid white;
border-bottom-left-radius:10px;
}
QPushButton{background:gray;border-radius:5px;border:none;color:white;}
QPushButton:hover{background:red}
QLabel{
border:none;
color:white;
font-size:18px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
QListWidget{border:none; border-radius:5px;color:white;font-size:18px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
background:gray;
}
QListWidget::Item{padding-top:20px; padding-bottom:4px; }
QListWidget::Item:hover{background:lightgray; }
QListWidget::item:selected{background:lightgray; color:white; }
QListWidget::item:selected:!active{border-width:0px; background:lightgray; }
''')
右侧控件
self.right_widget.setStyleSheet('''
QWidget#right_widget{
color:#232C51;
background:white;
border-bottom:1px solid darkGray;
border-right:1px solid darkGray;
border-bottom-right-radius:10px;
}
QLabel#right_lable{
border:none;
font-size:16px;
font-weight:700;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
QPushButton{
border:none;
color:gray;
font-size:14px;
height:40px;
}
QPushButton:hover{
color:black;
border:none;
border-radius:10px;
background:LightGray;
}
QTableWidget{
color:black;
background:white;
selection-background-color:lightgray;
border:none;
}
QSlider::add-page:horizontal{
background-color:lightgray;
height:4px;
}
QSlider::sub-page:horizontal{
background-color:red;
height:4px;
}
QSlider::groove:horizontal{
background:transparent;
height:6px;
}
QSlider::handle:horizontal{
height: 13px;
width: 13px;
border-image:url(ICON/dot.png);
margin: -4 -0px;
}
''')
Qt自带滑动条只能点击圆钮拖动
所以给滑动条增加点哪,圆钮到哪的功能
class progressSlider(QtWidgets.QSlider):
clicked = QtCore.pyqtSignal(int, int)
def __init__(self, orientation, parent=None):
super(progressSlider, self).__init__(orientation, parent)
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
super().mousePressEvent(event) # 调用父级的单击事件,听说这样能不影响进度条原来的拖动
val_por = event.pos().x() / self.width() # 获取鼠标在进度条的相对位置
now = self.value()
self.setValue(int(val_por * self.maximum())) # 改变进度条的值
self.clicked.emit(self.value(), self.value()-now)
创建歌单界面
class AddListUI(QtWidgets.QWidget):
_signal = QtCore.pyqtSignal(str)
def __init__(self):
super().__init__()
self.list_name = ''
self.setFixedSize(500, 300)
self.add_list_layout = QtWidgets.QVBoxLayout()
self.setLayout(self.add_list_layout)
self.setWindowFlag(QtCore.Qt.FramelessWindowHint) # 隐藏边框
self.close_btn = QtWidgets.QPushButton(qtawesome.icon('fa.times', color='#000000'), '')
self.create_btn = QtWidgets.QPushButton('创 建')
self.add_list_edit = QtWidgets.QLineEdit()
self.add_list_edit.setPlaceholderText('请输入新歌单标题')
self.add_list_label = QtWidgets.QLabel('新建歌单')
self.close_btn.clicked.connect(self.close)
self.create_btn.clicked.connect(self.add_list)
self.add_list_layout.addWidget(self.close_btn, 0, QtCore.Qt.AlignRight)
self.add_list_layout.addWidget(self.add_list_label, 1, QtCore.Qt.AlignCenter)
self.add_list_layout.addWidget(self.add_list_edit, 2, QtCore.Qt.AlignCenter)
self.add_list_layout.addWidget(self.create_btn, 3, QtCore.Qt.AlignCenter)
self.close_btn.setFixedSize(50,50)
self.create_btn.setFixedSize(70,40)
# self.add_list_label.setFixedHeight(80)
self.add_list_edit.setFixedHeight(40)
self.close_btn.setStyleSheet('''
QPushButton{
background:white;
border-radius:5px;
border:none;
}
''')
self.setStyleSheet('''
QWidget{
background:white;
border:none;
border-radius:10px;
}
QLabel{
border:none;
color:black;
font-size:24px;
font-family:STHUPO;
}
QPushButton{
color:white;
border:none;
border-radius:10px;
background:red;
font-size:22px;
font-family:STHUPO;
}
QLineEdit{
border:1px solid gray;
width:400px;
border-radius:10px;
padding:2px 4px;
}
''')
def add_list(self):
self.list_name = self.add_list_edit.text()
if self.list_name and not os.path.isfile('./list_dic/'+self.list_name+'.list'):
a_dict = {'songlist_meta': []}
f = open('./list_dic/'+self.list_name+'.list', 'w')
a_dict = add_songs(a_dict)
f.write(dumps(a_dict))
self._signal.emit(self.list_name)
f.close()
self.close()
else:
self.close()
功能代码及思路
一些散装函数
获取歌曲信息
如歌曲名,歌曲长度(ts),歌手信息等
def get_media_info(media_name):
media_info = MediaInfo.parse(media_name)
media_data = loads(media_info.to_json())["tracks"]
a = []
if 'title' in media_data[0].keys():
a.append(media_data[0]['title'])
else:
a.append(media_data[0]['file_name'].split('/')[-1])
if 'performer' in media_data[0].keys():
a.append(media_data[0]['performer'])
else:
a.append('无歌手名')
if media_data[0]['duration']:
a.append(media_data[0]['duration'])
a.append(m_s(media_data[0]['duration']))
else:
a.append(0)
a.append('')
a.append(media_name)
return a
将ts转化成00:00的时间格式
def m_s(ts):
s = ts//1000 + 1
m, s = s//60, s % 60
return str(m).rjust(2, '0')+':'+str(s).rjust(2, '0')
添加歌曲文件是出现文件选择框
def add_songs(a_dict):
list_meta=filedialog.askopenfilenames\
(filetypes=[('音频文件', ('*.mp3', '*.ogg'))])
for i in list_meta:
a_dict['songlist_meta'].append(get_media_info(i))
return a_dict
类内函数
打开歌单
由于打开歌单后就可以进行歌曲的播放了,
而播放音乐的一系列操作(比如监听歌曲是否结束,以及进度条),
我目前只会用循环来解决
所以这里选择开一条播放线程来解决这件事
def open_songlist(self, item):
self.list_path = './list_dic/' + str(item.text()) + '.list'
f = open(self.list_path, 'r')
self.songlist = loads(f.read())
self.songlist_num = len(self.songlist["songlist_meta"])
self.songlist_table.setRowCount(self.songlist_num)
flag = 0
for i in self.songlist["songlist_meta"]:
self.songlist_table.setItem(flag, 0, QtWidgets.QTableWidgetItem(i[0]))
self.songlist_table.setItem(flag, 1, QtWidgets.QTableWidgetItem(i[1]))
self.songlist_table.setItem(flag, 2, QtWidgets.QTableWidgetItem(i[3]))
flag += 1
f.close()
if self.thread_play == 0:
self.thread_play = threading.Thread(target=self.play)
self.thread_play.start()
控制播放的循环,即线程内容
其实可以多开几个线程的,但是我懒得
自上而下的每一个外部的if语句功能为:
1、控制Play关闭的(程序关闭的时候)
2、控制下一首歌是什么(随机播放和顺序播放,单曲循环找不到图标,所以就不做了)
3、让程序一开始就载入一首歌
4、控制播放进度条
5、控制歌曲快播放结束时,载入下一首歌
def play(self): # 监视变量NextSong,以达到控制播放的效果
while 1:
if self.stop:
self.Player.stop()
break
if self.NextSong == -1:
if self.PlayMode:
self.NextSong = RandInt(0, self.songlist_num-1,self.NowSong)
else:
self.NextSong = (self.NowSong + 1) % self.songlist_num
continue
if self.NowSong == -1:
self.NowSong = self.NextSong
self.NextSong = -1
self.Player.load(self.songlist["songlist_meta"][self.NowSong][4])
self.Player.play()
self.Player.pause()
if self.Player.get_busy():
self.songlist_label.setText(' 正在播放: '+self.songlist["songlist_meta"][self.NowSong][0])
self.process = self.Player.get_pos() + self.process_add
self.process_label.setText(m_s(self.process)+'/'+self.songlist["songlist_meta"][self.NowSong][3])
self.right_process_bar.setValue(self.process)
sleep(0.9)
if self.process >= self.process_max-3000:
sleep(2)
self.next_song()
函数 下一首歌
本程序所有的切歌操作都由其承担
def next_song(self):
self.PrevSong = self.NowSong
self.NowSong = self.NextSong
self.NextSong = -1
self.Player.load(self.songlist["songlist_meta"][self.NowSong][4])
self.process_add = 0
self.process_max = self.songlist["songlist_meta"][self.NowSong][2]
self.right_process_bar.setMaximum(self.process_max)
self.Player.play()
剩下的函数
def add_songlist(self): # 添加歌单
self.add_list = AddListUI()
self.add_list.show()
self.add_list._signal.connect(self.left_list.addItem)
def add_song(self): #添加音乐
a_dict = add_songs(self.songlist)
f = open(self.list_path, 'w')
f.write(dumps(a_dict))
f.close()
f = open(self.list_path, 'r')
self.songlist = loads(f.read())
self.songlist_num = len(self.songlist["songlist_meta"])
self.songlist_table.setRowCount(self.songlist_num)
flag = 0
for i in self.songlist["songlist_meta"]:
self.songlist_table.setItem(flag, 0, QtWidgets.QTableWidgetItem(i[0]))
self.songlist_table.setItem(flag, 1, QtWidgets.QTableWidgetItem(i[1]))
self.songlist_table.setItem(flag, 2, QtWidgets.QTableWidgetItem(i[3]))
flag += 1
f.close()
def prev_song(self): # 上一首歌
self.NextSong = self.PrevSong
self.next_song()
def play_song(self):
if self.PlayState: # 开始播放
self.PlayState = 0
self.console_button_3.setIcon(qtawesome.icon('fa.pause', color='#F76677', font=18))
self.Player.unpause()
else: # 暂停播放
self.PlayState = 1
self.console_button_3.setIcon(qtawesome.icon('fa.play', color='#F76677', font=18))
self.Player.pause()
def play_volume(self): # 控制是否静音
if self.HaveVolume:
self.HaveVolume = 0
self.console_button_4.setIcon(qtawesome.icon('fa.volume-off', color='#F76677'))
self.console_slide.setValue(0)
self.Player.set_volume(0)
else:
self.HaveVolume = 1
self.console_button_4.setIcon(qtawesome.icon('fa.volume-up', color='#F76677'))
self.console_slide.setValue(self.Volume)
self.Player.set_volume(self.Volume)
def play_mode(self): # 控制随机和循环播放
if self.PlayMode:
self.PlayMode = 0
self.console_button_5.setIcon(qtawesome.icon('fa.retweet', color='#F76677'))
self.NextSong = -1
else:
self.PlayMode = 1
self.console_button_5.setIcon(qtawesome.icon('fa.random', color='#F76677'))
self.NextSong = -1
def now_play(self, item): # 双击歌单内歌曲即播放该歌曲
self.PlayState = 1
self.console_button_3.setIcon(qtawesome.icon('fa.pause', color='#F76677', font=18))
self.NextSong = item.row()
self.next_song()
def click_process_slider(self, a, b): # 播放进度条控制
self.process_add += b
self.Player.set_pos(a/1000)
# self.right_process_bar.setValue(a)
def click_console_slider(self, a, b): # 音量控制
self.Player.set_volume(a/100)
结尾
这个播放器功能实现还不够完善,且有些许操作bug还未解决,以后会添加别的功能(在以后的文章里)