Browse Source

add menu action to show rudimentary window with webcam image updating repeatedly

Thomas Buck 3 years ago
parent
commit
4f6e1f3817
1 changed files with 152 additions and 10 deletions
  1. 152
    10
      octotray

+ 152
- 10
octotray View File

@@ -6,6 +6,10 @@
6 6
 # - python-pyqt5
7 7
 # - curl
8 8
 # - xdg-open
9
+#
10
+# see also:
11
+# https://doc.qt.io/qt-5/qtwidgets-widgets-imageviewer-example.html
12
+# https://stackoverflow.com/a/22618496
9 13
 
10 14
 import json
11 15
 import subprocess
@@ -13,15 +17,127 @@ import sys
13 17
 import os
14 18
 import threading
15 19
 import time
16
-from PyQt5 import QtWidgets, QtGui, QtCore
17
-from PyQt5.QtWidgets import QSystemTrayIcon, QAction, QMenu, QMessageBox
18
-from PyQt5.QtGui import QIcon, QPixmap
19
-from PyQt5.QtCore import QCoreApplication, QSettings
20
+from PyQt5 import QtWidgets, QtGui, QtCore, QtNetwork
21
+from PyQt5.QtWidgets import QSystemTrayIcon, QAction, QMenu, QMessageBox, QWidget, QLabel, QVBoxLayout, QHBoxLayout, QDesktopWidget, QSizePolicy, QSlider, QLayout
22
+from PyQt5.QtGui import QIcon, QPixmap, QImageReader
23
+from PyQt5.QtCore import QCoreApplication, QSettings, QUrl, QTimer, QSize, Qt
24
+
25
+class AspectRatioPixmapLabel(QLabel):
26
+    def __init__(self, *args, **kwargs):
27
+        super(AspectRatioPixmapLabel, self).__init__(*args, **kwargs)
28
+        self.setMinimumSize(1, 1)
29
+        self.setScaledContents(False)
30
+        self.pix = QPixmap(0, 0)
31
+
32
+    def setPixmap(self, p):
33
+        self.pix = p
34
+        super(AspectRatioPixmapLabel, self).setPixmap(self.scaledPixmap())
35
+
36
+    def heightForWidth(self, width):
37
+        if self.pix.isNull():
38
+            return self.height()
39
+        else:
40
+            return (self.pix.height() * width) / self.pix.width()
41
+
42
+    def sizeHint(self):
43
+        w = self.width()
44
+        return QSize(int(w), int(self.heightForWidth(w)))
45
+
46
+    def scaledPixmap(self):
47
+        return self.pix.scaled(self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
48
+
49
+    def resizeEvent(self, e):
50
+        if not self.pix.isNull():
51
+            super(AspectRatioPixmapLabel, self).setPixmap(self.scaledPixmap())
52
+
53
+class CamWindow(QWidget):
54
+    reloadDelayDefault = 1000 # in ms
55
+    addSize = 100
56
+    reloadOn = True
57
+
58
+    def __init__(self, parent, name, icon, app, manager, host, *args, **kwargs):
59
+        super(CamWindow, self).__init__(*args, **kwargs)
60
+        self.url = "http://" + host + ":8080/?action=snapshot"
61
+
62
+        self.app = app
63
+        self.parent = parent
64
+        self.manager = manager
65
+        self.manager.finished.connect(self.handleResponse)
66
+
67
+        self.setWindowTitle(name + " Webcam Stream")
68
+        self.setWindowIcon(icon)
69
+
70
+        box = QVBoxLayout()
71
+        self.setLayout(box)
72
+
73
+        label = QLabel(self.url)
74
+        box.addWidget(label, 0)
75
+        box.setAlignment(label, Qt.AlignHCenter)
76
+
77
+        self.img = AspectRatioPixmapLabel()
78
+        self.img.setPixmap(QPixmap(640, 480))
79
+        box.addWidget(self.img, 1)
80
+
81
+        slide = QHBoxLayout()
82
+        box.addLayout(slide, 0)
83
+
84
+        self.slider = QSlider(Qt.Horizontal)
85
+        self.slider.setMinimum(0)
86
+        self.slider.setMaximum(2000)
87
+        self.slider.setTickInterval(100)
88
+        self.slider.setPageStep(100)
89
+        self.slider.setSingleStep(100)
90
+        self.slider.setTickPosition(QSlider.TicksBelow)
91
+        self.slider.setValue(self.reloadDelayDefault)
92
+        self.slider.valueChanged.connect(self.sliderChanged)
93
+        slide.addWidget(self.slider, 1)
94
+
95
+        self.slideLabel = QLabel(str(self.reloadDelayDefault) + "ms")
96
+        slide.addWidget(self.slideLabel, 0)
97
+
98
+        size = self.size()
99
+        size.setHeight(size.height() + self.addSize)
100
+        self.resize(size)
101
+
102
+        self.loadImage()
103
+
104
+    def sliderChanged(self):
105
+        self.slideLabel.setText(str(self.slider.value()) + "ms")
106
+
107
+    def closeEvent(self, event):
108
+        self.reloadOn = False
109
+        self.url = ""
110
+        self.parent.removeWebcamWindow(self)
111
+
112
+    def scheduleLoad(self):
113
+        if self.reloadOn:
114
+            QTimer.singleShot(self.slider.value(), self.loadImage)
115
+
116
+    def loadImage(self):
117
+        url = QUrl(self.url)
118
+        request = QtNetwork.QNetworkRequest(url)
119
+        self.manager.get(request)
120
+
121
+    def handleResponse(self, reply):
122
+        if reply.url().url() == self.url:
123
+            if reply.error() == QtNetwork.QNetworkReply.NoError:
124
+                reader = QImageReader(reply)
125
+                reader.setAutoTransform(True)
126
+                image = reader.read()
127
+                if image != None:
128
+                    if image.colorSpace().isValid():
129
+                        image.convertToColorSpace(QColorSpace.SRgb)
130
+                    self.img.setPixmap(QPixmap.fromImage(image))
131
+                    self.scheduleLoad()
132
+                else:
133
+                    print("Error decoding image: " + reader.errorString())
134
+            else:
135
+                print("Error loading image: " + reply.errorString())
20 136
 
21 137
 class OctoTray():
22 138
     name = "OctoTray"
23 139
     vendor = "xythobuz"
24
-    version = "0.1"
140
+    version = "0.2"
25 141
 
26 142
     iconPath = "/usr/share/pixmaps/"
27 143
     iconName = "octotray_icon.png"
@@ -36,14 +152,18 @@ class OctoTray():
36 152
         "Printing", "Pausing", "Paused"
37 153
     ]
38 154
 
155
+    camWindows = []
156
+
39 157
     def __init__(self):
40
-        app = QtWidgets.QApplication(sys.argv)
158
+        self.app = QtWidgets.QApplication(sys.argv)
41 159
         QCoreApplication.setApplicationName(self.name)
42 160
 
43 161
         if not QSystemTrayIcon.isSystemTrayAvailable():
44 162
             print("System Tray is not available on this platform!")
45 163
             sys.exit(0)
46 164
 
165
+        self.manager = QtNetwork.QNetworkAccessManager()
166
+
47 167
         self.menu = QMenu()
48 168
 
49 169
         for p in self.printers:
@@ -72,6 +192,11 @@ class OctoTray():
72 192
             p.append(action)
73 193
             menu.addAction(action)
74 194
 
195
+            action = QAction("Show Webcam")
196
+            action.triggered.connect(lambda chk, x=p: self.printerWebcamAction(x))
197
+            p.append(action)
198
+            menu.addAction(action)
199
+
75 200
             action = QAction("Open Web UI")
76 201
             action.triggered.connect(lambda chk, x=p: self.printerWebAction(x))
77 202
             p.append(action)
@@ -89,18 +214,18 @@ class OctoTray():
89 214
         else:
90 215
             print("no icon found")
91 216
 
92
-        icon = QIcon()
217
+        self.icon = QIcon()
93 218
         if iconPathName != "":
94 219
             pic = QPixmap(32, 32)
95 220
             pic.load(iconPathName)
96
-            icon = QIcon(pic)
221
+            self.icon = QIcon(pic)
97 222
 
98
-        trayIcon = QSystemTrayIcon(icon)
223
+        trayIcon = QSystemTrayIcon(self.icon)
99 224
         trayIcon.setToolTip(self.name + " " + self.version)
100 225
         trayIcon.setContextMenu(self.menu)
101 226
         trayIcon.setVisible(True)
102 227
 
103
-        sys.exit(app.exec_())
228
+        sys.exit(self.app.exec_())
104 229
 
105 230
     def openBrowser(self, url):
106 231
         os.system("xdg-open http://" + url)
@@ -256,5 +381,22 @@ class OctoTray():
256 381
             warning = True
257 382
         self.showDialog("OctoTray Status", s, None, False, warning)
258 383
 
384
+    def printerWebcamAction(self, item):
385
+        window = CamWindow(self, self.name, self.icon, self.app, self.manager, item[0])
386
+        self.camWindows.append(window)
387
+
388
+        screenGeometry = QDesktopWidget().screenGeometry()
389
+        width = screenGeometry.width()
390
+        height = screenGeometry.height()
391
+        x = (width - window.width()) / 2
392
+        y = (height - window.height()) / 2
393
+        window.setGeometry(int(x), int(y), int(window.width()), int(window.height()))
394
+
395
+        window.show()
396
+        window.activateWindow()
397
+
398
+    def removeWebcamWindow(self, window):
399
+        self.camWindows.remove(window)
400
+
259 401
 if __name__ == "__main__":
260 402
     tray = OctoTray()

Loading…
Cancel
Save