ARTICLE AD BOX
I'm trying to create isolated Camera, and want to use it with signals through QLocalSocket. That's needed to prevent freezing GUI of crashing when catching C++ error
Classes relationships: ThreadedCamGear is a main class, it creates QProcess and QLocalSocket inside. After calling init_cam(), it starts _CamWorker inside QProcess and tries to connect QLocal socket with _CamWorker.
Example:
cam = ThreadedCamGear(10)
*cam.init_cam(source) # creates camera, and emits pyqtsignal with success flag
cam.read() # tries to read the frame and emits it/None with pyqtsignal
The problem is that QProcess always crashing neither with Unknown error or ProcessError.Crashed without any additional information. Of course, after(or before?) that I get QLocalSocket::connectToServer: Connection refused
With some simple prints I figured out that method _CamWorker.set*_client is never called, and listening starts successfully.
Some strange behavior, not so related to problem: QProcess's input prints only if I put return nearly after self.process.start()
Al names, except for fixed path of .socket existing files are Invalid with QLocalSocket.ConnectToServer().
My device: Ubuntu 24.04.3 LTS MACHENIKE L16, NVIDIA GeForce RTX™ 5070 Laptop GPU
GNOME version: 46
Kernel version: Linux 6.17.0-14-generic
methods of class, that tries to use ThreadedCamGear:
def set_path(self, path: str): """Change frame, delete all previous data Emits success of the operation with set_path_succeded(bool) pyqtsignal """ if self._async_scenario != 0: return self.path = path self.data = [] self._video_cap = ThreadedCamGear(7) self._video_cap.init_succeed.connect(self._finalize1_set_path) self._video_cap.received_frame.connect(self._finalize2_set_path) self._async_scenario = 1 self._video_cap.init_cam(source=path) def _finalize1_set_path(self, success: bool): async_scenario = self._async_scenario self._async_scenario = 0 if async_scenario != 1: return if not success: if self._video_cap: self._video_cap.finished.connect(self._remove_deleted_threads) self._later_delete_threads.append(self._video_cap) self._video_cap.stop() self._video_cap = None self.set_path_succeded.emit(False) return self._async_scenario = 2 self._video_cap.read() def _finalize2_set_path(self, frame): async_scenario = self._async_scenario self._async_scenario = 0 if async_scenario != 2: return if not frame is None: self.change_frame(frame) self._video_cap.finished.connect(self._remove_deleted_threads) self._later_delete_threads.append(self._video_cap) # to prevent sudden delete self._video_cap.stop() self._video_cap = None self.set_path_succeded.emit(not frame is None)threaded_camgear.py:
from PyQt6.QtCore import QObject, pyqtSignal, QTimer, QProcess from PyQt6.QtNetwork import QLocalSocket from enum import Enum import pickle from time import time_ns import os class ThreadedCamGear(QObject): process: QProcess received_frame = pyqtSignal(object) init_succeed = pyqtSignal(bool) finished = pyqtSignal() def __init__(self, max_timeout: float = 5): """ Parameters: max_timeout (float): max time to wait CamGear for initialization in seconds """ super().__init__(parent=None) self.cam = None self.timeout = max_timeout self.timer = QTimer() self.timer.setSingleShot(True) self.timer.timeout.connect(self._timeout) self._timer_type = _TimerType.NoWaiting self.process = QProcess() self.server_name = f"/home/user/Documents/project_dir/r1.socket" # self.server_name = f"/tmp/threadedcam_{id(self)}_{os.getpid()}.socket" - invalid name self.process.setObjectName(f"ThreadedCamGear.QProcess {id(self.process)}") self.socket = QLocalSocket() self.socket.readyRead.connect(self._receive_from_socket) self.process.readyReadStandardError.connect(self._read_process_error) self.process.readyReadStandardOutput.connect(self._read_process_output) self.process.errorOccurred.connect(self.error) self._init_source = "" def _read_process_error(self): data = self.process.readAllStandardError() print(f"[Child process stderr] {data.data().decode()}") def _read_process_output(self): data = self.process.readAllStandardOutput() print(f"[Child process stdout] {data.data().decode()}") def _receive_from_socket(self): message = pickle.loads(self.socket.readData(5*10**7)) # max image size in 4K if message["type"] == "init_success": self._finalize_init_cam(message["data"]) if message["type"] == "frame": self._finalize_read(message["data"]) def _send_to_socket(self, message: dict) -> bool: "Returns: success(bool)" return self.socket.writeData(pickle.dumps(message)) != -1 def _thread_finished(self): self.finished.emit() def _timeout(self): if self._timer_type == _TimerType.WaitingForInit: print( f"{ThreadedCamGear :: Failed init ThreadedCamGear with source: {self._init_source}" ) self.init_succeed.emit(False) self.stop() if self._timer_type == _TimerType.WaitingForRead: self.received_frame.emit(None) self.stop() self._timer_type = _TimerType.NoWaiting def error(self, error_msg: str): print(f"{self.objectName()}: {error_msg}") self.stop() def _finalize_init_cam(self, success: bool): print("fin", success) if self._timer_type != _TimerType.WaitingForInit: return self.timer.stop() self._timer_type = _TimerType.NoWaiting source = self._init_source self._init_source = "" try: if (not success): if self.process.state() == QProcess.ProcessState.Running: self._send_to_socket({ "type": "stop" }) print( f"{ThreadedCamGear :: Failed init ThreadedCamGear with source: {self._init_source}" ) self.init_succeed.emit(False) return except Exception as err: print( f"{ThreadedCamGear :: Failed init ThreadedCamGear with source: {self._init_source}" ) print("except") self.error(str(err)) self.init_succeed.emit(False) return self.init_succeed.emit(True) def init_cam( self, source: Any = 0, stream_mode: bool = False, backend: int = 0, colorspace: str = None, logging: bool = False, time_delay: int = 0, **options: dict, ): self._init_source = source self._timer_type = _TimerType.WaitingForInit print( f"ThreadedCamGear :: Trying init ThreadedCamGear with source: {source}" ) start_time = time_ns() self.process.start("python", ["-u", "/home/user/Documents/project_dir/source/camgear_process.py", self.server_name]) if not self.process.waitForStarted(self.timeout * 10**3): self.init_succeed.emit(False) return self.socket.connectToServer(self.server_name) timeout = self.timeout * 10**3 - int((time_ns() - start_time) * 10**(-6)) if (timeout <= 0) or not self.socket.waitForConnected(timeout): print(1, self.socket.errorString()) print(self.process.errorString(), 234675467654745456456456) self.init_succeed.emit(False) return print(1) # return self.timer.start(self.timeout * 10**3 - int((time_ns() - start_time) * 10**(-6))) self._send_to_socket({ "type": "init", "data": { "source": source, "stream_mode": stream_mode, "backend": backend, "colorspace": colorspace, "logging": logging, "time_delay": time_delay, **options, } }) def _finalize_read(self, frame): if self._timer_type != _TimerType.WaitingForRead: return self.timer.stop() self._timer_type = _TimerType.NoWaiting self.received_frame.emit(frame) def read(self): """Try to read frame. Result will be emitted within timeout with received_frame pyqtsignal With failed initialization emits None""" self.timer.stop() if self.process.state() != QProcess.ProcessState.Running: self.received_frame.emit(None) return self._timer_type = _TimerType.WaitingForRead self.timer.start(1000) self._send_to_socket({ "type": "read" }) def stop(self): try: self._send_to_socket({ "type": "stop" }) except Exception as err: pass try: self.process.finished.connect(self.deleteLater) self.process.terminate() except Exception as err: pass QTimer.singleShot(20*10**3, lambda: self.process.kill())camgear_process.py:
from PyQt6.QtCore import pyqtSignal, QCoreApplication from PyQt6.QtNetwork import QLocalServer, QLocalSocket from vidgear.gears import CamGear import pickle class _CamWorker(QLocalServer): client: QLocalSocket | None error = pyqtSignal(str) finished = pyqtSignal() initialization_succeed = pyqtSignal(bool) received_frame = pyqtSignal(object) def __init__(self, name: str, parent=...): super().__init__(parent=parent) self.camgear = None self.client = None self.newConnection.connect(self.set_client) QLocalServer.removeServer(name) if not self.listen(name): print(self.errorString()) self.close() print("listening") def set_client(self): print("set client") self.client = self.nextPendingConnection() if not self.client: print(self.errorString()) self.close() return self.client.disconnected.connect(self.deleteLater) self.client.readyRead.connect(self.handle_message) def send_to_client(self, message: dict): if self.client.writeData(pickle.dumps(message)) == -1: print(self.errorString()) self.close() print("send", message) def handle_message(self): message = pickle.loads(self.client.read(5*10**7)) # max image size in 4K if message["type"] == "init": self.init_cam(message["data"]) if message["type"] == "read": self.read() if message["type"] == "stop": self.stop() def init_cam(self, cam_options): try: self.camgear = CamGear(**cam_options) except Exception as err: print("erro") self.camgear = None self.send_to_client({ "type": "init_success", "data": False }) print(err) return self.set_init_succeded() self.send_to_client({ "type": "init_success", "data": True }) def read(self): """Tries to read the frame ------ Emits frame with received_frame pyqtsignal """ if not self.camgear: # self.received_frame.emit(None) self.send_to_client({ "type": "frame", "data": None }) return try: frame = self.camgear.read() except Exception as err: self.send_to_client({ "type": "frame", "data": None }) print(err) return self.send_to_client({ "type": "frame", "data": frame }) def stop(self): if self.camgear: try: self.camgear.stop() except Exception as err: self.camgear = None print(err) self.close() if __name__ == "__main__": import sys if len(sys.argv) < 2: print("Socket's name weren't set", file=sys.stderr) sys.exit(1) app = QCoreApplication(sys.argv) try: server = _CamWorker(sys.argv[1], parent=None) except Exception as err: print(f"Unable to start the server: {err}") sys.exit(-1) if not server.isListening(): print(f"Unable to start the server: {server.errorString()}") sys.exit(-1) sys.exit(app.exec())both files are located in /home/user/Documents/project_dir/source/ dir
GUI main.py file is running from /home/user/Documents/project_dir/GUI/
output example:
ThreadedCamGear :: Trying init ThreadedCamGear with source: /home/user/Downloads/video.avi
1 QLocalSocket::connectToServer: Connection refused
Unknown error 234675467654745456456456
: ProcessError.Crashed
output of QProcess, if return in the middle of init_cam() is uncommented:
[Child process stdout] 123532056798672
[Child process stdout] listening
