Reputation: 3
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_()
Upvotes: 0
Views: 1584
Reputation: 243887
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_()
Upvotes: 2