【Python】音乐可视化播放器(PyQt5 + matplotlib.animation)

浪尽此生 提交于 2020-03-11 19:54:57

前言

最近同学有个作业,做音乐可视化播放器,为了学习PyQt,我就尝试做了做。该设计主要分为音乐播放器可视化两部分。两部分单独做相对于结合在一起容易很多,结合的过程遇到了很多麻烦。

音乐播放器:

  • 采用QtDesigner进行界面设计。
  • 采用PyQt5.QtMultimedia进行功能设计,包含文件读取、播放、暂停、进度条、时间显示。

可视化:

  • 采用pydub结合ffmpeg将.mp3转成.wav。
  • 采用wave读取音频文件(.wav)。
  • 采用matplotlib.animation方法动态显示图像。

参考博客:
1 python 将MP3格式转换为WAV格式(ffmpeg安装,使用pycharm安装包)
2 pyqt5+matplotlib+Funcanimation+scatter(qt5+动态散点图)
3 python 音频可视化


一 界面设计

1.1 QtDesigner界面设计

界面设计采用QtDesigner进行设计,如下图:
在这里插入图片描述
包含对象如下:
在这里插入图片描述

1.2 导出代码

使用pyuic编译出.py代码。

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'gui.ui'
#
# Created by: PyQt5 UI code generator 5.14.1
#
# WARNING! All changes made in this file will be lost!


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(449, 439)
        MainWindow.setAnimated(True)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
        self.centralwidget.setSizePolicy(sizePolicy)
        self.centralwidget.setObjectName("centralwidget")
        self.layoutWidget = QtWidgets.QWidget(self.centralwidget)
        self.layoutWidget.setGeometry(QtCore.QRect(11, 11, 431, 30))
        self.layoutWidget.setObjectName("layoutWidget")
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.layoutWidget)
        self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
        self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.lab_name = QtWidgets.QLabel(self.layoutWidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.lab_name.sizePolicy().hasHeightForWidth())
        self.lab_name.setSizePolicy(sizePolicy)
        self.lab_name.setAlignment(QtCore.Qt.AlignCenter)
        self.lab_name.setObjectName("lab_name")
        self.horizontalLayout_2.addWidget(self.lab_name)
        self.btn_openFile = QtWidgets.QPushButton(self.layoutWidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.btn_openFile.sizePolicy().hasHeightForWidth())
        self.btn_openFile.setSizePolicy(sizePolicy)
        self.btn_openFile.setObjectName("btn_openFile")
        self.horizontalLayout_2.addWidget(self.btn_openFile)
        self.layoutWidget1 = QtWidgets.QWidget(self.centralwidget)
        self.layoutWidget1.setGeometry(QtCore.QRect(11, 400, 431, 30))
        self.layoutWidget1.setObjectName("layoutWidget1")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget1)
        self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.lab_time = QtWidgets.QLabel(self.layoutWidget1)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.lab_time.sizePolicy().hasHeightForWidth())
        self.lab_time.setSizePolicy(sizePolicy)
        self.lab_time.setObjectName("lab_time")
        self.horizontalLayout.addWidget(self.lab_time)
        self.slider_time = QtWidgets.QSlider(self.layoutWidget1)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.slider_time.sizePolicy().hasHeightForWidth())
        self.slider_time.setSizePolicy(sizePolicy)
        self.slider_time.setOrientation(QtCore.Qt.Horizontal)
        self.slider_time.setObjectName("slider_time")
        self.horizontalLayout.addWidget(self.slider_time)
        self.lab_duration = QtWidgets.QLabel(self.layoutWidget1)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.lab_duration.sizePolicy().hasHeightForWidth())
        self.lab_duration.setSizePolicy(sizePolicy)
        self.lab_duration.setObjectName("lab_duration")
        self.horizontalLayout.addWidget(self.lab_duration)
        self.btn_start = QtWidgets.QPushButton(self.layoutWidget1)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.btn_start.sizePolicy().hasHeightForWidth())
        self.btn_start.setSizePolicy(sizePolicy)
        self.btn_start.setObjectName("btn_start")
        self.horizontalLayout.addWidget(self.btn_start)
        self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
        self.verticalLayoutWidget.setGeometry(QtCore.QRect(10, 40, 431, 361))
        self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
        self.container = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
        self.container.setContentsMargins(0, 0, 0, 0)
        self.container.setObjectName("container")
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "音乐播放器"))
        self.lab_name.setText(_translate("MainWindow", "暂无歌曲导入"))
        self.btn_openFile.setText(_translate("MainWindow", "打开文件"))
        self.lab_time.setText(_translate("MainWindow", "00:00"))
        self.lab_duration.setText(_translate("MainWindow", "00:00"))
        self.btn_start.setText(_translate("MainWindow", "播放"))

二 播放器及可视化

2.1 演示效果

在这里插入图片描述
视频播放链接:

2.2 代码:

from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
from PyQt5.QtMultimedia import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.animation import FuncAnimation
from gui.gui import Ui_MainWindow
from scipy.signal import detrend
from pydub import AudioSegment

import matplotlib.pyplot as plt
import numpy as np
import struct
import wave
import os
import sys
import time

chunk = 1024

# 将该路径文件从mp3转为wav
def change_format(path):
    new_path = path.replace('mp3', 'wav')
    # 将mp3文件转换成wav
    sound = AudioSegment.from_mp3(path)
    sound.export(new_path, format="wav")


# FigureCanvas 对象
class MyMplCanvas(FigureCanvas):
    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = plt.figure(figsize=(width, height), dpi=dpi, facecolor='#f0f0f0')       # facecolor 背景色
        self.ax = fig.gca(projection='polar')
        self.ax.set_axis_off()
        self.ln, = self.ax.plot([], [])
        FigureCanvas.__init__(self, fig)


class MyWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.setupUi(self)
        self.initialize()

    '''初始化'''
    def initialize(self):
        self.setWindowTitle("音乐播放器^_^")
        self.setWindowIcon(QIcon('icon/音乐.png'))
        self.fileName = ""
        self.cur_song = ''
        self.is_pause = True
        self.y_temp = np.zeros(chunk)

        self.playlist = QMediaPlaylist()  # 播放列表
        self.playlist.setPlaybackMode(QMediaPlaylist.Loop)  # 列表循环
        self.player = QMediaPlayer(self)
        self.player.setPlaylist(self.playlist)
        self.player.setVolume(50.0)

        # 按键
        self.btn_openFile.clicked.connect(lambda: self.btn_openFile_click())
        self.btn_start.clicked.connect(lambda: self.btn_start_click())

        # 进度条
        self.slider_time.sliderMoved[int].connect(lambda: self.player.setPosition(self.slider_time.value()))

        # 计时器:控制进度条和进度时间
        self.timer = QTimer(self)
        self.timer.start(1000)
        self.timer.timeout.connect(self.player_timer)

        # 音乐可视化
        self.isualization()

    # 坐标初始化
    def init_draw(self):
        self.canvas.ax.set_ylim(-0.1, 0.1)
        self.canvas.ln.set_data(np.linspace(0, 2 * np.pi, chunk), np.zeros(chunk))


        return self.canvas.ln,

    # 坐标更新
    def update_line(self, frame):
        if self.is_pause is False:
            data = self.wf.readframes(chunk)
            data_int = struct.unpack(str(chunk * 4) + 'B', data)
            y_detrend = detrend(data_int)
            yft = np.abs(np.fft.fft(y_detrend))
            y_vals = yft[:chunk] / (chunk * chunk * 4)
            ind = np.where(y_vals > (np.max(y_vals) + np.min(y_vals)) / 2)
            y_vals[ind[0]] *= 3
            self.y_temp = y_vals
        else:
            y_vals = self.y_temp       # 当暂停时,保存的是上一次的值

        self.canvas.ln.set_ydata(y_vals)
        return self.canvas.ln,

    # 音乐可视化
    def isualization(self):
        self.canvas = MyMplCanvas(self.container, width=6, height=6, dpi=100)
        self.container.addWidget(self.canvas)  # 6
        self.ani = FuncAnimation(self.canvas.figure, self.update_line, init_func=self.init_draw, interval=32, blit=True)

    # 设置进度条和播放时间
    def player_timer(self):
        self.slider_time.setMinimum(0)
        self.slider_time.setMaximum(self.player.duration())
        self.slider_time.setValue(self.slider_time.value() + 1000)

        self.lab_time.setText(time.strftime('%M:%S', time.localtime(self.player.position() / 1000)))
        self.lab_duration.setText(time.strftime('%M:%S', time.localtime(self.player.duration() / 1000)))

        # 进度条满了之后回零
        if self.player.duration() == self.slider_time.value():
            self.slider_time.setValue(0)

    # 打开音乐文件,并添加至playlist
    def btn_openFile_click(self):
        self.playlist.clear()     # 读取歌曲前,清空playlist
        self.fileName, filetype = QFileDialog.getOpenFileName(self, '选择文件', '', '音频文件 (*.mp3; *.wav)')
        if len(self.fileName) == 0:
            print("取消选择")
            return
        else:
            print('当前歌曲路径:' + self.fileName)
            self.cur_song = os.path.basename(self.fileName)
            self.lab_name.setText(self.cur_song)

            # 如果是mp3格式,则将mp3转换wav,保存到同一目录下
            if self.cur_song[-3:] == 'mp3':
                change_format(self.fileName)
                self.fileName = self.fileName.replace('mp3', 'wav')
                print('new'+ self.fileName)

            # 将音频文件添加到playlist
            self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(self.fileName)))

            # 可视化部分wave
            self.wf = wave.open(self.fileName)

        # 正在播放音乐时,中断播放
        if self.is_pause is False:
            self.player.pause()
            self.btn_start.setText('播放')

    def btn_start_click(self):

        if self.is_pause:
            self.is_pause = False
            self.player.play()
            self.btn_start.setText('暂停')
            print('当前播放歌曲: ' + self.cur_song)
        else:
            self.is_pause = True
            self.player.pause()
            self.btn_start.setText('播放')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    myWin = MyWindow()
    myWin.show()
    sys.exit(app.exec_())

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!