0

I am trying to run a while loop but when it runs my gui(PyQt5) crashes. I am trying to run a function on button press. When the btn_off_on is pressed i would like off_on method. The method runs a virtual camera and the reason i need a loop is because i constantly need to send video frames to the virtual camera, if the hotkey is pressed the frames sent to the virtual camera change from a video to live webcam.

import PyQt5.QtWidgets as qtw
import pyvirtualcam
import cv2
import keyboard


class MainWindow(qtw.QWidget):
    def __init__(self):
        super().__init__()
        # Initiating Variables
        self.path = ""
        self.WIDTH = 1920
        self.HEIGHT = 1080
        self.FPS = 30
        self.HOTKEY = "alt+shift+;"
        self.cap = cv2.VideoCapture(0)
        self.camera_use = False
        self.FMT = pyvirtualcam.PixelFormat.BGR

        # Initiating Elements
        self.btn_off_on = qtw.QPushButton("On/Off")
        self.btn_path = qtw.QPushButton("Get Video")

        # Everything needed for PyQt5
        self.setWindowTitle("Matthew's Cute")
        self.setLayout(qtw.QVBoxLayout())
        self.front_page()

        self.show()

    def off_on(self):
        with pyvirtualcam.Camera(width=self.WIDTH, height=self.HEIGHT, fps=self.FPS, fmt=self.FMT) as cam:
            print(pyvirtualcam.Camera)
            print(f'Using virtual camera: {cam.device}')
            while True:
                while not self.camera_use: ##########HERE IS WHERE I RUN MY WHILE LOOP
                    try:
                        ret, frame = loop_cap.read()
                        frame = cv2.resize(frame, (self.WIDTH, self.HEIGHT), interpolation=cv2.INTER_AREA)
                        self.front_page()
                        self.show()
                        cam.send(frame)
                        cam.sleep_until_next_frame()
                    except:
                        loop_cap = cv2.VideoCapture(self.path[0])
                        self.camera_use = False

                    if keyboard.is_pressed(self.HOTKEY):
                        self.camera_use = True

                while self.camera_use:
                    ret, frame = self.cap.read()
                    frame = cv2.resize(frame, (self.WIDTH, self.HEIGHT), interpolation=cv2.INTER_AREA)
                    super().__init__()
                    self.setWindowTitle("Matthew's Cute")
                    self.setLayout(qtw.QVBoxLayout())
                    self.front_page()
                    self.show()
                    cam.send(frame)
                    cam.sleep_until_next_frame()

                    if keyboard.is_pressed(self.HOTKEY):
                        self.camera_use = False

    def get_path(self):
        self.path = qtw.QFileDialog.getOpenFileName()
        print(self.path[0])

    def front_page(self):
        container = qtw.QWidget()
        container.setLayout(qtw.QGridLayout())

        # Adding the buttons to the layout
        container.layout().addWidget(self.btn_off_on, 0, 0, 1, 2)
        self.btn_off_on.clicked.connect(self.off_on)############ HERE IS WHERE I CALL THE METHOD

        container.layout().addWidget(self.btn_path, 2, 0, 1, 2)
        self.btn_path.clicked.connect(self.get_path)

        self.layout().addWidget(container)


if __name__ == '__main__':
    app = qtw.QApplication([])
    mw = MainWindow()
    app.setStyle(qtw.QStyleFactory.create('Fusion'))
    app.exec_()
1
  • Running an infinite loop in the main thread will block the event loop. You could try moving the while loop to a QThread. Commented Apr 4, 2021 at 4:12

1 Answer 1

2

The while True are blocking tasks so they must be executed in a secondary thread. In this case, it is possible to read and write the image in that thread and invoke it every T seconds with a QTimer, so it will be easy to start or stop the playback.

from functools import cached_property

import PyQt5.QtCore as qtc
import PyQt5.QtWidgets as qtw
from PyQt5 import sip

import pyvirtualcam
import cv2
import keyboard
import threading


class VirtualCameraController(qtc.QObject):

    WIDTH = 1920
    HEIGHT = 1080
    FPS = 30
    FMT = pyvirtualcam.PixelFormat.BGR

    mutex = threading.Lock()
    finished = qtc.pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self._capture = None

        self.virtual_camera = pyvirtualcam.Camera(
            width=self.WIDTH, height=self.HEIGHT, fps=self.FPS, fmt=self.FMT
        )
        self.timer.timeout.connect(self.on_timeout)
        self.finished.connect(self.timer.start)

        print(f"Using virtual camera: {self.virtual_camera.device}")

    @cached_property
    def timer(self):
        return qtc.QTimer(singleShot=True, interval=0)

    @property
    def capture(self):
        return self._capture

    @capture.setter
    def capture(self, capture):
        if self.capture == capture:
            return
        with self.mutex:
            if self.capture is not None:
                self.capture.release()
            self._capture = capture

    def start(self):
        self.timer.start()

    def read_frame(self):
        if self._capture is not None:
            ret, frame = self._capture.read()
            if ret:
                return frame

    def on_timeout(self):
        threading.Thread(target=self._execute, daemon=True).start()

    def _execute(self):
        with self.mutex:
            frame = self.read_frame()
            if frame is not None:
                try:
                    frame = cv2.resize(
                        frame,
                        (self.virtual_camera.width, self.virtual_camera.height),
                        interpolation=cv2.INTER_AREA,
                    )
                    self.virtual_camera.send(frame)
                except Exception as e:
                    print(e)
                else:
                    self.virtual_camera.sleep_until_next_frame()
        if sip.isdeleted(self):
            self.virtual_camera.close()
            return
        self.finished.emit()

    def stop(self):
        self.timer.stop()


class MainWindow(qtw.QWidget):
    HOTKEY = "alt+shift+;"

    def __init__(self):
        super().__init__()

        self._filename = ""

        self.btn_off_on = qtw.QPushButton("On/Off")
        self.btn_path = qtw.QPushButton("Get Video")

        self.setWindowTitle("Matthew's Cute")

        lay = qtw.QVBoxLayout(self)
        container = qtw.QWidget()
        grid_layout = qtw.QGridLayout(container)
        grid_layout.addWidget(self.btn_off_on, 0, 0, 1, 2)
        container.layout().addWidget(self.btn_path, 2, 0, 1, 2)
        lay.addWidget(container)

        self.btn_off_on.clicked.connect(self.off_on)
        self.btn_path.clicked.connect(self.get_path)

        keyboard.add_hotkey(self.HOTKEY, self.change_capture)

        self._flag = False

    @cached_property
    def virtual_camera_controller(self):
        return VirtualCameraController()

    @property
    def filename(self):
        return self._filename

    def off_on(self):
        self.change_capture()
        self.virtual_camera_controller.start()

    def get_path(self):
        filename, _ = qtw.QFileDialog.getOpenFileName()
        if filename:
            self._filename = filename

    def closeEvent(self, event):
        super().closeEvent(event)
        self.virtual_camera_controller.stop()

    def use_camera(self):
        self.virtual_camera_controller.capture = cv2.VideoCapture(0)

    def use_video(self):
        self.virtual_camera_controller.capture = cv2.VideoCapture(self.filename)

    def change_capture(self):
        if self._flag:
            self.use_video()
        else:
            self.use_camera()
        self._flag = not self._flag


if __name__ == "__main__":
    app = qtw.QApplication([])
    w = MainWindow()
    w.show()
    ret = app.exec_()
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.