text: """ OpenCV Backend RTSP Client """
  
  import cv2
  from io import BytesIO
  from PIL import Image
  
  from threading import Thread
  
  class Client:
      """ Maintain live RTSP feed without buffering. """
      _stream = None
  
      def __init__(self, rtsp_server_uri, verbose = False):
          """
              rtsp_server_uri: the path to an RTSP server. should start with "rtsp://"
              verbose: print log or not
          """
          self.rtsp_server_uri = rtsp_server_uri
          self._verbose = verbose
  
          self._bg = False
          self.open()
  
      def __enter__(self,*args,**kwargs):
          """ Returns the object which later will have __exit__ called.
              This relationship creates a context manager. """
          return self
  
      def __exit__(self, type=None, value=None, traceback=None):
          """ Together with __enter__, allows support for `with-` clauses. """
          self.close()
  
      def open(self):
          if self.isOpened():
              return
          self._stream = cv2.VideoCapture(self.rtsp_server_uri)
          if self._verbose:
              print("Connected to video source {}.".format(self.rtsp_server_uri))
          self._bg = True
          t = Thread(target=self._update, args=())
          t.daemon = True
          t.start()
          return self
  
      def close(self):
          """ signal background thread to stop. release CV stream """
          self._bg = False
          self._stream.release()
          if self._verbose:
              print("Disconnected from {}".format(self.rtsp_server_uri))
  
      def isOpened(self):
          """ return true if stream is opened and being read, else ensure closed """
          try:
              return (self._stream is not None) and self._stream.isOpened() and self._bg
          except:
              self.close()
              return False
  
      def _update(self):
          while self.isOpened():
              (grabbed, frame) = self._stream.read()
              if not grabbed:
                  self._bg = False
              else:
                  self._queue = frame
  
      def read(self,raw=False):
          """ Retrieve most recent frame and convert to PIL. Return unconverted with raw=True. """
          try:
              if raw:
                  return self._queue
              else:
                  return Image.fromarray(cv2.cvtColor(self._queue, cv2.COLOR_BGR2RGB))
          except:
              return None
  
      def preview(self):
          """ Blocking function. Opens OpenCV window to display stream. """
          win_name = 'RTSP'
          cv2.namedWindow(win_name, cv2.WINDOW_AUTOSIZE)
          cv2.moveWindow(win_name,20,20)
          while(self.isOpened()):
              cv2.imshow(win_name,self.read(raw=True))
              if cv2.waitKey(25) & 0xFF == ord('q'):
                  break
          cv2.waitKey()
          cv2.destroyAllWindows()
          cv2.waitKey()
  
  class PicamVideoFeed:
  
      def __init__(self):
          import picamera
          self.cam = picamera.PiCamera()
  
      def preview(self,*args,**kwargs):
          """ Blocking function. Opens OpenCV window to display stream. """
          self.cam.start_preview(*args,**kwargs)
  
      def open(self):
          pass
  
      def isOpened(self):
          return True
  
      def read(self):
          """https://picamera.readthedocs.io/en/release-1.13/recipes1.html#capturing-to-a-pil-image"""
          stream = BytesIO()
          self.cam.capture(stream, format='png')
          # "Rewind" the stream to the beginning so we can read its content
          stream.seek(0)
          return Image.open(stream)
  
      def close(self):
          pass
  
      def stop(self):
          pass
  
  class WebcamVideoFeed:
      def __init__(self, source_id, verbose = False):
          """
              source_id: the id of a camera interface. Should be an integer
              verbose: print log or not
          """
          self._cam_id = source_id
          self._verbose = verbose
          self.open()
  
      def __enter__(self,*args,**kwargs):
          """ Returns the object which later will have __exit__ called.
              This relationship creates a context manager. """
          return self
  
      def __exit__(self, type=None, value=None, traceback=None):
          """ Together with __enter__, allows support for `with-` clauses. """
          self.close()
  
      def open(self):
          if self.isOpened():
              return
  
          self._stream = cv2.VideoCapture(self._cam_id)
  
          if self._verbose:
              if self.isOpened():
                  print("Connected to video source {}.".format(self._cam_id))
              else:
                  print("Failed to connect to source {}.".format(self._cam_id))
                  return
  
      def close(self):
          if self.isOpened():
              self._stream.release()
  
      def isOpened(self):
          try:
              return self._stream is not None and self._stream.isOpened()
          except:
              return False
  
      def read(self):
          (grabbed, frame) = self._stream.read()
          return Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
  
      def preview(self):
          """ Blocking function. Opens OpenCV window to display stream. """
          win_name = 'Camera'
          cv2.namedWindow(win_name, cv2.WINDOW_AUTOSIZE)
          cv2.moveWindow(win_name,20,20)
          self.open()
          while(self.isOpened()):
              cv2.imshow(win_name,self._stream.read()[1])
              if cv2.waitKey(25) & 0xFF == ord('q'):
                  break
          cv2.waitKey()
          cv2.destroyAllWindows()
          cv2.waitKey()
  