pyobs-gui 2.0.0.dev18__tar.gz → 2.0.0.dev19__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/PKG-INFO +1 -1
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/videowidget.py +61 -10
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyproject.toml +1 -1
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/uv.lock +1 -1
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/.claude/skills/verify/SKILL.md +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/.github/dependabot.yml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/.github/workflows/pypi.yml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/.github/workflows/pyrefly.yml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/.github/workflows/ruff.yml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/.gitignore +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/.pre-commit-config.yaml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/.readthedocs.yml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/CHANGELOG.rst +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/LICENSE +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/README.md +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/bin/mypyuic5 +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/docs/Makefile +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/docs/make.bat +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/docs/requirements.txt +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/docs/source/_static/pyobs.gif +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/docs/source/conf.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/docs/source/index.rst +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/mypy.ini +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/__init__.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/__main__.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/_nuitka_astropy_patch.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/accounts.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/acquisitionwidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/autofocuswidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/autoguidingwidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/base.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/camerawidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/commandinputwidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/compassmovewidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/coolingwidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/datadisplaywidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/eventswidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/filterwidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/fitsheaderswidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/focuswidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/gui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/login.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/loginwindow.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/logmodel.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/mainwindow.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/modewidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/modifiedmixin.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/modulegui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/__init__.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/acquisitionwidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/acquisitionwidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/autofocuswidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/autofocuswidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/autoguidingwidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/autoguidingwidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/camerawidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/camerawidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/compassmoveplugin.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/compassmovewidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/compassmovewidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/compassmovewidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/compile.sh +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/coolingwidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/coolingwidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/datadisplayplugin.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/datadisplaywidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/datadisplaywidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/designer.sh +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/eventswidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/eventswidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/filterwidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/filterwidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/fitsheaderswidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/fitsheaderswidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/focuswidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/focuswidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/mainwindow.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/mainwindow_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/modewidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/modewidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/arrow-alt-circle-down-solid.svg +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/arrow-alt-circle-left-solid.svg +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/arrow-alt-circle-right-solid.svg +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/arrow-alt-circle-up-solid.svg +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/arrow-up-right-from-square-solid.svg +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/arrows-to-eye-solid.svg +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/chart-line-solid.svg +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/circle-question-solid.svg +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/cloud-sun-solid.svg +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/edit-solid.svg +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/search-solid.svg +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/undo-solid.svg +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources.qrc +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources_rc.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/roofwidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/roofwidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/shellwidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/shellwidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/spectrographwidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/spectrographwidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/telescopewidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/telescopewidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/temperaturesplotwidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/temperaturesplotwidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/temperatureswidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/temperatureswidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/videowidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/videowidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/weatherwidget.ui +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/weatherwidget_ui.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/roofwidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/shellwidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/spectrographwidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/statuswidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/telescopewidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/temperaturesplotwidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/temperatureswidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/utils.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/watchedlabel.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/weatherwidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pysidedeploy.spec +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/specs/2026-08-20-gui-remote-call-error-handling.md +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/specs/index.md +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/spike_standalone/main.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/spike_standalone/pysidedeploy.spec +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/test/acquisition.yaml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/test/autofocus.yaml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/test/camera.yaml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/test/full.yaml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/test/guiding.yaml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/test/mode.yaml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/test/roof.yaml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/test/spectrograph.yaml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/test/telescope.yaml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/test/telescope_acl.yaml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/test/telescope_acl_denied.yaml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/test/video.yaml +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/tests/__init__.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/tests/conftest.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/tests/test_accounts.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/tests/test_camerawidget.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/tests/test_loginwindow.py +0 -0
- {pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/tests/test_videowidget.py +0 -0
|
@@ -22,7 +22,6 @@ from PySide6 import QtCore, QtGui, QtNetwork, QtWidgets # type: ignore
|
|
|
22
22
|
from .base import BaseWidget
|
|
23
23
|
from .qt.videowidget_ui import Ui_VideoWidget
|
|
24
24
|
|
|
25
|
-
|
|
26
25
|
log = logging.getLogger(__name__)
|
|
27
26
|
|
|
28
27
|
|
|
@@ -75,9 +74,16 @@ class VideoWidget(BaseWidget, Ui_VideoWidget):
|
|
|
75
74
|
# init buffer
|
|
76
75
|
self.buffer = b""
|
|
77
76
|
|
|
78
|
-
# socket
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
# raw socket for the MJPEG stream; created once the video URL is known
|
|
78
|
+
# (see _init), because its type depends on the URL scheme -- https needs
|
|
79
|
+
# a TLS socket so the plaintext HTTP request survives a TLS-terminating
|
|
80
|
+
# reverse proxy (e.g. nginx)
|
|
81
|
+
self.socket: QtNetwork.QAbstractSocket | None = None
|
|
82
|
+
self.scheme: str | None = None
|
|
83
|
+
|
|
84
|
+
# whether the HTTP response headers of the current stream connection have
|
|
85
|
+
# been stripped from self.buffer yet (see _received_data)
|
|
86
|
+
self._headers_received = False
|
|
81
87
|
|
|
82
88
|
# set exposure types
|
|
83
89
|
image_types = sorted([it.name for it in ImageType])
|
|
@@ -137,7 +143,7 @@ class VideoWidget(BaseWidget, Ui_VideoWidget):
|
|
|
137
143
|
|
|
138
144
|
# parse URL
|
|
139
145
|
o = urlparse(video_file.url)
|
|
140
|
-
if o.scheme
|
|
146
|
+
if o.scheme not in ["http", "https"]:
|
|
141
147
|
log.error("URL scheme to video of module %s must be HTTP.", self.module)
|
|
142
148
|
return
|
|
143
149
|
|
|
@@ -145,8 +151,17 @@ class VideoWidget(BaseWidget, Ui_VideoWidget):
|
|
|
145
151
|
s = o.netloc.split(":")[:2]
|
|
146
152
|
self.host, self.port = s[0], int(s[1])
|
|
147
153
|
else:
|
|
148
|
-
self.host, self.port = (o.netloc, 80)
|
|
154
|
+
self.host, self.port = (o.netloc, 443 if o.scheme == "https" else 80)
|
|
149
155
|
self.path = o.path
|
|
156
|
+
self.scheme = o.scheme
|
|
157
|
+
|
|
158
|
+
# create the raw socket for the stream; https URLs need a TLS socket so
|
|
159
|
+
# the plaintext MJPEG request works through a TLS-terminating reverse
|
|
160
|
+
# proxy (a plain QTcpSocket can only reach a plain-HTTP endpoint)
|
|
161
|
+
self.socket = QtNetwork.QSslSocket() if self.scheme == "https" else QtNetwork.QTcpSocket()
|
|
162
|
+
self.socket.readyRead.connect(self._received_data)
|
|
163
|
+
if isinstance(self.socket, QtNetwork.QSslSocket):
|
|
164
|
+
self.socket.sslErrors.connect(self._on_ssl_errors)
|
|
150
165
|
|
|
151
166
|
# subscribe to state
|
|
152
167
|
if has_exposure_time:
|
|
@@ -173,19 +188,38 @@ class VideoWidget(BaseWidget, Ui_VideoWidget):
|
|
|
173
188
|
await BaseWidget._showEvent(self, event)
|
|
174
189
|
|
|
175
190
|
# connect socket
|
|
176
|
-
if self.host is not None and self.port is not None and self.path is not None:
|
|
177
|
-
self.
|
|
191
|
+
if self.host is not None and self.port is not None and self.path is not None and self.socket is not None:
|
|
192
|
+
if self.scheme == "https":
|
|
193
|
+
if not isinstance(self.socket, QtNetwork.QSslSocket):
|
|
194
|
+
log.error("Video URL of %s is https but no TLS socket was created.", self.module)
|
|
195
|
+
return
|
|
196
|
+
self.socket.connectToHostEncrypted(self.host, self.port)
|
|
197
|
+
else:
|
|
198
|
+
self.socket.connectToHost(self.host, self.port)
|
|
178
199
|
host_header = self.host if self.port == 80 else f"{self.host}:{self.port}"
|
|
200
|
+
# HTTP/1.0 on purpose: HTTP/1.1 responses to an endless MJPEG stream use chunked
|
|
201
|
+
# transfer encoding, and this raw-socket parser doesn't decode chunk framing --
|
|
202
|
+
# the hex chunk-size lines would land inside the JPEG data and corrupt every
|
|
203
|
+
# frame they fall in. HTTP/1.0 forbids chunked encoding, so the body arrives
|
|
204
|
+
# as the plain MJPEG byte stream (with Connection: close, which just means the
|
|
205
|
+
# stream runs until the client disconnects).
|
|
179
206
|
self.socket.write(
|
|
180
|
-
b"GET %s HTTP/1.
|
|
207
|
+
b"GET %s HTTP/1.0\r\nHost: %s\r\n\r\n" % (bytes(self.path, "UTF-8"), bytes(host_header, "UTF-8"))
|
|
181
208
|
)
|
|
209
|
+
# new connection: the next bytes start with the HTTP response headers
|
|
210
|
+
self._headers_received = False
|
|
211
|
+
|
|
212
|
+
def _on_ssl_errors(self, errors: list) -> None:
|
|
213
|
+
"""Log SSL errors from the video-stream socket instead of failing silently."""
|
|
214
|
+
log.error("SSL errors connecting to video stream of %s: %s", self.module, errors)
|
|
182
215
|
|
|
183
216
|
def hideEvent(self, event: QtGui.QHideEvent) -> None:
|
|
184
217
|
# call base
|
|
185
218
|
BaseWidget.hideEvent(self, event)
|
|
186
219
|
|
|
187
220
|
# disconnect socket
|
|
188
|
-
self.socket
|
|
221
|
+
if self.socket is not None:
|
|
222
|
+
self.socket.disconnectFromHost()
|
|
189
223
|
|
|
190
224
|
def update_gui(self) -> None:
|
|
191
225
|
"""Update the GUI."""
|
|
@@ -203,8 +237,21 @@ class VideoWidget(BaseWidget, Ui_VideoWidget):
|
|
|
203
237
|
self.labelExposuresLeft.setText("")
|
|
204
238
|
|
|
205
239
|
def _received_data(self) -> None:
|
|
240
|
+
if self.socket is None:
|
|
241
|
+
return
|
|
206
242
|
boundary = b"--jpgboundary\r\n"
|
|
207
243
|
self.buffer += bytes(self.socket.readAll())
|
|
244
|
+
|
|
245
|
+
# strip the HTTP response headers before parsing the MJPEG body -- the
|
|
246
|
+
# boundary string also appears in the "Content-Type: ... boundary=--jpgboundary"
|
|
247
|
+
# header line, so parsing it as a frame would produce garbage
|
|
248
|
+
if not self._headers_received:
|
|
249
|
+
pos = self.buffer.find(b"\r\n\r\n")
|
|
250
|
+
if pos == -1:
|
|
251
|
+
return # headers not complete yet
|
|
252
|
+
self.buffer = self.buffer[pos + 4 :]
|
|
253
|
+
self._headers_received = True
|
|
254
|
+
|
|
208
255
|
while boundary in self.buffer:
|
|
209
256
|
# find boundary
|
|
210
257
|
pos = self.buffer.find(boundary)
|
|
@@ -217,6 +264,10 @@ class VideoWidget(BaseWidget, Ui_VideoWidget):
|
|
|
217
264
|
|
|
218
265
|
# find end of frame
|
|
219
266
|
image_data = frame[frame.find(b"\r\n\r\n") + 4 :]
|
|
267
|
+
if not image_data:
|
|
268
|
+
# preamble before the first boundary (e.g. HTTP response headers)
|
|
269
|
+
# or a frame with no payload yet -- nothing to show
|
|
270
|
+
continue
|
|
220
271
|
|
|
221
272
|
# to pixmap and show it
|
|
222
273
|
qp = QtGui.QPixmap()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/arrow-alt-circle-up-solid.svg
RENAMED
|
File without changes
|
|
File without changes
|
{pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/arrows-to-eye-solid.svg
RENAMED
|
File without changes
|
|
File without changes
|
{pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/pyobs_gui/qt/resources/circle-question-solid.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyobs_gui-2.0.0.dev18 → pyobs_gui-2.0.0.dev19}/specs/2026-08-20-gui-remote-call-error-handling.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|