简体   繁体   中英

Run While loop with PyQt5

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_()

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_()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM