5-18更新,全AI写的音乐播放器来了,大家拿去魔改玩吧(能下歌词及封面啦啦啦)。。

查看 88|回复 11
作者:liuyang207   
2025-5-18 更新。
@top777  首先感谢
[color=]top777
的下载歌词及封面的代码,让这个播放Mp3的小软件有了质的飞越。
现在软件启动时会自动下载播放列表中歌曲的歌词及封面并内嵌(如有则跳过),所以,你可能放着放着就发现歌曲有歌词和封面了(第一次启动时因为还没有添加歌曲列表,添加后播放列表中右键手动下载)。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------一直想做一个界面好看点的音乐播放器,今天在GPT-4.1的神功下,小半天的时间总算搞好了。有旋转的黑胶唱片封面、浮动的歌词显示。(程序读取歌曲内嵌的封面和歌词及目录下同名LRC。)
全AI写的,我没有写一句程序,大家可以随意魔改自己想要的功能。
目录里有编译好的EXE文件,也有源码。https://www.alipan.com/s/w2eqoL2fZbw
                                                                       https://pan.baidu.com/s/1hj6i3hBpidwD7NXy2GMh0Q?pwd=uky8    提取码: uky8
----------------------------------------------------------------------------------------------------------------------
[Python] 纯文本查看 复制代码import sys
import os
from PyQt5.QtWidgets import (
    QApplication, QWidget, QLabel, QPushButton, QSlider, QHBoxLayout, QVBoxLayout, QGridLayout, QFileDialog, QListWidget, QListWidgetItem, QMenu
)
from PyQt5.QtCore import Qt, QTimer, QUrl, QByteArray, pyqtSignal
from PyQt5.QtGui import QPixmap, QPainter, QTransform, QPainterPath, QFont, QColor, QLinearGradient, QBrush, QPen, QCursor
from mutagen.mp3 import MP3
from mutagen.id3 import ID3, APIC
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
import vlc
import re
def resource_path(relative_path):
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative_path)
    return os.path.join(os.path.abspath("."), relative_path)
# 旋转封面控件
class RotatingCover(QLabel):
    def __init__(self, song_path, default_cover="fm.png"):
        super().__init__()
        self.angle = 0
        self.pixmap = self.load_cover(song_path, default_cover)
        if self.pixmap.isNull():
            self.setText("未找到封面")
            self.setStyleSheet("color: #fff; background: #666; border-radius: 125px; font-size: 20px;")
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.rotate)
        self.timer.start(50)  # 旋转速度
    def load_cover(self, song_path, default_cover):
        # 1. 尝试同名jpg
        base, _ = os.path.splitext(song_path)
        jpg_path = base + ".jpg"
        if os.path.exists(jpg_path):
            return QPixmap(jpg_path)
        # 2. 尝试MP3内嵌封面
        try:
            audio = MP3(song_path, ID3=ID3)
            for tag in audio.tags.values():
                if isinstance(tag, APIC):
                    ba = QByteArray(tag.data)
                    pixmap = QPixmap()
                    pixmap.loadFromData(ba)
                    if not pixmap.isNull():
                        return pixmap
        except Exception as e:
            pass
        # 3. 默认封面
        if os.path.exists(default_cover):
            return QPixmap(default_cover)
        return QPixmap()  # 空pixmap
    def rotate(self):
        if self.pixmap.isNull():
            return
        self.angle = (self.angle + 2) % 360
        target_size = 250
        # 只旋转封面
        cover_scaled = self.pixmap.scaled(target_size, target_size, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)
        cover_rotated = cover_scaled.transformed(QTransform().rotate(self.angle), Qt.SmoothTransformation)
        # 裁剪为圆形
        cover_circle = QPixmap(target_size, target_size)
        cover_circle.fill(Qt.transparent)
        painter = QPainter(cover_circle)
        painter.setRenderHint(QPainter.Antialiasing)
        path = QPainterPath()
        path.addEllipse(0, 0, target_size, target_size)
        painter.setClipPath(path)
        x = (target_size - cover_rotated.width()) // 2
        y = (target_size - cover_rotated.height()) // 2
        painter.drawPixmap(x, y, cover_rotated)
        painter.end()
        self.setPixmap(cover_circle)
    def setCover(self, song_path, default_cover="fm.png"):
        self.pixmap = self.load_cover(song_path, default_cover)
class CoverWidget(QWidget):
    def __init__(self, default_cover="fm.png"):
        super().__init__()
        self.setFixedSize(250, 250)
        self.bg_pixmap = QPixmap(default_cover) if os.path.exists(default_cover) else QPixmap()
        self.cover_pixmap = QPixmap()
        self.angle = 0
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.rotate)
        self.timer.start(50)
        self.rotate()
        self.default_cover = default_cover
    def rotate(self):
        self.angle = (self.angle + 2) % 360
        self.update()
    def setCover(self, song_path):
        pixmap = self.load_cover(song_path, self.default_cover)
        self.cover_pixmap = pixmap
        self.update()
    def load_cover(self, song_path, default_cover):
        if not song_path or not os.path.exists(song_path):
            return QPixmap(default_cover) if os.path.exists(default_cover) else QPixmap()
        # 1. 尝试同名jpg
        base, _ = os.path.splitext(song_path)
        jpg_path = base + ".jpg"
        if os.path.exists(jpg_path):
            return QPixmap(jpg_path)
        # 2. 尝试MP3内嵌封面
        try:
            audio = MP3(song_path, ID3=ID3)
            for tag in audio.tags.values():
                if isinstance(tag, APIC):
                    ba = QByteArray(tag.data)
                    pixmap = QPixmap()
                    pixmap.loadFromData(ba)
                    if not pixmap.isNull():
                        return pixmap
        except Exception as e:
            pass
        # 3. 默认封面
        if os.path.exists(default_cover):
            return QPixmap(default_cover)
        return QPixmap()  # 空pixmap
    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        # 1. 画旋转后的 fm.png
        if not self.bg_pixmap.isNull():
            bg = self.bg_pixmap.scaled(250, 250, Qt.KeepAspectRatio, Qt.SmoothTransformation)
            # 以中心为原点旋转
            painter.save()
            painter.translate(self.width() // 2, self.height() // 2)
            painter.rotate(self.angle)
            painter.translate(-bg.width() // 2, -bg.height() // 2)
            painter.drawPixmap(0, 0, bg)
            painter.restore()
        # 2. 画旋转后的封面(内切圆,直径130,居中)
        if not self.cover_pixmap.isNull():
            size = 130
            # 1. 先裁剪为正方形
            src = self.cover_pixmap
            w, h = src.width(), src.height()
            if w != h:
                side = min(w, h)
                x = (w - side) // 2
                y = (h - side) // 2
                src = src.copy(x, y, side, side)
            # 2. 缩放
            cover = src.scaled(size, size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
            # 3. 裁剪为圆形
            cover_circle = QPixmap(size, size)
            cover_circle.fill(Qt.transparent)
            p = QPainter(cover_circle)
            p.setRenderHint(QPainter.Antialiasing)
            path = QPainterPath()
            path.addEllipse(0, 0, size, size)
            p.setClipPath(path)
            p.drawPixmap(0, 0, cover)
            p.end()
            # 4. 以中心为原点旋转
            painter.save()
            painter.translate(self.width() // 2, self.height() // 2)
            painter.rotate(self.angle)
            painter.translate(-size // 2, -size // 2)
            painter.drawPixmap(0, 0, cover_circle)
            painter.restore()
        painter.end()
# 动态歌词控件(简化版)
class LyricLabel(QWidget):
    def __init__(self):
        super().__init__()
        self.lyrics = []  # [(time, text), ...]
        self.current_index = 0
        self.setMinimumHeight(120)
        self.setStyleSheet("background: transparent;")
        self.color_main = QColor("#fff")
        self.color_other = QColor("#aaa")
    def setDemoText(self, text):
        # 只显示一行小字号的提示
        self.lyrics = []
        self.current_index = 0
        self.demo_text = text
        self.update()
    def setLyrics(self, lyrics):
        self.lyrics = lyrics
        self.current_index = 0
        self.demo_text = None
        self.update()
    def setCurrentTime(self, cur_time):
        self.cur_time = cur_time  # 记录当前时间
        idx = 0
        for i, (t, _) in enumerate(self.lyrics):
            if cur_time >= t:
                idx = i
            else:
                break
        if idx != self.current_index:
            self.current_index = idx
            self.update()
        else:
            self.update()  # 即使index没变,也要刷新实现平滑
    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        w, h = self.width(), self.height()
        # 计算要显示的歌词行
        lines = []
        for offset in range(-2, 3):
            idx = self.current_index + offset
            if 0  10:
            font_main = QFont("微软雅黑", main_font_size, QFont.Bold)
            painter.setFont(font_main)
            rect = painter.fontMetrics().boundingRect(main_text)
            if rect.width()  cur_line_time:
            percent = min(max((cur_time - cur_line_time) / (next_line_time - cur_line_time), 0), 1)
        else:
            percent = 0
        # 歌词整体Y轴平滑上移
        scroll_offset = -percent * line_height
        # 歌词整体垂直居中
        start_y = (h - total_lines * line_height) // 2 + scroll_offset
        for i, (offset, text) in enumerate(lines):
            y = start_y + i * line_height + line_height // 2
            if offset == 0:
                painter.setFont(font_main)
                # ====== 彩虹色高亮 ======
                grad = QLinearGradient(0, y-line_height//2, w, y+line_height//2)
                for j in range(7):
                    grad.setColorAt(j/6, QColor.fromHsv(int((j*60 + (cur_time*60)%360)%360), 255, 255))
                painter.setPen(QPen(QBrush(grad), 0))
            else:
                painter.setFont(font_other)
                painter.setPen(self.color_other)
            painter.drawText(0, int(y-line_height//2), w, line_height, Qt.AlignHCenter | Qt.AlignVCenter, text)
class PlaylistWidget(QListWidget):
    favSong = pyqtSignal(str)
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setStyleSheet("font-size:18px;background:#222;color:#fff;")
        self.setDragDropMode(QListWidget.InternalMove)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.show_menu)
        self.model().rowsMoved.connect(self.on_rows_moved)
    def show_menu(self, pos):
        menu = QMenu(self)
        act_fav = menu.addAction("收藏该歌曲")
        act_del = menu.addAction("删除选中项")
        act_clear = menu.addAction("清空列表")
        action = menu.exec_(self.mapToGlobal(pos))
        if action == act_fav:
            self.fav_selected()
        elif action == act_del:
            self.delete_selected()
        elif action == act_clear:
            self.clear_playlist()
    def delete_selected(self):
        for item in self.selectedItems():
            row = self.row(item)
            self.takeItem(row)
            if hasattr(self.parent(), "sync_song_list"):
                self.parent().sync_song_list()
    def clear_playlist(self):
        self.clear()
        if hasattr(self.parent(), "sync_song_list"):
            self.parent().sync_song_list()
    def on_rows_moved(self, *args):
        if hasattr(self.parent(), "sync_song_list"):
            self.parent().sync_song_list()
    def fav_selected(self):
        for item in self.selectedItems():
            song = item.toolTip()
            self.favSong.emit(song)
class FloatingLyricWindow(QWidget):
    EDGE_MARGIN = 8  # 边缘判定宽度
    def __init__(self):
        super().__init__()
        self.setWindowFlags(
            Qt.FramelessWindowHint |
            Qt.WindowStaysOnTopHint |
            Qt.Tool
        )
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setWindowOpacity(0.85)  # 半透明
        self.lyric = FloatingLyricLabel()  # 只显示2行
        layout = QVBoxLayout()
        layout.setContentsMargins(16, 16, 16, 16)
        layout.addWidget(self.lyric)
        self.setLayout(layout)
        self.resize(800, 100)
        desktop = QApplication.desktop()
        self.move(
            (desktop.width() - self.width()) // 2,
            desktop.height() - 150
        )
        # 拖动和缩放相关
        self._move_drag = False
        self._move_DragPosition = None
        self._resize_drag = False
        self._resize_dir = None
    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        rect = self.rect()
        color = QColor(30, 30, 30, 200)  # 深色半透明
        painter.setBrush(color)
        painter.setPen(Qt.NoPen)
        painter.drawRoundedRect(rect, 18, 18)
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            pos = event.pos()
            margin = self.EDGE_MARGIN
            rect = self.rect()
            # 判断是否在边缘
            if pos.x()  rect.width() - margin and pos.y()  rect.height() - margin:
                self._resize_drag = True
                self._resize_dir = 'bottomleft'
            elif pos.x() > rect.width() - margin and pos.y() > rect.height() - margin:
                self._resize_drag = True
                self._resize_dir = 'bottomright'
            elif pos.x()  rect.width() - margin:
                self._resize_drag = True
                self._resize_dir = 'right'
            elif pos.y()  rect.height() - margin:
                self._resize_drag = True
                self._resize_dir = 'bottom'
            else:
                self._move_drag = True
                self._move_DragPosition = event.globalPos() - self.pos()
            event.accept()
    def mouseMoveEvent(self, event):
        if self._move_drag and event.buttons() == Qt.LeftButton:
            self.move(event.globalPos() - self._move_DragPosition)
            event.accept()
        elif self._resize_drag and event.buttons() == Qt.LeftButton:
            gpos = event.globalPos()
            geo = self.geometry()
            minw, minh = 200, 50
            if self._resize_dir == 'left':
                diff = gpos.x() - geo.x()
                neww = geo.width() - diff
                if neww > minw:
                    geo.setLeft(gpos.x())
            elif self._resize_dir == 'right':
                neww = gpos.x() - geo.x()
                if neww > minw:
                    geo.setWidth(neww)
            elif self._resize_dir == 'top':
                diff = gpos.y() - geo.y()
                newh = geo.height() - diff
                if newh > minh:
                    geo.setTop(gpos.y())
            elif self._resize_dir == 'bottom':
                newh = gpos.y() - geo.y()
                if newh > minh:
                    geo.setHeight(newh)
            elif self._resize_dir == 'topleft':
                diffx = gpos.x() - geo.x()
                diffy = gpos.y() - geo.y()
                neww = geo.width() - diffx
                newh = geo.height() - diffy
                if neww > minw:
                    geo.setLeft(gpos.x())
                if newh > minh:
                    geo.setTop(gpos.y())
            elif self._resize_dir == 'topright':
                diffy = gpos.y() - geo.y()
                neww = gpos.x() - geo.x()
                newh = geo.height() - diffy
                if neww > minw:
                    geo.setWidth(neww)
                if newh > minh:
                    geo.setTop(gpos.y())
            elif self._resize_dir == 'bottomleft':
                diffx = gpos.x() - geo.x()
                neww = geo.width() - diffx
                newh = gpos.y() - geo.y()
                if neww > minw:
                    geo.setLeft(gpos.x())
                if newh > minh:
                    geo.setHeight(newh)
            elif self._resize_dir == 'bottomright':
                neww = gpos.x() - geo.x()
                newh = gpos.y() - geo.y()
                if neww > minw:
                    geo.setWidth(neww)
                if newh > minh:
                    geo.setHeight(newh)
            self.setGeometry(geo)
            event.accept()
        # 无论是否拖动,都要设置光标
        self.update_cursor(event.pos())
    def mouseReleaseEvent(self, event):
        self._move_drag = False
        self._resize_drag = False
        self._resize_dir = None
    def setLyrics(self, lyrics):
        self.lyric.setLyrics(lyrics)
    def setCurrentTime(self, time):
        self.lyric.setCurrentTime(time)
    def update_cursor(self, pos):
        margin = self.EDGE_MARGIN
        rect = self.rect()
        if (pos.x()  rect.width() - margin and pos.y() > rect.height() - margin):
            self.setCursor(Qt.SizeFDiagCursor)
        elif (pos.x() > rect.width() - margin and pos.y()  rect.height() - margin):
            self.setCursor(Qt.SizeBDiagCursor)
        elif pos.x()  rect.width() - margin:
            self.setCursor(Qt.SizeHorCursor)
        elif pos.y()  rect.height() - margin:
            self.setCursor(Qt.SizeVerCursor)
        else:
            self.setCursor(Qt.ArrowCursor)
    def enterEvent(self, event):
        self.update_cursor(self.mapFromGlobal(QCursor.pos()))
    def leaveEvent(self, event):
        self.setCursor(Qt.ArrowCursor)
class FloatingLyricLabel(LyricLabel):
    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        w, h = self.width(), self.height()
        # 只显示当前行和下一行,且排除空行
        lines = []
        idx = self.current_index
        # 找到当前行及下一个非空行
        count = 0
        while idx  10:
                font = QFont("微软雅黑", font_size, QFont.Bold if bold else QFont.Normal)
                painter.setFont(font)
                rect = painter.fontMetrics().boundingRect(text)
                if rect.width() = 0:
            self.current_index = row
            state = self.vlc_player.get_state()
            if state in (vlc.State.Playing, vlc.State.Buffering):
                self.vlc_player.pause()
            elif state == vlc.State.Paused:
                self.vlc_player.play()  # 继续播放
            elif state in (vlc.State.Stopped, vlc.State.Ended, vlc.State.NothingSpecial):
                self.play_song_by_index(row)
    def play_song_by_index(self, idx):
        if 0  0:
            pos = self.slider.value() / 100
            self.vlc_player.set_time(int(total * pos))
    def on_slider_moved(self, value):
        total = self.vlc_player.get_length()
        if total > 0:
            cur_time = int(total * value / 100)
            self.time_label_left.setText(self.ms_to_mmss(cur_time))
    def update_progress(self):
        if self.vlc_player.is_playing() and not self.slider_is_pressed:
            total = self.vlc_player.get_length()
            cur = self.vlc_player.get_time()
            if total > 0:
                percent = int(cur / total * 100)
                self.slider.setValue(percent)
                self.time_label_left.setText(self.ms_to_mmss(cur))
                self.time_label_right.setText(self.ms_to_mmss(total))
            else:
                self.slider.setValue(0)
                self.time_label_right.setText("--:--")
        elif not self.vlc_player.is_playing():
            if self.vlc_player.get_state() == vlc.State.Ended:
                if self.shuffle_mode:
                    import random
                    if self.song_list:
                        # 避免重复播放当前曲目
                        candidates = [i for i in range(len(self.song_list)) if i != self.current_index]
                        if candidates:
                            next_index = random.choice(candidates)
                        else:
                            next_index = self.current_index
                        self.play_song_by_index(next_index)
                else:
                    self.play_next()
        # 图标联动
        state = self.vlc_player.get_state()
        if state in (vlc.State.Playing, vlc.State.Buffering):
            self.btn_play.setText("⏸")
        else:
            self.btn_play.setText("▶")
        if hasattr(self, "lyric") and hasattr(self, "lyrics_parsed"):
            cur = self.vlc_player.get_time() / 1000
            self.lyric.setCurrentTime(cur)
            self.floating_lyric.setCurrentTime(cur)  # 同步到浮动歌词
    def ms_to_mmss(self, ms):
        s = int(ms // 1000)
        m = s // 60
        s = s % 60
        return f"{m:02d}:{s:02d}"
    def save_playlist(self):
        self.sync_song_list()
        file_path, _ = QFileDialog.getSaveFileName(self, "保存播放列表", "", "播放列表文件 (*.m3u *.txt);;所有文件 (*)")
        if file_path:
            try:
                with open(file_path, "w", encoding="utf-8") as f:
                    for song in self.song_list:
                        f.write(song + "\n")
            except Exception as e:
                print("保存失败:", e)
    def load_playlist(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "加载播放列表", "", "播放列表文件 (*.m3u *.txt);;所有文件 (*)")
        if file_path:
            try:
                with open(file_path, "r", encoding="utf-8") as f:
                    lines = [line.strip() for line in f if line.strip()]
                self.song_list.clear()
                self.list_widget.clear()
                for song in lines:
                    if os.path.exists(song):
                        self.song_list.append(song)
                        item = QListWidgetItem(os.path.basename(song))
                        item.setToolTip(song)
                        self.list_widget.addItem(item)
                if self.song_list:
                    self.list_widget.setCurrentRow(0)
                    self.play_song_by_index(0)
                # 保存最后一次歌单路径
                with open("last_playlist.txt", "w", encoding="utf-8") as f:
                    f.write(file_path)
            except Exception as e:
                print("加载失败:", e)
    def sync_song_list(self):
        self.song_list = []
        for i in range(self.list_widget.count()):
            item = self.list_widget.item(i)
            if item and item.toolTip():
                self.song_list.append(item.toolTip())
    def toggle_shuffle_mode(self):
        self.shuffle_mode = not self.shuffle_mode
        self.update_shuffle_button_style()
        print(f"切换后模式: {self.shuffle_mode}, 按钮文本: {self.btn_loop.text()}")
    def stop_play(self):
        self.vlc_player.stop()
        self.timer.stop()
        self.slider.setValue(0)
        self.time_label_left.setText("00:00")
        self.time_label_right.setText("--:--")
        self.lyric.setDemoText(self.default_lyric_text)
        self.append_to_fav(self.song_list[self.current_index])
    def update_shuffle_button_style(self):
        if self.shuffle_mode:
            self.btn_loop.setText("🔀")
        else:
            self.btn_loop.setText("🔁")
        self.btn_loop.update()  # 强制刷新按钮
    def load_lrc(self, song_path):
        lrc_path = os.path.splitext(song_path)[0] + ".lrc"
        if os.path.exists(lrc_path):
            with open(lrc_path, encoding="utf-8") as f:
                return f.read()
        return None
    def load_embedded_lyric(self, song_path):
        try:
            audio = MP3(song_path, ID3=ID3)
            for tag in audio.tags.values():
                if tag.FrameID == "USLT":
                    return tag.text
        except Exception:
            pass
        return None
    def toggle_floating_lyric(self):
        if self.floating_lyric_visible:
            self.floating_lyric.hide()
            self.floating_lyric_visible = False
            self.btn_float_lyric.setStyleSheet(
                "font-size: 24px; background: #333; color: #fff; border-radius: 24px; font-weight: bold;"
            )
        else:
            self.floating_lyric.show()
            self.floating_lyric_visible = True
            self.btn_float_lyric.setStyleSheet(
                "font-size: 24px; background: #09f; color: #fff; border-radius: 24px; font-weight: bold;"
            )
    def toggle_mute(self):
        self.is_muted = not self.is_muted
        self.vlc_player.audio_set_mute(self.is_muted)
        if self.is_muted:
            self.btn_mute.setText("🔇")
            self.btn_mute.setStyleSheet("font-size: 24px; background: #09f; color: #fff; border-radius: 24px; font-weight: bold;")
        else:
            self.btn_mute.setText("🔊")
            self.btn_mute.setStyleSheet("font-size: 24px; background: #333; color: #fff; border-radius: 24px; font-weight: bold;")
    def append_to_fav(self, song):
        fav_path = os.path.abspath("收藏歌单.m3u")
        try:
            need_header = not os.path.exists(fav_path)
            if os.path.exists(fav_path):
                with open(fav_path, "r", encoding="utf-8") as f:
                    lines = [line.strip() for line in f if line.strip()]
                if song in lines:
                    return
            with open(fav_path, "a", encoding="utf-8") as f:
                if need_header:
                    f.write("#EXTM3U\n")
                f.write(song + "\n")
            print(f"已收藏到: {fav_path}")  # 调试用
        except Exception as e:
            print("收藏失败:", e)
    def load_last_playlist(self):
        try:
            if os.path.exists("last_playlist.txt"):
                with open("last_playlist.txt", "r", encoding="utf-8") as f:
                    file_path = f.read().strip()
                if file_path and os.path.exists(file_path):
                    with open(file_path, "r", encoding="utf-8") as f:
                        lines = [line.strip() for line in f if line.strip()]
                    self.song_list.clear()
                    self.list_widget.clear()
                    for song in lines:
                        if os.path.exists(song):
                            self.song_list.append(song)
                            item = QListWidgetItem(os.path.basename(song))
                            item.setToolTip(song)
                            self.list_widget.addItem(item)
                    if self.song_list:
                        self.list_widget.setCurrentRow(0)
                        self.play_song_by_index(0)
        except Exception as e:
            print("自动加载上次歌单失败:", e)
def parse_lrc(lrc_text):
    pattern = re.compile(r"\[(\d+):(\d+)(?:\.(\d+))?\](.*)")
    result = []
    for line in lrc_text.splitlines():
        m = pattern.match(line)
        if m:
            minute = int(m.group(1))
            second = int(m.group(2))
            ms = int(m.group(3) or 0)
            time = minute * 60 + second + ms / 100 if ms else minute * 60 + second
            text = m.group(4).strip()
            result.append((time, text))
    result.sort()
    return result
if __name__ == "__main__":
    app = QApplication(sys.argv)
    player = MP3Player()
    player.show()
    print("当前工作目录:", os.getcwd())
    sys.exit(app.exec_())

封面, 歌词

ciocoinwnnw   

楼主,真是厉害!短短时间内就搞出一个这么好看的音乐播放器,而且功能还这么强大。黑胶唱片封面和浮动歌词特别有感觉,完全提升了听歌体验。用GPT-4.1写的代码也让我很感慨AI的发展,居然能帮助开发出这么复杂的应用。
liuyang207
OP
  

下载提供最新的编译版本了,界面美化了一下,感觉差不多了。
动态的唱针移动,光影流动的背景等等。
https://www.alipan.com/s/w2eqoL2fZbw
liutao0474   

感谢楼主分享
zqfbz   

程序员要下岗了!!!
qsxls   

这个好,可以玩还可以学习。谢谢楼主分享
ailiwude   

谢谢谢谢谢谢
xk1539287520   

大佬能写个wince使用的吗?
Open   

AI增加了便利性
shimeng0624   

AI带的便利是有好有坏呀,期待有更大的突破。
您需要登录后才可以回帖 登录 | 立即注册

返回顶部