NetworkCamera.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. from UM.Logger import Logger
  2. from PyQt5.QtCore import QUrl, pyqtProperty, pyqtSignal, QObject, pyqtSlot
  3. from PyQt5.QtGui import QImage
  4. from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
  5. class NetworkCamera(QObject):
  6. newImage = pyqtSignal()
  7. def __init__(self, target = None, parent = None):
  8. super().__init__(parent)
  9. self._stream_buffer = b""
  10. self._stream_buffer_start_index = -1
  11. self._manager = None
  12. self._image_request = None
  13. self._image_reply = None
  14. self._image = QImage()
  15. self._image_id = 0
  16. self._target = target
  17. self._started = False
  18. @pyqtSlot(str)
  19. def setTarget(self, target):
  20. restart_required = False
  21. if self._started:
  22. self.stop()
  23. restart_required = True
  24. self._target = target
  25. if restart_required:
  26. self.start()
  27. @pyqtProperty(QUrl, notify=newImage)
  28. def latestImage(self):
  29. self._image_id += 1
  30. # There is an image provider that is called "camera". In order to ensure that the image qml object, that
  31. # requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
  32. # as new (instead of relying on cached version and thus forces an update.
  33. temp = "image://camera/" + str(self._image_id)
  34. return QUrl(temp, QUrl.TolerantMode)
  35. @pyqtSlot()
  36. def start(self):
  37. # Ensure that previous requests (if any) are stopped.
  38. self.stop()
  39. if self._target is None:
  40. Logger.log("w", "Unable to start camera stream without target!")
  41. return
  42. self._started = True
  43. url = QUrl(self._target)
  44. self._image_request = QNetworkRequest(url)
  45. if self._manager is None:
  46. self._manager = QNetworkAccessManager()
  47. self._image_reply = self._manager.get(self._image_request)
  48. self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
  49. @pyqtSlot()
  50. def stop(self):
  51. self._stream_buffer = b""
  52. self._stream_buffer_start_index = -1
  53. if self._image_reply:
  54. try:
  55. # disconnect the signal
  56. try:
  57. self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
  58. except Exception:
  59. pass
  60. # abort the request if it's not finished
  61. if not self._image_reply.isFinished():
  62. self._image_reply.close()
  63. except Exception as e: # RuntimeError
  64. pass # It can happen that the wrapped c++ object is already deleted.
  65. self._image_reply = None
  66. self._image_request = None
  67. self._manager = None
  68. self._started = False
  69. def getImage(self):
  70. return self._image
  71. ## Ensure that close gets called when object is destroyed
  72. def __del__(self):
  73. self.stop()
  74. def _onStreamDownloadProgress(self, bytes_received, bytes_total):
  75. # An MJPG stream is (for our purpose) a stream of concatenated JPG images.
  76. # JPG images start with the marker 0xFFD8, and end with 0xFFD9
  77. if self._image_reply is None:
  78. return
  79. self._stream_buffer += self._image_reply.readAll()
  80. if len(self._stream_buffer) > 2000000: # No single camera frame should be 2 Mb or larger
  81. Logger.log("w", "MJPEG buffer exceeds reasonable size. Restarting stream...")
  82. self.stop() # resets stream buffer and start index
  83. self.start()
  84. return
  85. if self._stream_buffer_start_index == -1:
  86. self._stream_buffer_start_index = self._stream_buffer.indexOf(b'\xff\xd8')
  87. stream_buffer_end_index = self._stream_buffer.lastIndexOf(b'\xff\xd9')
  88. # If this happens to be more than a single frame, then so be it; the JPG decoder will
  89. # ignore the extra data. We do it like this in order not to get a buildup of frames
  90. if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1:
  91. jpg_data = self._stream_buffer[self._stream_buffer_start_index:stream_buffer_end_index + 2]
  92. self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2:]
  93. self._stream_buffer_start_index = -1
  94. self._image.loadFromData(jpg_data)
  95. self.newImage.emit()