Search code examples
pythonanimationmatplotlibpyqt5qvideowidget

How can run both of matplotlib and PyQt5 Video with python


I'm trying to run QVideoWidget and matplotlib animation together,

but when I run this py, QVideoWidget is not working.

I got QVideoWidget code and matplotlib animation code then sum those code together in below code.

If I close the matplotlib animation Window, then the QVideoWidget is working.

I wonder why this happen and solve this problem

Thank you

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import random
from PyQt5.QtCore import QDir, Qt, QUrl
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtWidgets import (QApplication, QFileDialog, QHBoxLayout, QLabel,
    QPushButton, QSizePolicy, QSlider, QStyle, QVBoxLayout, QWidget)
from PyQt5.QtWidgets import QMainWindow,QWidget, QPushButton, QAction
from PyQt5.QtGui import QIcon
import sys
from PyQt5.QtWidgets import (QApplication, QCheckBox, QGridLayout, QGroupBox,
    QMenu, QPushButton, QRadioButton, QVBoxLayout, QWidget)

# Parameters
x_len = 200         # Number of points to display
y_range = [10, 40]  # Range of possible Y values to display

# Create figure for plotting
fig = plt.figure(facecolor='white')
ax = fig.add_subplot(2, 1, 1)
bx = fig.add_subplot(2, 1, 2)
xs = list(range(0, 200))
ys = [0] * x_len
ax.set_ylim(y_range)
bx.set_ylim(y_range)
# Initialize communication with TMP102


# When everything done, release the capture


# Create a blank line. We will update the line in animate
line, = ax.plot(xs, ys)
line2, = bx.plot(xs, ys)
# Add labels
plt.title('TMP102 Temperature over Time')
plt.xlabel('Samples')
plt.ylabel('Temperature (deg C)')
class VideoWindow(QMainWindow):
    def __init__(self, parent=None):

        super(VideoWindow, self).__init__(parent)

        self.setWindowTitle("PyQt Video Player Widget Example - pythonprogramminglanguage.com")

        self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)

        videoWidget = QVideoWidget()

        self.playButton = QPushButton()
        self.playButton.setEnabled(False)
        self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
        self.playButton.clicked.connect(self.play)

        self.positionSlider = QSlider(Qt.Horizontal)
        self.positionSlider.setRange(0, 0)
        self.positionSlider.sliderMoved.connect(self.setPosition)

        self.errorLabel = QLabel()
        self.errorLabel.setSizePolicy(QSizePolicy.Preferred,
            QSizePolicy.Maximum)

        # Create new action
        openAction = QAction(QIcon('open.png'), '&Open', self)
        openAction.setShortcut('Ctrl+O')
        openAction.setStatusTip('Open movie')
        openAction.triggered.connect(self.openFile)

        # Create exit action
        exitAction = QAction(QIcon('exit.png'), '&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(self.exitCall)

        # Create menu bar and add action
        menuBar = self.menuBar()
        fileMenu = menuBar.addMenu('&File')
        #fileMenu.addAction(newAction)
        fileMenu.addAction(openAction)
        fileMenu.addAction(exitAction)

        # Create a widget for window contents
        wid = QWidget(self)
        self.setCentralWidget(wid)

        # Create layouts to place inside widget
        controlLayout = QHBoxLayout()
        controlLayout.setContentsMargins(0, 0, 0, 0)
        controlLayout.addWidget(self.playButton)
        controlLayout.addWidget(self.positionSlider)
        layout = QVBoxLayout()
        layout.addWidget(videoWidget)
        layout.addLayout(controlLayout)
        layout.addWidget(self.errorLabel)
        # Set widget to contain window contents

        wid.setLayout(layout)

        self.mediaPlayer.setVideoOutput(videoWidget)
        self.mediaPlayer.stateChanged.connect(self.mediaStateChanged)
        self.mediaPlayer.positionChanged.connect(self.positionChanged)
        self.mediaPlayer.durationChanged.connect(self.durationChanged)
        self.mediaPlayer.error.connect(self.handleError)


    def openFile(self):
        fileName, _ = QFileDialog.getOpenFileName(self, "Open Movie",
            QDir.homePath())

        if fileName != '':
            self.mediaPlayer.setMedia(
                QMediaContent(QUrl.fromLocalFile(fileName)))
            self.playButton.setEnabled(True)

    def exitCall(self):
        sys.exit(app.exec_())

    def play(self):
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.mediaPlayer.pause()
        else:
            self.mediaPlayer.play()

    def mediaStateChanged(self, state):
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.playButton.setIcon(
                self.style().standardIcon(QStyle.SP_MediaPause))
        else:
            self.playButton.setIcon(
                self.style().standardIcon(QStyle.SP_MediaPlay))

    def positionChanged(self, position):
        self.positionSlider.setValue(position)

    def durationChanged(self, duration):
        self.positionSlider.setRange(0, duration)

    def setPosition(self, position):
        self.mediaPlayer.setPosition(position)



    def handleError(self):
        self.playButton.setEnabled(False)
        self.errorLabel.setText("Error: " + self.mediaPlayer.errorString())

    def animate(i, ys):

        # Read temperature (Celsius) from TMP102
        temp_c = random.randint(15, 35)

        # Add y to list
        ys.append(temp_c)

        # Limit y list to set number of items
        ys = ys[-x_len:]

        # Update line with new Y values
        line.set_ydata(ys)
        line2.set_ydata(ys)
        return line, line2,

    ani = animation.FuncAnimation(fig, animate, fargs=(ys,),
                              interval=50,
                              blit=True)

# This function is called periodically from FuncAnimation


# Set up plot to call animate() function periodically



if __name__ == '__main__':
    app = QApplication(sys.argv)

    player = VideoWindow()
    player.resize(640, 480)
    player.show()
    plt.show()
    sys.exit(app.exec_())

Solution

  • Every GUI needs an event loop to be able to update itself and attend user events, OS events, etc. And in the case of matplotlib it is called when using plt.show() and in the case of PyQt5 when calling app.exec_(). With your current code the event loop of matplotlib is blocking PyQt, so you can not interact with the window created by PyQt5.

    The solution is simple, matplotlib supports several backends including PyQt5, so the solution is to use it, in the following link you can find an example.

    Considering the above, the following code implements the solution:

    import random
    import matplotlib
    # Make sure that we are using QT5
    matplotlib.use('Qt5Agg')
    from PyQt5 import QtCore, QtGui, QtWidgets, QtMultimedia, QtMultimediaWidgets
    
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.figure import Figure
    import matplotlib.animation as animation
    
    
    class MyMplCanvas(FigureCanvas):
        def __init__(self, parent=None, width=5, height=4, dpi=100):
            fig = Figure(figsize=(width, height), dpi=dpi)
    
            # https://stackoverflow.com/a/6981055/6622587
            ax = fig.add_subplot(111)    # The big subplot
            self.ax = fig.add_subplot(211)
            self.bx = fig.add_subplot(212)
    
            FigureCanvas.__init__(self, fig)
            self.setParent(parent)
    
            FigureCanvas.setSizePolicy(self,
                                       QtWidgets.QSizePolicy.Expanding,
                                       QtWidgets.QSizePolicy.Expanding)
            FigureCanvas.updateGeometry(self)
    
            ax.set_title('TMP102 Temperature over Time')
            ax.spines['top'].set_color('none')
            ax.spines['bottom'].set_color('none')
            ax.spines['left'].set_color('none')
            ax.spines['right'].set_color('none')
            ax.tick_params(labelcolor='w', top=False, bottom=False, left=False, right=False)
            ax.set_xlabel('Samples')
            ax.set_ylabel('Temperature (deg C)')
    
            self.x_len = 200         # Number of points to display
            self.y_range = [10, 40]  # Range of possible Y values to display
            self.xs = list(range(0, 200))
    
            self.ys = [0 for _ in range(self.x_len)]        
            self.anim = animation.FuncAnimation(fig, self.animate, init_func=self.init, interval=50,blit=True)
    
        def init(self):
            y_range = [10, 40]
            self.ax.set_ylim(*y_range)
            self.bx.set_ylim(*y_range)
            self.line, = self.ax.plot(self.xs, self.ys)
            self.line2, = self.bx.plot(self.xs, self.ys)
            return self.line, self.line2
    
        def animate(self, i):
            temp_c = random.randint(15, 35)
            self.ys.append(temp_c)
            self.ys = self.ys[-self.x_len:]
            self.line.set_ydata(self.ys)
            self.line2.set_ydata(self.ys)
            return self.line, self.line2
    
    
    class ApplicationWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(ApplicationWindow, self).__init__(parent)
            main_widget = QtWidgets.QWidget()
            l = QtWidgets.QVBoxLayout(main_widget)
            sc = MyMplCanvas(main_widget, width=5, height=4, dpi=100)
            l.addWidget(sc)
            self.setCentralWidget(main_widget)
    
    
    class VideoWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
    
            super(VideoWindow, self).__init__(parent)
    
            self.setWindowTitle("PyQt Video Player Widget Example - pythonprogramminglanguage.com")
    
            self.mediaPlayer = QtMultimedia.QMediaPlayer(self, QtMultimedia.QMediaPlayer.VideoSurface)
    
            videoWidget = QtMultimediaWidgets.QVideoWidget()
    
            self.playButton = QtWidgets.QPushButton()
            self.playButton.setEnabled(False)
            self.playButton.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaPlay))
            self.playButton.clicked.connect(self.play)
    
            self.positionSlider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
            self.positionSlider.setRange(0, 0)
            self.positionSlider.sliderMoved.connect(self.setPosition)
    
            self.errorLabel =QtWidgets.QLabel()
            self.errorLabel.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum)
    
            # Create new action
            openAction = QtWidgets.QAction(QtGui.QIcon('open.png'), '&Open', self)
            openAction.setShortcut('Ctrl+O')
            openAction.setStatusTip('Open movie')
            openAction.triggered.connect(self.openFile)
    
            # Create exit action
            exitAction = QtWidgets.QAction(QtGui.QIcon('exit.png'), '&Exit', self)
            exitAction.setShortcut('Ctrl+Q')
            exitAction.setStatusTip('Exit application')
            exitAction.triggered.connect(self.exitCall)
    
            # Create menu bar and add action
            menuBar = self.menuBar()
            fileMenu = menuBar.addMenu('&File')
            #fileMenu.addAction(newAction)
            fileMenu.addAction(openAction)
            fileMenu.addAction(exitAction)
    
            # Create a widget for window contents
            wid = QtWidgets.QWidget()
            self.setCentralWidget(wid)
    
            # Create layouts to place inside widget
            controlLayout = QtWidgets.QHBoxLayout()
            controlLayout.setContentsMargins(0, 0, 0, 0)
            controlLayout.addWidget(self.playButton)
            controlLayout.addWidget(self.positionSlider)
            layout = QtWidgets.QVBoxLayout()
            layout.addWidget(videoWidget)
            layout.addLayout(controlLayout)
            layout.addWidget(self.errorLabel)
            # Set widget to contain window contents
    
            wid.setLayout(layout)
    
            self.mediaPlayer.setVideoOutput(videoWidget)
            self.mediaPlayer.stateChanged.connect(self.mediaStateChanged)
            self.mediaPlayer.positionChanged.connect(self.positionChanged)
            self.mediaPlayer.durationChanged.connect(self.durationChanged)
            self.mediaPlayer.error.connect(self.handleError)
    
    
        def openFile(self):
            fileName, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open Movie", QtCore.QDir.homePath())
    
            if fileName:
                media = QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(fileName))
                self.mediaPlayer.setMedia(media)
                self.playButton.setEnabled(True)
    
        def exitCall(self):
            QtWidgets.QApplication.quit()
    
        def play(self):
            if self.mediaPlayer.state() == QtMultimedia.QMediaPlayer.PlayingState:
                self.mediaPlayer.pause()
            else:
                self.mediaPlayer.play()
    
        def mediaStateChanged(self, state):
            if self.mediaPlayer.state() == QtMultimedia.QMediaPlayer.PlayingState:
                self.playButton.setIcon(
                    self.style().standardIcon(QtWidgets.QStyle.SP_MediaPause))
            else:
                self.playButton.setIcon(
                    self.style().standardIcon(QtWidgets.QStyle.SP_MediaPlay))
    
        def positionChanged(self, position):
            self.positionSlider.setValue(position)
    
        def durationChanged(self, duration):
            self.positionSlider.setRange(0, duration)
    
        def setPosition(self, position):
            self.mediaPlayer.setPosition(position)
    
        def handleError(self):
            self.playButton.setEnabled(False)
            self.errorLabel.setText("Error: " + self.mediaPlayer.errorString())
    
    
    if __name__ == '__main__':
        import sys
        app = QtWidgets.QApplication(sys.argv)
    
        player = VideoWindow()
        player.resize(640, 480)
        player.show()
    
        w = ApplicationWindow()
        w.show()
        sys.exit(app.exec_())