NetworkCamera.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  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._target = target
  16. self._started = False
  17. @pyqtSlot(str)
  18. def setTarget(self, target):
  19. restart_required = False
  20. if self._started:
  21. self.stop()
  22. restart_required = True
  23. self._target = target
  24. if restart_required:
  25. self.start()
  26. @pyqtProperty(QImage, notify=newImage)
  27. def latestImage(self):
  28. return self._image
  29. @pyqtSlot()
  30. def start(self):
  31. # Ensure that previous requests (if any) are stopped.
  32. self.stop()
  33. if self._target is None:
  34. Logger.log("w", "Unable to start camera stream without target!")
  35. return
  36. self._started = True
  37. url = QUrl(self._target)
  38. self._image_request = QNetworkRequest(url)
  39. if self._manager is None:
  40. self._manager = QNetworkAccessManager()
  41. self._image_reply = self._manager.get(self._image_request)
  42. self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
  43. @pyqtSlot()
  44. def stop(self):
  45. self._stream_buffer = b""
  46. self._stream_buffer_start_index = -1
  47. if self._image_reply:
  48. try:
  49. # disconnect the signal
  50. try:
  51. self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
  52. except Exception:
  53. pass
  54. # abort the request if it's not finished
  55. if not self._image_reply.isFinished():
  56. self._image_reply.close()
  57. except Exception as e: # RuntimeError
  58. pass # It can happen that the wrapped c++ object is already deleted.
  59. self._image_reply = None
  60. self._image_request = None
  61. self._manager = None
  62. self._started = False
  63. def getImage(self):
  64. return self._image
  65. ## Ensure that close gets called when object is destroyed
  66. def __del__(self):
  67. self.stop()
  68. def _onStreamDownloadProgress(self, bytes_received, bytes_total):
  69. # An MJPG stream is (for our purpose) a stream of concatenated JPG images.
  70. # JPG images start with the marker 0xFFD8, and end with 0xFFD9
  71. if self._image_reply is None:
  72. return
  73. self._stream_buffer += self._image_reply.readAll()
  74. if len(self._stream_buffer) > 2000000: # No single camera frame should be 2 Mb or larger
  75. Logger.log("w", "MJPEG buffer exceeds reasonable size. Restarting stream...")
  76. self.stop() # resets stream buffer and start index
  77. self.start()
  78. return
  79. if self._stream_buffer_start_index == -1:
  80. self._stream_buffer_start_index = self._stream_buffer.indexOf(b'\xff\xd8')
  81. stream_buffer_end_index = self._stream_buffer.lastIndexOf(b'\xff\xd9')
  82. # If this happens to be more than a single frame, then so be it; the JPG decoder will
  83. # ignore the extra data. We do it like this in order not to get a buildup of frames
  84. if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1:
  85. jpg_data = self._stream_buffer[self._stream_buffer_start_index:stream_buffer_end_index + 2]
  86. self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2:]
  87. self._stream_buffer_start_index = -1
  88. self._image.loadFromData(jpg_data)
  89. self.newImage.emit()