1

I am new to classes and PyQt5 and trying to build a video player using PyQt5 and Opencv. Using the code in OpenCV Video Capture with PyQt4 and making the changes to convert commands in PyQt4 into PyQt5 and adding a pause button, I now have a video player with pause, play, end, and quit buttons. This is the code which is working fine:

import cv2
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication, QPushButton, 
QVBoxLayout, QFileDialog

fileName = 'C:/Users/Art/Downloads/testVideo.mp4'


class Capture():
    def __init__(self):
        self.capturing = False
        self.c = cv2.VideoCapture(fileName)

    def startCapture(self):
        self.capturing = True
        cap = self.c
        while(self.capturing):
            ret, frame = cap.read()
            cv2.imshow("Capture", frame)
            cv2.waitKey(5)
        cv2.destroyAllWindows()

    def endCapture(self):
        self.capturing = False

    def pauseCapture(self): 
        if cv2.waitKey(0) & 0xFF == ord('p'):  # Pause
            self.capturing = False

    def quitCapture(self):
        cap = self.c
        cv2.destroyAllWindows()
        cap.release()
        QtCore.QCoreApplication.quit()

class Window(QtWidgets.QWidget):
    def __init__(self):

    QtWidgets.QWidget.__init__(self)
    self.setWindowTitle('Control Panel')

    self.capture = Capture()
    self.start_button = QPushButton('Start', self)
    self.start_button.clicked.connect(self.capture.startCapture)

    self.end_button = QPushButton('End', self)
    self.end_button.clicked.connect(self.capture.endCapture)

    self.pause_button = QPushButton('Pause', self)
    self.pause_button.clicked.connect(self.capture.pauseCapture)

    self.quit_button = QPushButton('Quit', self)
    self.quit_button.clicked.connect(self.capture.quitCapture)

    vbox = QVBoxLayout(self)
    vbox.addWidget(self.start_button)
    vbox.addWidget(self.end_button)
    vbox.addWidget(self.pause_button)
    vbox.addWidget(self.quit_button)

    self.setLayout(vbox)
    self.setGeometry(100, 100, 200, 200)
    self.show()

    if __name__== '__main__':
        import sys
        app = QApplication(sys.argv)
        window = Window()
        sys.exit(app.exec())

So far, I hard coded the video file name and its path into the code (fileName). Now, I want to add a load button, that let the user select the video. Something like this:

 self.load_button = QPushButton('Load', self)
 self.load_button.clicked.connect(self.pick_video)

 def pick_video():
     dialog = QtGui.QFileDialog()
     fileName = dialog.getExistingDirectory(None, 
      "Select Folder")
     return fileName

And adding the load button to the list of existing buttons, like this:

vbox.addWidget(self.load_button)

My problem is I don't know how can I merge this into the existing code. If I put it inside Window class, it throws me an error. My question is how I can add something like this into my existing code, such that the user can select the video file after pressing a load button.

Edit: After changing the code based on @ekhumoro, I got something like this:

import cv2
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication, QPushButton, 
QVBoxLayout, QFileDialog


class Capture():
    def __init__(self):
        self.capturing = False

    def startCapture(self, path):
        self.capturing = True
        self.c = cv2.VideoCapture(path)
        while self.capturing:
            ret, frame = self.c.read()
            cv2.imshow("Capture", frame)
            cv2.waitKey(5)
        cv2.destroyAllWindows()

    def endCapture(self):
        self.capturing = False

    def pauseCapture(self): 
        if cv2.waitKey(0) & 0xFF == ord('p'):  # Pause
            self.capturing = False

    def quitCapture(self):
        cap = self.c
        cv2.destroyAllWindows()
        cap.release()
        QtCore.QCoreApplication.quit()

class Window(QtWidgets.QWidget):
    def __init__(self):

        QtWidgets.QWidget.__init__(self)
        self.setWindowTitle('Control Panel')

        self.capture = Capture()
        self.start_button = QPushButton('Start', self)
        self.start_button.clicked.connect(self.start)

        self.end_button = QPushButton('End', self)
        self.end_button.clicked.connect(self.capture.endCapture)

        self.pause_button = QPushButton('Pause', self)
        self.pause_button.clicked.connect(self.capture.pauseCapture)

        self.quit_button = QPushButton('Quit', self)
        self.quit_button.clicked.connect(self.capture.quitCapture)

        vbox = QVBoxLayout(self)
        vbox.addWidget(self.start_button)
        vbox.addWidget(self.end_button)
        vbox.addWidget(self.pause_button)
        vbox.addWidget(self.quit_button)

        self.setLayout(vbox)
        self.setGeometry(100, 100, 200, 200)
        self.show()

    def start(self):
        path = QtWidgets.QFileDialog.getOpenFileName(self)[0]
        if path:
            self.capture.startCapture(path)

if __name__== '__main__':
    import sys
    app = QApplication(sys.argv)
    window = Window()
    sys.exit(app.exec())

But, when I run this code, I get this error: AttributeError: 'Window' object has no attribute 'start'. Another thing is that I want to have a separate button for this process, meaning that once the user run the code, in the window that opens, he/she can click on that button (let's call it load button), then select the video file, which I don't see in this code. Am I missing something somewhere? Or maybe the rearranged code is not what @ekhumoro meant.

Miranda
  • 565
  • 1
  • 10
  • 27

1 Answers1

2

Rearrange the code so that startCapture takes a path parameter. Then pass in the path from the file-dialog in the slot for the start button:

class Capture():
    def __init__(self):
        self.capturing = False

    def startCapture(self, path):
        self.capturing = True
        self.c = cv2.VideoCapture(path)
        while self.capturing:
            ret, frame = self.c.read()
            cv2.imshow("Capture", frame)
            cv2.waitKey(5)
        cv2.destroyAllWindows()

class Window(QtWidgets.QWidget):
    def __init__(self):
        ...
        self.start_button = QPushButton('Start', self)
        self.start_button.clicked.connect(self.start)
        ...

    def start(self):
        path = QtWidgets.QFileDialog.getOpenFileName(self)[0]
        if path:
            self.capture.startCapture(path)

Here is a complete alternative implementation:

import cv2
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication, QPushButton,QVBoxLayout, QFileDialog

class Capture():
    def __init__(self):
        self.capturing = False
        self.c = None

    def setVideoFile(self, path):
        if self.c is not None:
            cv2.destroyAllWindows()
            self.c.release()
        self.c = cv2.VideoCapture(path)
        self.startCapture()

    def startCapture(self):
        self.capturing = True
        cap = self.c
        while(self.capturing):
            ret, frame = cap.read()
            cv2.imshow("Capture", frame)
            cv2.waitKey(5)
        cv2.destroyAllWindows()

    def endCapture(self):
        self.capturing = False

    def pauseCapture(self):
        if cv2.waitKey(0) & 0xFF == ord('p'):  # Pause
            self.capturing = False

    def quitCapture(self):
        cap = self.c
        cv2.destroyAllWindows()
        cap.release()
        QtCore.QCoreApplication.quit()

class Window(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        self.setWindowTitle('Control Panel')

        self.capture = Capture()
        self.open_button = QPushButton('Open', self)
        self.open_button.clicked.connect(self.open)

        self.start_button = QPushButton('Start', self)
        self.start_button.clicked.connect(self.capture.startCapture)

        self.end_button = QPushButton('End', self)
        self.end_button.clicked.connect(self.capture.endCapture)

        self.pause_button = QPushButton('Pause', self)
        self.pause_button.clicked.connect(self.capture.pauseCapture)

        self.quit_button = QPushButton('Quit', self)
        self.quit_button.clicked.connect(self.capture.quitCapture)

        vbox = QVBoxLayout(self)
        vbox.addWidget(self.open_button)
        vbox.addWidget(self.start_button)
        vbox.addWidget(self.end_button)
        vbox.addWidget(self.pause_button)
        vbox.addWidget(self.quit_button)

        self.setLayout(vbox)
        self.setGeometry(100, 100, 200, 200)
        self.show()

    def open(self):
        path = QtWidgets.QFileDialog.getOpenFileName(self)[0]
        if path:
            self.capture.setVideoFile(path)

if __name__== '__main__':
    import sys
    app = QApplication(sys.argv)
    window = Window()
    sys.exit(app.exec())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • @Miranda The indentation in your example is messed up. If you correct it, my code will work just fine. I have added a corrected version to my answer. – ekhumoro Feb 08 '19 at 20:02
  • @ ekhumoro Thank you for the full script! It is working. There is one problem though. When I click on Pause button, in order to start again (start from the frame it was paused), I need to click on Start again. By adding load action to the Start button based on your code, whenever I pause, when I click on Start button to play again, it opens the dialogue box for video file selection. So, my question is, is it possible to add a separate button for load to avoid this problem? – Miranda Feb 08 '19 at 20:24
  • @Miranda I have added an alternative implementation that should do what you want. – ekhumoro Feb 08 '19 at 20:57
  • @ ekhumoro. Thanks! Your script does exactly what I was looking for. – Miranda Feb 08 '19 at 21:03