5 Star 53 Fork 10

Nagisa / LrcMusicPlayer

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
main.py 47.85 KB
一键复制 编辑 原始数据 按行查看 历史
Nagisa 提交于 2023-12-04 11:52 . 优化播放结束后的切换方法。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114
import json
import os
import random
import sys
import webbrowser
import requests
import cgitb
from PySide6 import QtWidgets
from PySide6.QtCore import QCoreApplication, Qt, QUrl, QTimer, QPoint
from PySide6.QtGui import QPixmap, QFontDatabase
from PySide6 import QtCore
from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput
from PySide6.QtWidgets import QListWidgetItem
from pypinyin import lazy_pinyin, Style
from OnlineInfo import online_info
from mainwindow import Ui_MainWindow
from song import Song
from sqlite_lib import UsingSqlite
NOW_VERSION = 'v1.6.1'
class PlayerWindow(QtWidgets.QMainWindow):
"""主窗体类"""
def __init__(self):
# 继承父类
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# 载入字体
font_path = ':/fonts/fonts/LXGWWenKai-Regular.ttf'
fid = QFontDatabase.addApplicationFont(font_path)
families = QFontDatabase.applicationFontFamilies(fid)
print(f'载入字体:{families}')
# 窗口相关
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowSystemMenuHint |
Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint) # 设置为无标题栏
self.setAttribute(Qt.WA_TranslucentBackground) # 设置窗口背景透明
self.ui.pushButton_update.setText(NOW_VERSION) # 设定版本号
# 控件阴影
self.effect_shadow = QtWidgets.QGraphicsDropShadowEffect(self) # 阴影效果
self.effect_shadow.setOffset(0, 0) # 偏移
self.effect_shadow.setBlurRadius(10) # 阴影半径
self.effect_shadow.setColor(Qt.black) # 阴影颜色
self.ui.widget.setGraphicsEffect(self.effect_shadow) # 将设置套用到widget窗口
self.effect_shadow = QtWidgets.QGraphicsDropShadowEffect(self) # 阴影效果
self.effect_shadow.setOffset(0, 0) # 偏移
self.effect_shadow.setBlurRadius(10) # 阴影半径
self.effect_shadow.setColor(Qt.black) # 阴影颜色
self.ui.widget_pic.setGraphicsEffect(self.effect_shadow) # 将设置套用到label_pic
# 事件过滤器
self.installEventFilter(self)
# 鼠标响应
self.setMouseTracking(True)
self.ui.centralwidget.setMouseTracking(True)
self.ui.widget.setMouseTracking(True)
self.ui.widget_bottom.setMouseTracking(True)
# 窗口变量初始化
self.move_drag = False
self.move_DragPosition = 0
self.right_bottom_corner_drag = False
self.left_bottom_corner_drag = False
self.left_drag = False
self.right_drag = False
self.bottom_drag = False
self.left_rect = []
self.right_rect = []
self.bottom_rect = []
self.right_bottom_corner_rect = []
self.left_bottom_corner_rect = []
# 按键绑定
self.ui.pushButton_start.clicked.connect(self.song_start_switch)
self.ui.pushButton_path.clicked.connect(self.get_songs_from_directory)
self.ui.pushButton_is_comment.clicked.connect(self.change_comment_mode)
self.ui.listWidget.doubleClicked.connect(self.song_double_clicked)
self.ui.listWidget_2.doubleClicked.connect(self.lrc_double_clicked)
self.ui.listWidget_3.doubleClicked.connect(self.info_double_clicked)
self.ui.horizontalSlider.sliderReleased.connect(self.slider_release)
self.ui.horizontalSlider.sliderPressed.connect(self.slider_press)
self.ui.horizontalSlider.sliderMoved.connect(self.slider_move)
self.ui.pushButton_red.clicked.connect(self.close_window)
self.ui.pushButton_green.clicked.connect(self.showMinimized)
self.ui.pushButton_yellow.clicked.connect(self.maximize_window)
self.ui.pushButton_next.clicked.connect(self.play_next)
self.ui.pushButton_last.clicked.connect(self.play_last)
self.ui.pushButton_song_find.clicked.connect(self.song_find)
self.ui.pushButton_save_to_playlist.clicked.connect(self.save_playlist)
self.ui.pushButton_random.clicked.connect(self.change_play_mode)
self.ui.pushButton_is_online.clicked.connect(self.change_lrc_mode)
self.ui.lineEdit.textChanged.connect(self.search_song)
self.ui.pushButton_is_trans.clicked.connect(self.change_trans_mode)
self.ui.pushButton_update.clicked.connect(self.get_update)
# self.ui.pushButton_update.clicked.connect(self.test)
self.ui.pushButton_volume.clicked.connect(self.change_volume)
self.ui.pushButton_change_sort_mode.clicked.connect(self.change_sort_mode)
# 歌词刷新计时器
self.lrc_timer = QTimer(self)
self.lrc_timer.timeout.connect(self.song_timer)
# 歌曲淡入淡出计时器
self.volume_smooth_timer = QTimer(self)
self.volume_smooth_timer.timeout.connect(self.volume_smooth_timeout)
self.volume_add_buffer = 0
self.volume_buffer = 0
self.volume_smooth_low_mode = True
# 初始化
self.player = QMediaPlayer()
self.audio_output = QAudioOutput()
self.player.setAudioOutput(self.audio_output)
self.player.mediaStatusChanged.connect(self.media_status_changed)
self.song_selected = Song(None) # 当前音乐对象
self.song_path_list = [] # 歌曲路径列表
self.song_path_playlist = [] # 歌曲路径播放列表
self.local_path_list = [] # 本地歌曲路径列表
self.local_songs_count = 0 # 歌曲计数
self.directory_path = '' # 歌曲文件夹路径
self.song_now_path = '' # 当前歌曲路径
self.is_started = False # 播放按钮状态
self.is_sliderPress = False # 进度条按压状态
self.lrc_trans_mode = 1 # 歌词翻译模式
self.lrc_time_index = 0 # 歌词时间戳标记
self.song_index = 0 # 歌曲标记
self.lrc_time_list = [] # 歌词时间戳列表
self.play_mode = 0 # 播放模式
self.lrc_mode = 0 # 歌词模式
self.comment_mode = 0 # 评论模式
self.is_got_comment = False # 评论获取状态
self.is_window_maximized = False # 窗口最大化状态
self.volume_change_mode = 0 # 点击的音量档位
self.sort_mode = 0 # 排序方式
self.ui.label_pic_hires.setVisible(False) # 是否显示小金标
self.ui.lineEdit.setClearButtonEnabled(True) # 显示一个清空按钮
self.ui.widget_comment.setVisible(False) # 关闭评论列表
self.audio_output.setVolume(90) # 默认音量
self.volume_style_refresh() # 刷新音量显示
# 自动换行
# self.ui.listWidget.setWordWrap(True)
self.ui.listWidget_2.setWordWrap(True)
self.ui.listWidget_3.setWordWrap(True)
self.ui.listWidget_comment.setWordWrap(True)
self.ui.label_name.setWordWrap(True)
# 列表滚动条步长
self.ui.listWidget.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollMode.ScrollPerItem)
# self.ui.listWidget.verticalScrollBar().setSingleStep(15)
self.ui.listWidget_2.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollMode.ScrollPerItem)
# self.ui.listWidget_2.verticalScrollBar().setSingleStep(15)
self.ui.listWidget_comment.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollMode.ScrollPerItem)
# self.ui.listWidget_comment.verticalScrollBar().setSingleStep(10)
# 气泡提示
self.ui.pushButton_red.setToolTip('关闭')
self.ui.pushButton_yellow.setToolTip('最大化')
self.ui.pushButton_green.setToolTip('最小化')
self.ui.pushButton_next.setToolTip('下一首')
self.ui.pushButton_last.setToolTip('上一首')
self.ui.pushButton_start.setToolTip('播放')
self.ui.pushButton_path.setToolTip('选择路径')
self.ui.pushButton_song_find.setToolTip('回到当前播放位置')
self.ui.pushButton_save_to_playlist.setToolTip('保存当前列表至播放列表')
self.ui.pushButton_is_comment.setToolTip('切换评论显示')
self.ui.pushButton_is_trans.setToolTip('显示翻译')
self.ui.pushButton_volume.setToolTip('调整音量')
# 创建线程
self.thread_load_songs = Thread()
self.thread_load_songs.signal_item.connect(self.thread_search_num)
self.thread_load_songs.signal_stop.connect(self.thread_search_stop)
# 文件路径
self.setting_path = 'setting.json'
self.db_path = UsingSqlite.DB_PATH
if os.path.exists(self.db_path):
# 载入配置文件
self.load_setting()
def close_window(self):
"""关闭按钮"""
# 自动保存配置
self.save_setting()
QCoreApplication.quit()
def maximize_window(self):
"""窗口最大化切换"""
if self.is_window_maximized:
self.showNormal()
self.is_window_maximized = False
else:
self.showMaximized()
self.is_window_maximized = True
def load_setting(self):
"""载入配置文件"""
if not os.path.exists(self.setting_path):
return
with open('setting.json') as f:
config = json.load(f)
# 载入播放路径,检测数目
self.directory_path = config.get('directory_path')
if self.directory_path:
self.update_songs()
# 载入歌词模式
self.lrc_mode = config.get('lrc_mode', 0)
self.set_lrc_mode_stylesheet()
# 载入翻译模式
self.lrc_trans_mode = config.get('lrc_trans_mode', 1)
self.set_trans_mode_stylesheet()
# 载入音量
self.set_volume_int(config.get('player_volume', 90))
self.volume_style_refresh()
# 载入排序方式
self.sort_mode = config.get('sort_mode', 0)
self.set_sort_mode_stylesheet()
# 载入数据库数据
if os.path.exists(self.db_path):
self.select_songs('')
self.song_path_playlist = self.song_path_list.copy()
# 载入播放模式
self.play_mode = config.get('play_mode', 0)
self.set_play_mode() # 播放模式设置需置于播放列表创建后
self.set_play_mode_stylesheet()
def save_setting(self):
"""保存配置文件"""
config = {
"directory_path": self.directory_path,
"play_mode": self.play_mode,
"lrc_mode": self.lrc_mode,
"lrc_trans_mode": self.lrc_trans_mode,
"player_volume": self.get_volume_int(),
"sort_mode": self.sort_mode
}
with open(self.setting_path, 'w') as f:
json.dump(config, f)
def slider_move(self):
"""移动滑条,刷新标签"""
self.ui.label_time_start.setText(self.ms_to_str(self.ui.horizontalSlider.value()))
def slider_release(self):
"""释放滑条,调整进度"""
self.player.setPosition(self.ui.horizontalSlider.value())
self.lrc_time_index = 0
self.is_sliderPress = False
def slider_press(self):
"""按下滑条"""
self.is_sliderPress = True
def lrc_double_clicked(self):
"""双击歌词,跳转对应时间"""
# 已获取歌词时间戳
if len(self.lrc_time_list) != 0:
print(f'跳转至{self.lrc_time_list[self.ui.listWidget_2.currentRow()]}')
self.player.setPosition(self.lrc_time_list[self.ui.listWidget_2.currentRow()])
self.lrc_time_index = 0
@staticmethod
def ms_to_str(ms):
"""将毫秒时间转换为时间标签"""
s, ms = divmod(ms, 1000)
m, s = divmod(s, 60)
# h, m = divmod(m, 60)
return f'{str(m).zfill(2)}:{str(s).zfill(2)}'
def song_timer(self):
"""定时器,500ms"""
# 时间标签格式化
if not self.is_sliderPress:
self.ui.label_time_start.setText(self.ms_to_str(self.player.position()))
self.ui.label_time_end.setText(self.ms_to_str(self.player.duration()))
# 歌词时间戳定位
if len(self.lrc_time_list) != 0:
while True:
# 保证列表不超限
if self.lrc_time_index > len(self.lrc_time_list) - 1:
break
# 匹配歌词时间
elif self.lrc_time_list[self.lrc_time_index] < self.player.position():
self.lrc_time_index += 1
else:
break
# 移动到对应位置并选中
# self.ui.listWidget_2.verticalScrollBar().setSliderPosition(self.lrc_time_index - 1)
self.ui.listWidget_2.setCurrentRow(self.lrc_time_index - 1)
item = self.ui.listWidget_2.item(self.lrc_time_index - 1)
self.ui.listWidget_2.scrollToItem(item, QtWidgets.QAbstractItemView.ScrollHint.PositionAtCenter)
# 设定滑条限度
self.ui.horizontalSlider.setMaximum(self.player.duration())
self.ui.horizontalSlider.setMinimum(0)
# 释放滑条,调整进度
if not self.is_sliderPress:
self.ui.horizontalSlider.setValue(self.player.position())
# # 歌曲播放完毕
# if self.player.position() >= self.player.duration():
# print('Time over')
# self.play_next()
def media_status_changed(self, status):
"""媒体状态转移"""
# 播放结束,切换至下一个音频
if status == QMediaPlayer.MediaStatus.EndOfMedia:
print('Time over')
self.play_next()
def set_volume_int(self, volume):
"""从0-100设置0-1的音量"""
self.audio_output.setVolume(float(volume) / 100)
def get_volume_int(self):
"""从0-1获取0-100的音量"""
return round(self.audio_output.volume() * 100)
def volume_smooth_timeout(self):
"""音量淡入淡出"""
volume = self.volume_buffer # 获取音量真值
if volume == 0: # 排除静音状态
if self.volume_smooth_low_mode:
self.song_pause()
self.volume_smooth_timer.stop()
else:
volume_step = volume / 500 # 1ms中断一次,共500ms,即进入500次
self.volume_add_buffer += volume_step
if self.volume_add_buffer > 1: # 缓存浮点数,传递整数
self.volume_add_buffer -= 1
if self.volume_smooth_low_mode: # 暂停
if self.get_volume_int() - 1 >= 0:
self.set_volume_int(self.get_volume_int() - 1)
else:
self.song_pause()
self.volume_smooth_timer.stop()
self.set_volume_int(volume)
else: # 播放
if self.get_volume_int() + 1 <= volume:
self.set_volume_int(self.get_volume_int() + 1)
else:
self.volume_smooth_timer.stop()
def volume_add(self, step=5):
"""播放器音量加"""
volume = self.get_volume_int()
if volume + step > 100:
volume = 100
else:
volume += step
self.set_volume_int(volume)
print(f'音量调整至:{volume}')
self.volume_style_refresh()
def volume_sub(self, step=5):
"""播放器音量减"""
volume = self.get_volume_int()
if volume - step < 0:
volume = 0
else:
volume -= step
self.set_volume_int(volume)
print(f'音量调整至:{volume}')
self.volume_style_refresh()
def volume_style_refresh(self):
"""刷新音量显示样式"""
volume = self.get_volume_int()
if volume == 100:
self.ui.pushButton_volume.setText('M')
else:
self.ui.pushButton_volume.setText(str(volume))
def change_volume(self):
"""按键切换音量档位"""
volume = self.get_volume_int()
if volume == 0:
volume_out = 100
else:
volume_out = volume - 20
if volume_out < 0:
volume_out = 0
self.set_volume_int(volume_out)
self.volume_style_refresh()
def change_sort_mode(self):
"""切换排序模式"""
self.sort_mode += 1
if self.sort_mode > 2:
self.sort_mode = 0
self.set_sort_mode_stylesheet()
def set_sort_mode_stylesheet(self):
"""设置排序按钮样式"""
if self.sort_mode == 0:
text = '按字母顺序正序排序'
elif self.sort_mode == 1:
text = '按修改时间倒序排序'
else:
text = '按创建时间倒序排序'
self.ui.pushButton_change_sort_mode.setToolTip(text)
# 刷新列表显示
self.select_songs(self.ui.lineEdit.text())
def change_comment_mode(self):
"""切换评论显示"""
if self.song_now_path != '':
self.comment_mode += 1
if self.comment_mode > 1:
self.comment_mode = 0
if self.comment_mode == 1:
self.ui.widget_comment.setVisible(True)
self.ui.widget_info.setVisible(False)
if self.is_got_comment is False:
self.song_get_comment()
else:
self.ui.widget_comment.setVisible(False)
self.ui.widget_info.setVisible(True)
def change_trans_mode(self):
"""
切换翻译模式
0 => 仅主歌词
1 => 主副歌词
2 => 主副歌词及罗马音
"""
if self.song_now_path == '':
return
self.lrc_trans_mode += 1
if self.lrc_trans_mode > 2:
self.lrc_trans_mode = 0
self.song_lrc_init()
self.set_trans_mode_stylesheet()
def set_trans_mode_stylesheet(self):
"""翻译图标样式"""
if self.lrc_trans_mode == 0:
self.ui.pushButton_is_trans.setToolTip('关闭翻译')
if self.lrc_trans_mode == 1:
self.ui.pushButton_is_trans.setToolTip('显示翻译')
if self.lrc_trans_mode == 2:
self.ui.pushButton_is_trans.setToolTip('显示翻译及罗马音')
def change_lrc_mode(self):
"""切换歌词模式"""
self.lrc_mode += 1
if self.lrc_mode > 1:
self.lrc_mode = 0
self.set_lrc_mode_stylesheet()
def set_lrc_mode_stylesheet(self):
"""歌词图标样式"""
if self.lrc_mode == 0:
self.ui.pushButton_is_online.setToolTip('内嵌歌词')
self.ui.pushButton_is_online.setStyleSheet("QPushButton{\n"
"image: url(:/icon/images/cloud_off.png);\n}\n"
"QPushButton:hover{\n"
"image: url(:/icon/images/cloud_off 深色.png);\n}")
if self.lrc_mode == 1:
self.ui.pushButton_is_online.setToolTip('在线歌词')
self.ui.pushButton_is_online.setStyleSheet("QPushButton{\n"
"image: url(:/icon/images/cloud_download.png);\n}\n"
"QPushButton:hover{\n"
"image: url(:/icon/images/cloud_download 深色.png);\n}")
def change_play_mode(self):
"""
切换播放模式
0 => 顺序播放
1 => 单曲循环
2 => 随机播放
"""
self.play_mode += 1
if self.play_mode > 2:
self.play_mode = 0
self.set_play_mode_stylesheet()
self.set_play_mode()
def set_play_mode(self):
"""设置播放模式"""
if self.play_mode == 0: # 顺序
self.song_path_playlist.sort(key=lambda x: x['index'])
# 匹配新播放列表的索引值
if self.song_now_path != '':
for item in self.song_path_playlist:
if item['path'] == self.song_now_path:
self.song_index = self.song_path_playlist.index(item)
elif self.play_mode == 1: # 单曲
pass
elif self.play_mode == 2: # 随机
random.shuffle(self.song_path_playlist)
def set_play_mode_stylesheet(self):
"""播放图标样式"""
if self.play_mode == 0:
self.ui.pushButton_random.setToolTip('列表循环')
self.ui.pushButton_random.setStyleSheet("QPushButton{\n"
"image: url(:/icon/images/列表循环.png);\n}\n"
"QPushButton:hover{\n"
"image: url(:/icon/images/列表循环 深色.png);\n}")
if self.play_mode == 1:
self.ui.pushButton_random.setToolTip('单曲循环')
self.ui.pushButton_random.setStyleSheet("QPushButton{\n"
"image: url(:/icon/images/单曲循环.png);\n}\n"
"QPushButton:hover{\n"
"image: url(:/icon/images/单曲循环 深色.png);\n}")
if self.play_mode == 2:
self.ui.pushButton_random.setToolTip('随机播放')
self.ui.pushButton_random.setStyleSheet("QPushButton{\n"
"image: url(:/icon/images/随机播放.png);\n}\n"
"QPushButton:hover{\n"
"image: url(:/icon/images/随机播放 深色.png);\n}")
def play_init(self):
"""播放初始化"""
self.player.setSource(QUrl(self.song_now_path))
# self.set_volume_int(100)
self.song_data_init()
self.song_play()
def play_next(self):
"""下一首"""
print('下一首')
if len(self.song_path_playlist) == 0:
return
elif self.play_mode != 1 and self.song_now_path != '': # 非单曲循环,已载入歌曲
self.song_index += 1
if self.song_index > len(self.song_path_playlist) - 1:
self.song_index = 0
self.song_now_path = self.song_path_playlist[self.song_index]['path'] # 载入新的歌曲路径
self.play_init()
self.song_find()
def play_last(self):
"""上一首"""
print('上一首')
if len(self.song_path_playlist) == 0:
return
elif self.play_mode != 1 and self.song_now_path != '': # 非单曲循环,已载入歌曲
self.song_index -= 1
if self.song_index < 0 or (self.song_index > len(self.song_path_playlist) - 1):
self.song_index = len(self.song_path_playlist) - 1
self.song_now_path = self.song_path_playlist[self.song_index]['path']
self.play_init()
self.song_find()
def song_play(self):
"""音乐播放"""
self.player.play()
self.lrc_timer.start(500)
self.is_started = True
print('播放')
# 注入样式表-播放(图标为暂停)
self.ui.pushButton_start.setToolTip('暂停')
self.ui.pushButton_start.setStyleSheet("QPushButton{\n"
"image: url(:/icon/images/播放中.png);\n}\n"
"QPushButton:hover{\n"
"image: url(:/icon/images/播放中 深色.png);\n}")
def song_pause(self):
"""音乐暂停"""
self.player.pause()
self.lrc_timer.stop()
self.is_started = False
print('暂停')
# 注入样式表-暂停(图标为播放)
self.ui.pushButton_start.setToolTip('播放')
self.ui.pushButton_start.setStyleSheet("QPushButton{\n"
"image: url(:/icon/images/暂停.png);\n}\n"
"QPushButton:hover{\n"
"image: url(:/icon/images/暂停 深色.png);\n}")
def song_start_switch(self):
"""播放键,播放切换"""
if self.song_now_path == '' and self.local_songs_count != 0:
self.play_next()
return
elif self.song_now_path == '':
return
self.volume_buffer = self.get_volume_int()
self.volume_add_buffer = 0
if self.is_started is True:
# self.song_pause()
self.volume_smooth_low_mode = True
self.volume_smooth_timer.start(1)
else:
self.volume_smooth_low_mode = False
self.set_volume_int(0)
self.song_play()
self.volume_smooth_timer.start(1)
def song_data_init(self):
"""歌曲初始化,导入歌词和时间戳"""
# 获取歌曲信息
self.song_selected = Song(self.song_now_path)
# 在线获取歌词(若启用)
if self.lrc_mode == 1:
item_name = self.song_selected.title + '-' + self.song_selected.artist
music_id = online_info.get_music_info_163(item_name)['id']
get_lrc_online = online_info.get_lyric_163(music_id)
if get_lrc_online != '':
self.song_selected.lyrics = get_lrc_online
# 在线获取评论(若启用)
self.is_got_comment = False # 设置评论flag关闭
self.ui.listWidget_comment.clear()
if self.comment_mode == 1:
self.song_get_comment()
# 装填歌词
self.song_lrc_init()
# 歌曲封面嵌入
# self.ui.label_pic.clear()
pix = QPixmap()
pix.load(':/images/images/Nagisa.png') # 先载入默认图像
self.ui.label_pic.setPixmap(pix)
pix.loadFromData(self.song_selected.cover) # 后载入封面,防止封面为空
self.ui.label_pic.setPixmap(pix)
self.ui.label_pic.setScaledContents(True)
# 歌曲信息导入
self.ui.listWidget_3.clear()
self.ui.label_name.setText(self.song_selected.title)
self.setWindowTitle(self.song_selected.title + ' - ' + self.song_selected.artist + ' - LrcMusicPlayer')
self.ui.listWidget_3.addItem(f'标题:{self.song_selected.title}')
self.ui.listWidget_3.addItem(f'艺术家:{self.song_selected.artist}')
self.ui.listWidget_3.addItem(f'专辑:{self.song_selected.album}')
self.ui.listWidget_3.addItem(f'日期:{self.song_selected.date}')
self.ui.listWidget_3.addItem(f'风格:{self.song_selected.genre}')
# Hi-Res小金标显示
self.ui.label_pic_hires.setVisible(self.song_selected.is_hr)
# 音乐信息(隐藏MP3的位深信息)
print(self.song_selected)
if self.song_selected.bits_per_sample == 0:
self.ui.label_info.setText(
f'{round(self.song_selected.sample_rate / 1000, 1)} kHz / '
f'{self.song_selected.audio_type}'
)
else:
self.ui.label_info.setText(
f'{round(self.song_selected.sample_rate / 1000, 1)} kHz / '
f'{self.song_selected.bits_per_sample} bits / '
f'{self.song_selected.audio_type}'
)
def song_lrc_init(self):
"""歌词初始化"""
# 重置索引值,清空列表
self.lrc_time_index = 0
self.ui.listWidget_2.clear()
lrc_dict = self.song_selected.get_lrc_dict()
self.lrc_time_list = list(lrc_dict.keys())
# 时间戳不为空,添加歌词
if len(self.lrc_time_list) != 0:
for lrc_time in self.lrc_time_list:
# 添加主歌词
lrc_output = lrc_dict[lrc_time][0]
# 添加罗马音
if self.lrc_trans_mode == 2:
if self.song_selected.contains_japanese(lrc_dict[lrc_time][0]):
lrc_romaji = self.song_selected.get_romaji(lrc_dict[lrc_time][0])
lrc_output += '\n' + lrc_romaji
# 添加副歌词
if self.lrc_trans_mode == 1 or self.lrc_trans_mode == 2:
if len(lrc_dict[lrc_time]) != 1:
for lrc in lrc_dict[lrc_time][1:]:
lrc_output += '\n' + lrc
item = QListWidgetItem(lrc_output)
item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.ui.listWidget_2.addItem(item)
else:
item = QListWidgetItem('纯音乐\n/\n歌词无时间戳')
item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.ui.listWidget_2.addItem(item)
def song_get_comment(self):
"""导入热门评论"""
item_name = self.song_selected.title + '-' + self.song_selected.artist
info = online_info.get_music_info_163(item_name)
music_id = info['id']
music_name = info['name']
music_artist = info['artist']
self.ui.listWidget_comment.clear()
music_com_dict = online_info.get_music_comment_163(music_id)
self.ui.listWidget_comment.addItem(f'评论来自:\nid:{music_id}\n标题:{music_name}\n艺术家:{music_artist}\n')
for item in music_com_dict:
self.ui.listWidget_comment.addItem(item + ':\n' + music_com_dict[item])
self.ui.listWidget_comment.addItem('')
self.is_got_comment = True
def song_double_clicked(self):
"""双击歌曲列表"""
# 获取列表索引值,获取歌曲路径
song_index = self.ui.listWidget.currentRow()
song_dict: dict = self.song_path_list[song_index]
self.song_now_path = song_dict['path']
if self.play_mode == 2: # 随机
# 打乱播放列表
random.shuffle(self.song_path_playlist)
# 将选中歌曲在播放列表中的索引设为当前索引
for item in self.song_path_playlist:
if item['path'] == self.song_now_path:
self.song_index = self.song_path_playlist.index(item)
self.play_init()
def info_double_clicked(self):
"""双击歌曲信息"""
if self.song_now_path:
text = self.ui.listWidget_3.currentItem().text().split(':')[-1]
self.ui.lineEdit.setText(text) # 在搜索框中键入文字
def song_find(self):
"""移动到对应位置,选中"""
# self.ui.listWidget.verticalScrollBar().setSliderPosition(self.song_index)
flag = False
song_index = 0
for item in self.song_path_list:
if item['path'] == self.song_now_path:
song_index = self.song_path_list.index(item)
flag = True
if flag:
self.ui.listWidget.setCurrentRow(song_index)
item = self.ui.listWidget.item(song_index)
self.ui.listWidget.scrollToItem(item, QtWidgets.QAbstractItemView.ScrollHint.PositionAtTop)
def save_playlist(self):
"""保存列表至播放列表"""
self.song_path_playlist = self.song_path_list.copy()
self.set_play_mode()
QtWidgets.QMessageBox.information(
self, "保存成功", '已使用当前列表替换播放列表', QtWidgets.QMessageBox.StandardButton.Ok
)
def get_songs_from_directory(self):
"""从目录获取音乐数据"""
# 暂停音乐
self.song_pause()
# 获取文件夹目录
self.directory_path = QtWidgets.QFileDialog.getExistingDirectory(
self, '选择文件夹', '', QtWidgets.QFileDialog.Option.ShowDirsOnly
)
if not self.directory_path:
print('已取消')
return
# 清理旧数据
self.song_index = 0
self.ui.listWidget.clear()
self.get_songs_count()
if self.local_songs_count == 0:
mess_str = '无匹配文件'
print(mess_str)
QtWidgets.QMessageBox.information(self, "未发现歌曲", mess_str, QtWidgets.QMessageBox.StandardButton.Ok)
else:
mess_str = '发现' + str(self.local_songs_count) + '首歌,点击Ok导入数据。\n这可能会花费一些时间,请稍候片刻。'
print(mess_str)
QtWidgets.QMessageBox.information(self, "发现歌曲", mess_str, QtWidgets.QMessageBox.StandardButton.Ok)
self.ui.horizontalSlider.setMaximum(self.local_songs_count)
self.ui.horizontalSlider.setMinimum(0)
self.thread_load_songs.directory_path = self.directory_path
self.thread_load_songs.start() # 开始线程,将音乐数据存入数据库
def get_songs_count(self):
"""遍历本地音乐总数"""
path_list = []
for root, dirs, items in os.walk(self.directory_path):
for item in items:
file_path = f'{root}/{item}'
suffix = file_path.split('.')[-1].lower()
if suffix not in Song.SONG_FORMAT_LIMIT:
continue
path_list.append(file_path)
self.local_songs_count = len(path_list)
self.local_path_list = path_list
def thread_search_num(self, data):
"""子线程,导入数据的进度反馈"""
print('已导入第' + str(data) + '个,共' + str(self.local_songs_count) + '个')
self.ui.horizontalSlider.setValue(data)
self.ui.label_time_start.setText(str(data))
self.ui.label_time_end.setText(str(self.local_songs_count))
def thread_search_stop(self):
"""子线程,导入数据完成"""
mess_str = '导入数据完成!共' + str(self.local_songs_count) + '首音乐'
QtWidgets.QMessageBox.information(self, "导入数据完成", mess_str, QtWidgets.QMessageBox.StandardButton.Ok)
self.select_songs('')
self.song_path_playlist = self.song_path_list.copy()
self.set_play_mode()
# 保存配置
self.save_setting()
def search_song(self):
"""搜索歌曲"""
if os.path.exists(self.db_path):
self.select_songs(self.ui.lineEdit.text())
def select_songs(self, text):
"""搜索数据库数据"""
with UsingSqlite() as us:
text = '%{}%'.format(text)
sql = """
select * from music_info
where title like ? or artist like ? or album like ? or genre like ? or date like ? or file_name like ?
"""
params = (text, text, text, text, text, text)
result = us.fetch_all(sql, params)
# 音乐无标题,替换为文件名
for item in result:
if item['title'] == '暂无':
item['title'] = item['file_name']
if self.sort_mode == 0:
# 按拼音/字母排序
result = sorted(result, key=lambda x: ''.join(lazy_pinyin(x['title'], style=Style.TONE3)).lower())
elif self.sort_mode == 1:
# 按修改时间排序
result = sorted(result, key=lambda x: x['mtime'], reverse=True)
elif self.sort_mode == 2:
# 按创建时间排序
result = sorted(result, key=lambda x: x['ctime'], reverse=True)
# 导入列表框架,同步创建对应路径列表
self.ui.listWidget.clear()
self.song_path_list = []
count = 0
for item in result:
self.ui.listWidget.addItem(item['title'] + '\n- ' + item['artist'])
self.song_path_list.append({
'index': count,
'path': item['path']
})
count += 1
self.local_songs_count = count
# 刷新label显示
self.ui.label_list_name.setText(f'歌曲列表({count})')
def update_songs(self):
"""更新音乐数据库"""
def list_compare(local_list, db_list):
"""比较列表项不同"""
local_list = set(local_list)
db_list = set(db_list)
_updated_list = local_list - db_list
_removed_list = db_list - local_list
return _updated_list, _removed_list
# 获取本地歌曲数量和歌曲列表
self.get_songs_count()
with UsingSqlite() as us:
sql = 'select path from music_info'
results = us.fetch_all(sql)
sql_list = [] # 数据库中的路径列表
for result in results:
sql_list.append(result['path'])
updated_list, removed_list = list_compare(self.local_path_list, sql_list)
if len(updated_list) == 0 and len(removed_list) == 0:
print('数目一致')
else:
# 更新数据库
for path in updated_list:
song = Song(path)
sql = """
insert into music_info (file_name, path, title, artist, album, date, genre, mtime, ctime)
values (?,?,?,?,?,?,?,?,?)
"""
file_name = song.path.split('/')[-1]
get_mtime = int(os.path.getmtime(song.path))
get_ctime = int(os.path.getctime(song.path))
params = (file_name, song.path, song.title, song.artist, song.album, song.date, song.genre,
get_mtime, get_ctime)
us.cursor.execute(sql, params)
print(f'已插入{path}')
# 清理数据库
for path in removed_list:
sql = """
delete from music_info
where path like ?
"""
params = (path,)
us.cursor.execute(sql, params)
print(f'已清理{path}')
text = f'检测到歌曲数目不一致\n已更新数目:{len(updated_list)}\n已删除数目:{len(removed_list)}'
QtWidgets.QMessageBox.information(self, "歌曲数据库更新完成", text,
QtWidgets.QMessageBox.StandardButton.Ok)
def get_update(self):
"""获取版本更新"""
try:
response = requests.get("https://gitee.com/api/v5/repos/pth2000/LrcMusicPlayer/releases/latest")
except requests.exceptions.ConnectionError:
str_update = '获取失败!请检查网络连接'
print(str_update)
QtWidgets.QMessageBox.critical(self, "错误", str_update, QtWidgets.QMessageBox.StandardButton.Ok)
else:
res = response.json()
latest_version = res['tag_name']
latest_version_name = res['name']
latest_version_time = res['created_at']
latest_version_download_url = res['assets'][0]['browser_download_url']
if NOW_VERSION >= latest_version:
str_update = f'当前版本为最新版\n服务器版本:{latest_version}\n更新时间:{latest_version_time}'
QtWidgets.QMessageBox.information(self, "检查更新", str_update, QtWidgets.QMessageBox.StandardButton.Ok)
else:
str_update = f"""
发现新版本!{NOW_VERSION} --> {latest_version}\n
更新内容:{latest_version_name}\n更新时间:{latest_version_time}\n\n点击 Ok 下载最新版本
"""
message = QtWidgets.QMessageBox.question(
self, "检查更新", str_update,
QtWidgets.QMessageBox.StandardButton.Ok | QtWidgets.QMessageBox.StandardButton.Cancel)
if message == QtWidgets.QMessageBox.StandardButton.Ok:
webbrowser.open(latest_version_download_url, new=0, autoraise=True)
def resizeEvent(self, resize_event):
"""自定义窗口调整大小事件"""
# 重新调整边界范围以备实现鼠标拖放缩放窗口大小,采用三个列表生成式生成三个列表
self.left_rect = [QPoint(x, y) for x in range(0, 5)
for y in range(5, self.height() - 5)]
self.right_rect = [QPoint(x, y) for x in range(self.width() - 5, self.width() + 1)
for y in range(5, self.height() - 5)]
self.bottom_rect = [QPoint(x, y) for x in range(5, self.width() - 5)
for y in range(self.height() - 5, self.height() + 1)]
self.right_bottom_corner_rect = [QPoint(x, y) for x in range(self.width() - 5, self.width() + 1)
for y in range(self.height() - 5, self.height() + 1)]
self.left_bottom_corner_rect = [QPoint(x, y) for x in range(0, 5)
for y in range(self.height() - 5, self.height() + 1)]
def mouseMoveEvent(self, mouse_event):
"""重写函数,实现拖动"""
# 判断鼠标位置切换鼠标手势
if mouse_event.position().toPoint() in self.right_bottom_corner_rect:
self.setCursor(Qt.SizeFDiagCursor)
elif mouse_event.position().toPoint() in self.left_bottom_corner_rect:
self.setCursor(Qt.SizeBDiagCursor)
elif mouse_event.position().toPoint() in self.bottom_rect:
self.setCursor(Qt.SizeVerCursor)
elif mouse_event.position().toPoint() in self.right_rect:
self.setCursor(Qt.SizeHorCursor)
elif mouse_event.position().toPoint() in self.left_rect:
self.setCursor(Qt.SizeHorCursor)
else:
self.setCursor(Qt.ArrowCursor)
# 当鼠标左键点击不放及满足点击区域的要求后,分别实现不同的窗口调整(+5 或 -5 匹配margin)
if Qt.LeftButton and self.right_drag:
# 右侧调整窗口宽度
self.resize(mouse_event.position().toPoint().x() + 5, self.height())
mouse_event.accept()
elif Qt.LeftButton and self.left_drag:
# 左侧调整窗口高度
self.resize(self.width() - mouse_event.position().toPoint().x() + 5, self.height())
self.move(self.x() + mouse_event.position().toPoint().x() - 5, self.y())
mouse_event.accept()
elif Qt.LeftButton and self.bottom_drag:
# 下侧调整窗口高度
self.resize(self.width(), mouse_event.position().toPoint().y() + 5)
mouse_event.accept()
elif Qt.LeftButton and self.right_bottom_corner_drag:
# 右下角同时调整高度和宽度
self.resize(mouse_event.position().toPoint().x() + 5, mouse_event.position().toPoint().y() + 5)
mouse_event.accept()
elif Qt.LeftButton and self.left_bottom_corner_drag:
# 左下角同时调整高度和宽度
self.resize(self.width() - mouse_event.position().toPoint().x() + 5,
mouse_event.position().toPoint().y() + 5)
self.move(self.x() + mouse_event.position().toPoint().x() - 5, self.y())
mouse_event.accept()
elif Qt.LeftButton and self.move_drag:
# 标题栏拖放窗口位置
self.move(mouse_event.globalPosition().toPoint() - self.move_DragPosition)
mouse_event.accept()
def mousePressEvent(self, event):
"""重写鼠标点击的事件"""
if (event.button() == Qt.LeftButton) and (event.position().toPoint().y() < self.ui.widget_top.height() + 5):
# 鼠标左键点击标题栏区域
self.move_drag = True
self.move_DragPosition = event.globalPosition().toPoint() - self.pos()
event.accept()
elif (event.button() == Qt.LeftButton) and (event.position().toPoint() in self.right_bottom_corner_rect):
# 鼠标左键点击右下角边界区域
self.right_bottom_corner_drag = True
event.accept()
elif (event.button() == Qt.LeftButton) and (event.position().toPoint() in self.left_bottom_corner_rect):
# 鼠标左键点击左下角边界区域
self.left_bottom_corner_drag = True
event.accept()
elif (event.button() == Qt.LeftButton) and (event.position().toPoint() in self.left_rect):
# 鼠标左键点击左侧边界区域
self.left_drag = True
event.accept()
elif (event.button() == Qt.LeftButton) and (event.position().toPoint() in self.right_rect):
# 鼠标左键点击右侧边界区域
self.right_drag = True
event.accept()
elif (event.button() == Qt.LeftButton) and (event.position().toPoint() in self.bottom_rect):
# 鼠标左键点击下侧边界区域
self.bottom_drag = True
event.accept()
if event.button() == Qt.LeftButton:
# 左键单击使输入框失焦
self.ui.lineEdit.clearFocus()
def mouseDoubleClickEvent(self, event):
"""重写鼠标双击事件"""
if (event.button() == Qt.LeftButton) and (event.y() < self.ui.widget_top.height() + 5):
# 鼠标左键点击标题栏区域
self.maximize_window()
def mouseReleaseEvent(self, mouse_event):
"""鼠标释放后,各扳机复位"""
self.move_drag = False
self.right_bottom_corner_drag = False
self.bottom_drag = False
self.right_drag = False
self.left_drag = False
self.left_bottom_corner_drag = False
def keyPressEvent(self, event):
"""按键响应事件"""
# print("按下:" + str(event.key()))
if event.key() == Qt.Key_Escape:
self.close_window()
elif (event.key() == Qt.Key_Enter) or (event.key() == Qt.Key_Return):
self.song_start_switch()
elif event.key() == Qt.Key_Space:
self.song_start_switch()
elif event.key() == Qt.Key_Down:
self.volume_sub()
elif event.key() == Qt.Key_Up:
self.volume_add()
elif event.key() == Qt.Key_Right:
self.play_next()
elif event.key() == Qt.Key_Left:
self.play_last()
class Thread(QtCore.QThread):
"""子线程类"""
signal_stop = QtCore.Signal()
signal_item = QtCore.Signal(int)
def __init__(self):
super().__init__()
self.directory_path = ''
def run(self):
"""重写执行方法"""
self.creat_song_table()
def creat_song_table(self):
"""建立数据库,建立表单,存储数据"""
with UsingSqlite() as us:
us.cursor.execute("drop table if exists music_info")
us.cursor.execute("""
create table if not exists music_info (
id integer primary key autoincrement,
file_name text, path text, title text, artist text, album text, date text, genre text,
mtime int, ctime int
)""")
file_num = 0
for root, dirs, items in os.walk(self.directory_path):
for item in items:
file_path = f'{root}/{item}'
file_name = os.path.splitext(item)[0]
suffix = file_path.split('.')[-1].lower()
if suffix not in Song.SONG_FORMAT_LIMIT:
continue
song = Song(file_path)
sql = """
insert into music_info (file_name, path, title, artist, album, date, genre, mtime, ctime)
values (?,?,?,?,?,?,?,?,?)
"""
get_mtime = int(os.path.getmtime(file_path))
get_ctime = int(os.path.getctime(file_path))
params = (file_name, song.path, song.title, song.artist, song.album, song.date, song.genre,
get_mtime, get_ctime)
us.cursor.execute(sql, params)
file_num += 1
self.signal_item.emit(file_num)
self.signal_stop.emit()
if __name__ == '__main__':
cgitb.enable(format='text', logdir='./') # 程序崩溃时输出日志
app = QtWidgets.QApplication(sys.argv) # 声明应用程序
player_window = PlayerWindow() # 声明窗口
player_window.show()
sys.exit(app.exec()) # 当点击窗口的x时,退出程序
1
https://gitee.com/pth2000/LrcMusicPlayer.git
git@gitee.com:pth2000/LrcMusicPlayer.git
pth2000
LrcMusicPlayer
LrcMusicPlayer
master

搜索帮助