From ebd56c98d83cd213cbd76a142c0768df822e4202 Mon Sep 17 00:00:00 2001 From: Sven337 Date: Wed, 14 Aug 2024 22:47:54 +0200 Subject: [PATCH 1/6] stream gui start --- examples/stream_gui.py | 97 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 examples/stream_gui.py diff --git a/examples/stream_gui.py b/examples/stream_gui.py new file mode 100644 index 0000000..548faef --- /dev/null +++ b/examples/stream_gui.py @@ -0,0 +1,97 @@ +import sys +import os +from configparser import RawConfigParser +from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QSlider, QLabel +from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput +from PyQt6.QtMultimediaWidgets import QVideoWidget +from PyQt6.QtCore import Qt, QUrl + +def read_config(props_path: str) -> dict: + config = RawConfigParser() + assert os.path.exists(props_path), f"Path does not exist: {props_path}" + config.read(props_path) + return config + +class ZoomSlider(QSlider): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def keyPressEvent(self, event): + if event.key() in (Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up, Qt.Key.Key_Down): + event.ignore() + else: + super().keyPressEvent(event) + +class CameraPlayer(QWidget): + def __init__(self, rtsp_url): + super().__init__() + self.setWindowTitle("Camera Player") + self.setGeometry(10, 10, 1900, 1150) + self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + + + # Create media player + self.media_player = QMediaPlayer() + self.audio_output = QAudioOutput() + self.media_player.setAudioOutput(self.audio_output) + self.media_player.errorOccurred.connect(self.handle_error) + + # Create video widget + self.video_widget = QVideoWidget() + self.media_player.setVideoOutput(self.video_widget) + + # Create zoom slider + self.zoom_slider = ZoomSlider(Qt.Orientation.Vertical) + self.zoom_slider.setRange(0, 100) + self.zoom_slider.setValue(0) + self.zoom_slider.valueChanged.connect(self.set_zoom) + + # Create layout + layout = QVBoxLayout() + video_layout = QHBoxLayout() + video_layout.addWidget(self.video_widget, 4) + video_layout.addWidget(self.zoom_slider, 1) + layout.addLayout(video_layout) + self.setLayout(layout) + + # Set source and start playing + self.media_player.setSource(QUrl(rtsp_url)) + self.media_player.play() + + + def keyPressEvent(self, event): + if event.key() == Qt.Key.Key_Escape: + self.close() + elif event.key() == Qt.Key.Key_Left: + self.move_camera("left") + elif event.key() == Qt.Key.Key_Right: + self.move_camera("right") + elif event.key() == Qt.Key.Key_Up: + self.move_camera("up") + elif event.key() == Qt.Key.Key_Down: + self.move_camera("down") + + def move_camera(self, direction): + print(f"Moving camera {direction}") + + def set_zoom(self, value): + print(f"Setting zoom to {value}") + + def handle_error(self, error): + print(f"Media player error: {error}") + + +if __name__ == '__main__': + # Read in your ip, username, & password from the configuration file + config = read_config('camera.cfg') + ip = config.get('camera', 'ip') + un = config.get('camera', 'username') + pw = config.get('camera', 'password') + + rtsp_url = f"rtsp://{un}:{pw}@{ip}/h264Preview_01_main" + print(f"Connecting to: {rtsp_url}") + + app = QApplication(sys.argv) + player = CameraPlayer(rtsp_url) + player.show() + sys.exit(app.exec()) From 1492e21437940dd415402cefe872cb293db86ffd Mon Sep 17 00:00:00 2001 From: Sven337 Date: Wed, 14 Aug 2024 23:06:25 +0200 Subject: [PATCH 2/6] initial video stream gui with PTZ -- pan works, zoom broken --- examples/stream_gui.py | 54 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/examples/stream_gui.py b/examples/stream_gui.py index 548faef..7e779ef 100644 --- a/examples/stream_gui.py +++ b/examples/stream_gui.py @@ -1,10 +1,11 @@ import sys import os from configparser import RawConfigParser -from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QSlider, QLabel +from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QSlider from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput from PyQt6.QtMultimediaWidgets import QVideoWidget from PyQt6.QtCore import Qt, QUrl +from reolinkapi import Camera def read_config(props_path: str) -> dict: config = RawConfigParser() @@ -23,18 +24,20 @@ def keyPressEvent(self, event): super().keyPressEvent(event) class CameraPlayer(QWidget): - def __init__(self, rtsp_url): + def __init__(self, rtsp_url, camera: Camera): super().__init__() self.setWindowTitle("Camera Player") self.setGeometry(10, 10, 1900, 1150) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + self.camera = camera # Create media player self.media_player = QMediaPlayer() self.audio_output = QAudioOutput() self.media_player.setAudioOutput(self.audio_output) self.media_player.errorOccurred.connect(self.handle_error) + self.media_player.setSource(QUrl(rtsp_url)) # Create video widget self.video_widget = QVideoWidget() @@ -72,10 +75,45 @@ def keyPressEvent(self, event): self.move_camera("down") def move_camera(self, direction): - print(f"Moving camera {direction}") + speed = 25 # You can adjust this value as needed + try: + if direction == "left": + response = self.camera.move_left(speed) + elif direction == "right": + response = self.camera.move_right(speed) + elif direction == "up": + response = self.camera.move_up(speed) + elif direction == "down": + response = self.camera.move_down(speed) + else: + print(f"Invalid direction: {direction}") + return + + response = response[0] + if response.get('code') == 0: + print(f"Successfully moved camera {direction}") + else: + self.show_error_message(f"Failed to move camera {direction}", response.get('msg', 'Unknown error')) + except Exception as e: + self.show_error_message(f"Error moving camera {direction}", str(e)) def set_zoom(self, value): - print(f"Setting zoom to {value}") + try: + zoom_speed = value / 2 # Adjust this factor as needed + if value > self.zoom_slider.value(): + response = self.camera._send_operation('ZoomInc', speed=zoom_speed) + else: + response = self.camera._send_operation('ZoomDec', speed=zoom_speed) + + if response.get('code') == 0: + print(f"Successfully set zoom to {value}") + else: + self.show_error_message("Failed to set zoom", response.get('msg', 'Unknown error')) + except Exception as e: + self.show_error_message("Error setting zoom", str(e)) + + def show_error_message(self, title, message): + print(f"Error: {title} {message}") def handle_error(self, error): print(f"Media player error: {error}") @@ -88,10 +126,12 @@ def handle_error(self, error): un = config.get('camera', 'username') pw = config.get('camera', 'password') - rtsp_url = f"rtsp://{un}:{pw}@{ip}/h264Preview_01_main" - print(f"Connecting to: {rtsp_url}") + # Connect to camera + cam = Camera(ip, un, pw, https=True) + + rtsp_url = f"rtsp://{un}:{pw}@{ip}/h264Preview_01_sub" app = QApplication(sys.argv) - player = CameraPlayer(rtsp_url) + player = CameraPlayer(rtsp_url, cam) player.show() sys.exit(app.exec()) From 772e76c0b0a3f5dd80322c7f656da86de88ed78f Mon Sep 17 00:00:00 2001 From: Sven337 Date: Wed, 14 Aug 2024 23:38:16 +0200 Subject: [PATCH 3/6] stream gui: pan and zoom work, needs refinement --- examples/stream_gui.py | 111 ++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 45 deletions(-) diff --git a/examples/stream_gui.py b/examples/stream_gui.py index 7e779ef..53bad64 100644 --- a/examples/stream_gui.py +++ b/examples/stream_gui.py @@ -5,7 +5,10 @@ from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput from PyQt6.QtMultimediaWidgets import QVideoWidget from PyQt6.QtCore import Qt, QUrl +from PyQt6.QtGui import QWheelEvent from reolinkapi import Camera +from threading import Lock + def read_config(props_path: str) -> dict: config = RawConfigParser() @@ -24,43 +27,43 @@ def keyPressEvent(self, event): super().keyPressEvent(event) class CameraPlayer(QWidget): - def __init__(self, rtsp_url, camera: Camera): + def __init__(self, rtsp_url_wide, rtsp_url_telephoto, camera: Camera): super().__init__() self.setWindowTitle("Camera Player") self.setGeometry(10, 10, 1900, 1150) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.camera = camera + self.move_lock = Lock() + self.move_in_progress = False - # Create media player - self.media_player = QMediaPlayer() - self.audio_output = QAudioOutput() - self.media_player.setAudioOutput(self.audio_output) - self.media_player.errorOccurred.connect(self.handle_error) - self.media_player.setSource(QUrl(rtsp_url)) - - # Create video widget - self.video_widget = QVideoWidget() - self.media_player.setVideoOutput(self.video_widget) + # Create media players + self.media_player_wide = QMediaPlayer() + self.media_player_telephoto = QMediaPlayer() - # Create zoom slider - self.zoom_slider = ZoomSlider(Qt.Orientation.Vertical) - self.zoom_slider.setRange(0, 100) - self.zoom_slider.setValue(0) - self.zoom_slider.valueChanged.connect(self.set_zoom) + # Create video widgets + self.video_widget_wide = QVideoWidget() + self.video_widget_telephoto = QVideoWidget() + self.media_player_wide.setVideoOutput(self.video_widget_wide) + self.media_player_telephoto.setVideoOutput(self.video_widget_telephoto) + self.video_widget_wide.wheelEvent = self.handle_wheel_event + self.video_widget_telephoto.wheelEvent = self.handle_wheel_event # Create layout - layout = QVBoxLayout() - video_layout = QHBoxLayout() - video_layout.addWidget(self.video_widget, 4) - video_layout.addWidget(self.zoom_slider, 1) - layout.addLayout(video_layout) + layout = QHBoxLayout() + layout.addWidget(self.video_widget_wide, 2) + layout.addWidget(self.video_widget_telephoto, 2) self.setLayout(layout) - # Set source and start playing - self.media_player.setSource(QUrl(rtsp_url)) - self.media_player.play() + # Start playing the streams + self.media_player_wide.setSource(QUrl(rtsp_url_wide)) + self.media_player_telephoto.setSource(QUrl(rtsp_url_telephoto)) + self.media_player_wide.play() + self.media_player_telephoto.play() + def is_move_in_progress(self): + with self.move_lock: + return self.move_in_progress def keyPressEvent(self, event): if event.key() == Qt.Key.Key_Escape: @@ -75,8 +78,15 @@ def keyPressEvent(self, event): self.move_camera("down") def move_camera(self, direction): - speed = 25 # You can adjust this value as needed + if self.is_move_in_progress(): + print(f"Move already in progress, ignoring {direction} command") + return + + speed = 25 try: + with self.move_lock: + self.move_in_progress = True + if direction == "left": response = self.camera.move_left(speed) elif direction == "right": @@ -89,28 +99,35 @@ def move_camera(self, direction): print(f"Invalid direction: {direction}") return - response = response[0] - if response.get('code') == 0: - print(f"Successfully moved camera {direction}") + if response[0].get('code') == 0: + print(f"Successfully started moving camera {direction}") else: - self.show_error_message(f"Failed to move camera {direction}", response.get('msg', 'Unknown error')) - except Exception as e: - self.show_error_message(f"Error moving camera {direction}", str(e)) + self.show_error_message(f"Failed to move camera {direction}", response[0].get('msg', 'Unknown error')) - def set_zoom(self, value): - try: - zoom_speed = value / 2 # Adjust this factor as needed - if value > self.zoom_slider.value(): - response = self.camera._send_operation('ZoomInc', speed=zoom_speed) - else: - response = self.camera._send_operation('ZoomDec', speed=zoom_speed) + # Immediately stop the camera movement + stop_response = self.camera.stop_ptz() + if stop_response[0].get('code') != 0: + self.show_error_message("Failed to stop camera movement", stop_response[0].get('msg', 'Unknown error')) - if response.get('code') == 0: - print(f"Successfully set zoom to {value}") - else: - self.show_error_message("Failed to set zoom", response.get('msg', 'Unknown error')) except Exception as e: - self.show_error_message("Error setting zoom", str(e)) + self.show_error_message(f"Error moving camera {direction}", str(e)) + finally: + with self.move_lock: + self.move_in_progress = False + + def handle_wheel_event(self, event: QWheelEvent): + delta = event.angleDelta().y() + if delta > 0: + self.change_zoom(zoom_in = True) + elif delta < 0: + self.change_zoom(zoom_in = False) + + def change_zoom(self, zoom_in): + zoom_speed = 10 + cmd = 'ZoomInc' if zoom_in else 'ZoomDec' + response = self.camera._send_operation(cmd, speed=zoom_speed) + response = response[0] + print(str(response)) def show_error_message(self, title, message): print(f"Error: {title} {message}") @@ -129,9 +146,13 @@ def handle_error(self, error): # Connect to camera cam = Camera(ip, un, pw, https=True) - rtsp_url = f"rtsp://{un}:{pw}@{ip}/h264Preview_01_sub" + rtsp_url_wide = f"rtsp://{un}:{pw}@{ip}/Preview_01_sub" + rtsp_url_telephoto = f"rtsp://{un}:{pw}@{ip}/Preview_02_sub" + + # Connect to camera + cam = Camera(ip, un, pw, https=True) app = QApplication(sys.argv) - player = CameraPlayer(rtsp_url, cam) + player = CameraPlayer(rtsp_url_wide, rtsp_url_telephoto, cam) player.show() sys.exit(app.exec()) From be57c67048eab43933c932632de4e28690d5583b Mon Sep 17 00:00:00 2001 From: Sven337 Date: Thu, 15 Aug 2024 13:02:54 +0200 Subject: [PATCH 4/6] stream gui: some improvements .latency still terrible and key repeat is a problem --- examples/stream_gui.py | 98 +++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/examples/stream_gui.py b/examples/stream_gui.py index 53bad64..94274d1 100644 --- a/examples/stream_gui.py +++ b/examples/stream_gui.py @@ -4,11 +4,13 @@ from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QSlider from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput from PyQt6.QtMultimediaWidgets import QVideoWidget -from PyQt6.QtCore import Qt, QUrl +from PyQt6.QtCore import Qt, QUrl, QTimer from PyQt6.QtGui import QWheelEvent from reolinkapi import Camera from threading import Lock +import urllib3 +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def read_config(props_path: str) -> dict: config = RawConfigParser() @@ -34,8 +36,9 @@ def __init__(self, rtsp_url_wide, rtsp_url_telephoto, camera: Camera): self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.camera = camera - self.move_lock = Lock() - self.move_in_progress = False + self.zoom_timer = QTimer(self) + self.zoom_timer.timeout.connect(self.stop_zoom) + self.move_timer = QTimer(self) # Create media players self.media_player_wide = QMediaPlayer() @@ -61,32 +64,39 @@ def __init__(self, rtsp_url_wide, rtsp_url_telephoto, camera: Camera): self.media_player_wide.play() self.media_player_telephoto.play() - def is_move_in_progress(self): - with self.move_lock: - return self.move_in_progress def keyPressEvent(self, event): if event.key() == Qt.Key.Key_Escape: self.close() - elif event.key() == Qt.Key.Key_Left: - self.move_camera("left") - elif event.key() == Qt.Key.Key_Right: - self.move_camera("right") - elif event.key() == Qt.Key.Key_Up: - self.move_camera("up") - elif event.key() == Qt.Key.Key_Down: - self.move_camera("down") + elif event.key() in (Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up, Qt.Key.Key_Down): + print("Arrow key pressed") + self.start_move(event.key()) - def move_camera(self, direction): - if self.is_move_in_progress(): - print(f"Move already in progress, ignoring {direction} command") - return + def keyReleaseEvent(self, event): + if event.key() in (Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up, Qt.Key.Key_Down): + self.stop_move() + + def start_move(self, key): + direction = { + Qt.Key.Key_Left: "left", + Qt.Key.Key_Right: "right", + Qt.Key.Key_Up: "up", + Qt.Key.Key_Down: "down" + }.get(key) + + if direction: + self.move_camera(direction) + + def stop_move(self): + self.move_timer.stop() + response = self.camera.stop_ptz() + print("Stop PTZ") + if response[0].get('code') != 0: + self.show_error_message("Failed to stop camera movement", response[0].get('msg', 'Unknown error')) + def move_camera(self, direction): speed = 25 try: - with self.move_lock: - self.move_in_progress = True - if direction == "left": response = self.camera.move_left(speed) elif direction == "right": @@ -100,34 +110,44 @@ def move_camera(self, direction): return if response[0].get('code') == 0: - print(f"Successfully started moving camera {direction}") + print(f"Moving camera {direction}") else: self.show_error_message(f"Failed to move camera {direction}", response[0].get('msg', 'Unknown error')) - # Immediately stop the camera movement - stop_response = self.camera.stop_ptz() - if stop_response[0].get('code') != 0: - self.show_error_message("Failed to stop camera movement", stop_response[0].get('msg', 'Unknown error')) - except Exception as e: self.show_error_message(f"Error moving camera {direction}", str(e)) - finally: - with self.move_lock: - self.move_in_progress = False def handle_wheel_event(self, event: QWheelEvent): delta = event.angleDelta().y() if delta > 0: - self.change_zoom(zoom_in = True) + self.zoom_in() elif delta < 0: - self.change_zoom(zoom_in = False) - - def change_zoom(self, zoom_in): - zoom_speed = 10 - cmd = 'ZoomInc' if zoom_in else 'ZoomDec' - response = self.camera._send_operation(cmd, speed=zoom_speed) - response = response[0] - print(str(response)) + self.zoom_out() + + def zoom_in(self): + self.start_zoom('in') + + def zoom_out(self): + self.start_zoom('out') + + def start_zoom(self, direction: str): + self.zoom_timer.stop() # Stop any ongoing zoom timer + speed = 60 # You can adjust this value as needed + if direction == 'in': + response = self.camera.start_zooming_in(speed) + else: + response = self.camera.start_zooming_out(speed) + + if response[0].get('code') == 0: + print(f"Started zooming {direction}") + self.zoom_timer.start(200) # Stop zooming after 200ms + else: + self.show_error_message(f"Failed to start zooming {direction}", response[0].get('msg', 'Unknown error')) + + def stop_zoom(self): + response = self.camera.stop_zooming() + if response[0].get('code') != 0: + self.show_error_message("Failed to stop zooming", response[0].get('msg', 'Unknown error')) def show_error_message(self, title, message): print(f"Error: {title} {message}") From 6a344569ee5bd70181a297f253ca955ef2733f2d Mon Sep 17 00:00:00 2001 From: Sven337 Date: Thu, 15 Aug 2024 14:37:41 +0200 Subject: [PATCH 5/6] stream gui: fix autorepeat messing up commands Includes minor cleanups for publication --- examples/stream_gui.py | 55 ++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/examples/stream_gui.py b/examples/stream_gui.py index 94274d1..45607b3 100644 --- a/examples/stream_gui.py +++ b/examples/stream_gui.py @@ -31,14 +31,13 @@ def keyPressEvent(self, event): class CameraPlayer(QWidget): def __init__(self, rtsp_url_wide, rtsp_url_telephoto, camera: Camera): super().__init__() - self.setWindowTitle("Camera Player") - self.setGeometry(10, 10, 1900, 1150) + self.setWindowTitle("Reolink PTZ Streamer") + self.setGeometry(10, 10, 1900, 600) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.camera = camera self.zoom_timer = QTimer(self) self.zoom_timer.timeout.connect(self.stop_zoom) - self.move_timer = QTimer(self) # Create media players self.media_player_wide = QMediaPlayer() @@ -66,13 +65,16 @@ def __init__(self, rtsp_url_wide, rtsp_url_telephoto, camera: Camera): def keyPressEvent(self, event): + if event.isAutoRepeat(): + return if event.key() == Qt.Key.Key_Escape: self.close() elif event.key() in (Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up, Qt.Key.Key_Down): - print("Arrow key pressed") self.start_move(event.key()) def keyReleaseEvent(self, event): + if event.isAutoRepeat(): + return if event.key() in (Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up, Qt.Key.Key_Down): self.stop_move() @@ -88,34 +90,29 @@ def start_move(self, key): self.move_camera(direction) def stop_move(self): - self.move_timer.stop() response = self.camera.stop_ptz() print("Stop PTZ") if response[0].get('code') != 0: - self.show_error_message("Failed to stop camera movement", response[0].get('msg', 'Unknown error')) + self.show_error_message("Failed to stop camera movement", str(response[0])) def move_camera(self, direction): speed = 25 - try: - if direction == "left": - response = self.camera.move_left(speed) - elif direction == "right": - response = self.camera.move_right(speed) - elif direction == "up": - response = self.camera.move_up(speed) - elif direction == "down": - response = self.camera.move_down(speed) - else: - print(f"Invalid direction: {direction}") - return - - if response[0].get('code') == 0: - print(f"Moving camera {direction}") - else: - self.show_error_message(f"Failed to move camera {direction}", response[0].get('msg', 'Unknown error')) - - except Exception as e: - self.show_error_message(f"Error moving camera {direction}", str(e)) + if direction == "left": + response = self.camera.move_left(speed) + elif direction == "right": + response = self.camera.move_right(speed) + elif direction == "up": + response = self.camera.move_up(speed) + elif direction == "down": + response = self.camera.move_down(speed) + else: + print(f"Invalid direction: {direction}") + return + + if response[0].get('code') == 0: + print(f"Moving camera {direction}") + else: + self.show_error_message(f"Failed to move camera {direction}", str(response[0])) def handle_wheel_event(self, event: QWheelEvent): delta = event.angleDelta().y() @@ -139,15 +136,15 @@ def start_zoom(self, direction: str): response = self.camera.start_zooming_out(speed) if response[0].get('code') == 0: - print(f"Started zooming {direction}") + print(f"Zooming {direction}") self.zoom_timer.start(200) # Stop zooming after 200ms else: - self.show_error_message(f"Failed to start zooming {direction}", response[0].get('msg', 'Unknown error')) + self.show_error_message(f"Failed to start zooming {direction}", str(response[0])) def stop_zoom(self): response = self.camera.stop_zooming() if response[0].get('code') != 0: - self.show_error_message("Failed to stop zooming", response[0].get('msg', 'Unknown error')) + self.show_error_message("Failed to stop zooming", str(response[0])) def show_error_message(self, title, message): print(f"Error: {title} {message}") From 0a3df2c8042759da5daf56e959a19ecfc269bb1b Mon Sep 17 00:00:00 2001 From: Sven337 Date: Thu, 15 Aug 2024 14:47:31 +0200 Subject: [PATCH 6/6] video_review_gui: disable SSL warnings The camera uses a self-signed certificate so prevent the annoying spamming. --- examples/video_review_gui.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/video_review_gui.py b/examples/video_review_gui.py index 369ee8b..93a1c36 100644 --- a/examples/video_review_gui.py +++ b/examples/video_review_gui.py @@ -17,6 +17,9 @@ from PyQt6.QtCore import Qt, QUrl, QTimer, QThread, pyqtSignal, QMutex from PyQt6.QtGui import QColor, QBrush, QFont +import urllib3 +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + def path_name_from_camera_path(fname): # Mp4Record/2024-08-12/RecM13_DST20240812_214255_214348_1F1E828_4DDA4D.mp4 return fname.replace('/', '_')