DreamLife_MusicPlayer/musicplayer.py

230 lines
9.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
import os
import pygame
import time
import re
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QLabel,
QPushButton, QSlider, QHBoxLayout
)
from PyQt6.QtCore import QTimer, Qt, QSize, QPropertyAnimation, QPoint
from PyQt6.QtGui import QPixmap, QIcon, QPalette, QColor
class MusicPlayer(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("DreamLife|MusicPlayer")
self.setGeometry(300, 200, 400, 500)
self.initUI()
pygame.mixer.init()
self.lyrics = [] # 解析后的歌词列表 [(时间戳, 歌词)]
self.current_lyric_index = 0 # 当前歌词索引
self.is_playing = False # 播放状态
self.music_length = 0 # 音乐总时长(秒)
self.start_time = 0 # 播放开始时间(用于计算已播放时间)
self.pause_time = 0 # 暂停时记录的播放进度(秒)
self.animation = None # 存储滚动动画对象
self.last_lyric = None # 上一次显示的歌词,用于避免重复启动动画
self.load_lyrics()
def initUI(self):
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.layout = QVBoxLayout()
self.layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.central_widget.setLayout(self.layout)
# 设置背景颜色
self.set_background_color("#2E2E2E")
# 封面
self.cover_label = QLabel()
self.layout.addWidget(self.cover_label, alignment=Qt.AlignmentFlag.AlignCenter)
self.load_cover()
# 歌词显示区域(固定大小容器,便于动画处理,不随内容改变大小)
self.lyrics_container = QWidget()
self.lyrics_container.setFixedSize(300, 50)
self.lyrics_container.setStyleSheet("background-color: transparent;")
# 歌词标签放置在容器中
self.lyrics_browser = QLabel("", self.lyrics_container)
self.lyrics_browser.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.lyrics_browser.setStyleSheet("font-size: 24px; color: white; font-weight: bold;")
self.lyrics_browser.setFixedHeight(50)
self.lyrics_browser.move(0, 0)
self.layout.addWidget(self.lyrics_container, alignment=Qt.AlignmentFlag.AlignCenter)
# 进度条
self.slider = QSlider(Qt.Orientation.Horizontal)
self.slider.setRange(0, 100)
self.slider.sliderPressed.connect(self.pause_music)
self.slider.sliderReleased.connect(self.seek_music)
self.layout.addWidget(self.slider)
# 播放控制区域
self.control_layout = QHBoxLayout()
self.control_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.play_button = QPushButton()
self.play_button.setIcon(QIcon(self.resource_path("file/play.png")))
self.play_button.setIconSize(QSize(52, 52)) # 明确设置图标尺寸
self.play_button.setStyleSheet("border: none;")
self.play_button.clicked.connect(self.toggle_play_pause)
self.control_layout.addWidget(self.play_button)
self.layout.addLayout(self.control_layout)
# 定时器每500毫秒更新一次进度和歌词
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_lyrics_and_progress)
def resource_path(self, relative_path):
""" 获取资源文件的路径,适配 PyInstaller 打包模式 """
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS # PyInstaller 运行时解压目录
else:
base_path = os.path.dirname(os.path.abspath(__file__)) # 开发模式
return os.path.join(base_path, relative_path)
def set_background_color(self, color):
palette = self.palette()
palette.setColor(QPalette.ColorRole.Window, QColor(color))
self.setPalette(palette)
self.central_widget.setStyleSheet(f"background-color: {color};")
def load_cover(self):
cover_path = self.resource_path("file/cover.jpg")
if os.path.exists(cover_path):
pixmap = QPixmap(cover_path)
self.cover_label.setPixmap(pixmap.scaled(256, 256, Qt.AspectRatioMode.KeepAspectRatio))
else:
self.cover_label.setText("封面未找到")
def load_lyrics(self):
lyrics_path = self.resource_path("file/林俊杰-光阴副本.lrc")
if os.path.exists(lyrics_path):
try:
with open(lyrics_path, 'r', encoding='utf-8') as file:
lines = file.readlines()
self.lyrics = []
for line in lines:
match = re.match(r"\[(\d+):(\d+\.\d+)](.*)", line)
if match:
minutes = int(match.group(1))
seconds = float(match.group(2))
timestamp = minutes * 60 + seconds
lyrics_text = match.group(3).strip()
self.lyrics.append((timestamp, lyrics_text))
self.lyrics.sort()
except Exception as e:
self.lyrics_browser.setText("加载歌词出错: " + str(e))
else:
self.lyrics_browser.setText("歌词文件未找到")
def toggle_play_pause(self):
if not self.is_playing:
if self.pause_time > 0:
self.resume_music()
else:
self.play_music()
else:
self.pause_music()
def play_music(self):
music_path = self.resource_path("file/林俊杰-光阴副本.wav")
if os.path.exists(music_path):
try:
pygame.mixer.music.load(music_path)
pygame.mixer.music.play(start=0)
self.music_length = pygame.mixer.Sound(music_path).get_length()
self.start_time = time.time()
self.pause_time = 0
self.timer.start(500)
self.play_button.setIcon(QIcon(self.resource_path("file/pause.png")))
self.is_playing = True
except Exception as e:
pass
def resume_music(self):
try:
pygame.mixer.music.unpause() # 直接恢复播放
self.start_time = time.time() - self.pause_time
self.timer.start(500)
self.play_button.setIcon(QIcon(self.resource_path("file/pause.png")))
self.is_playing = True
except Exception as e:
pass
def pause_music(self):
if self.is_playing:
self.pause_time = time.time() - self.start_time
pygame.mixer.music.pause()
self.timer.stop()
self.play_button.setIcon(QIcon(self.resource_path("file/play.png")))
self.is_playing = False
def seek_music(self):
if self.music_length > 0:
new_time = self.slider.value() / 100 * self.music_length
pygame.mixer.music.play(start=new_time)
self.start_time = time.time() - new_time
self.pause_time = new_time
self.current_lyric_index = self.find_lyric_index(new_time)
self.update_lyrics_display()
self.timer.start(500)
self.play_button.setIcon(QIcon(self.resource_path("file/pause.png")))
self.is_playing = True
def find_lyric_index(self, current_time):
for i, (timestamp, _) in enumerate(self.lyrics):
if current_time < timestamp:
return max(i - 1, 0)
return len(self.lyrics) - 1
def update_lyrics_display(self):
if self.current_lyric_index < len(self.lyrics):
_, lyric = self.lyrics[self.current_lyric_index]
# 仅当歌词发生变化时再更新(避免重复重启动画)
if self.last_lyric != lyric:
self.last_lyric = lyric
# 如果歌词宽度超过容器,则滚动显示
if self.lyrics_browser.fontMetrics().horizontalAdvance(lyric) > self.lyrics_container.width():
self.scroll_lyrics(lyric)
else:
if self.animation is not None:
self.animation.stop()
self.animation = None
self.lyrics_browser.setText(lyric)
self.lyrics_browser.adjustSize()
self.lyrics_browser.move((self.lyrics_container.width() - self.lyrics_browser.width()) // 2, 0)
def scroll_lyrics(self, lyric):
self.lyrics_browser.setText(lyric)
self.lyrics_browser.adjustSize()
label_width = self.lyrics_browser.width()
container_width = self.lyrics_container.width()
start_pos = QPoint(50, 0)
end_pos = QPoint(-label_width, 0)
if self.animation is not None:
self.animation.stop()
self.animation = QPropertyAnimation(self.lyrics_browser, b"pos")
# 根据歌词长度调整滚动时间
duration = 8000 + (label_width - container_width) * 20
self.animation.setDuration(duration)
self.animation.setStartValue(start_pos)
self.animation.setEndValue(end_pos)
self.animation.start()
def update_lyrics_and_progress(self):
elapsed_time = time.time() - self.start_time
self.current_lyric_index = self.find_lyric_index(elapsed_time)
self.update_lyrics_display()
if self.music_length > 0:
progress = int((elapsed_time / self.music_length) * 100)
self.slider.setValue(progress)
if __name__ == '__main__':
app = QApplication(sys.argv)
player = MusicPlayer()
player.show()
sys.exit(app.exec())