class MJPEGStreamer(object): _httpPort = 8085 def __init__(self, videoDevice, size, fps, format): self._device = '/dev/video%d' % videoDevice self._size = size self._fps = fps self._format = format self._videoRunning = False self._process = None self._infoArea = cv2.imread(os.path.join(app.static_folder, 'img', 'camera-info-overlay.jpg'), cv2.cv.CV_LOAD_IMAGE_COLOR) self._infoAreaShape = self._infoArea.shape #precalculated stuff watermark = cv2.imread(os.path.join(app.static_folder, 'img', 'astroprint_logo.png')) watermark = cv2.resize( watermark, ( 100, 100 * watermark.shape[0]/watermark.shape[1] ) ) self._watermarkShape = watermark.shape watermarkMask = cv2.cvtColor(watermark, cv2.COLOR_BGR2GRAY) / 255.0 watermarkMask = np.repeat( watermarkMask, 3).reshape( (self._watermarkShape[0],self._watermarkShape[1],3) ) self._watermakMaskWeighted = watermarkMask * watermark self._watermarkInverted = 1.0 - watermarkMask def startVideo(self): if not self._process: command = [ "/mjpeg_streamer/mjpg_streamer", "-i", "input_uvc.so -d %s -f %s -r %s --no_dynctrl%s" % (self._device, self._fps, self._size, ' -y' if self._format == 'x-raw' else ''), "-o", "output_http.so -p %d" % self._httpPort ] self._process = Command(command, env={'LD_LIBRARY_PATH': '/mjpeg_streamer'}, stderr=open(os.devnull, 'w')) if self._process: self._process.run(async=True) time.sleep(0.2) return self._process.returncode is None return False def stopVideo(self): if self._process: if self._process.returncode is None: self._process.terminate() self._process.wait() self._process = None def getPhoto(self, text=None, doneCb=None): image = None stopAfterPhoto = False if not self._process: self.startVideo() stopAfterPhoto = True try: if self._format == 'x-raw': time.sleep(1.8) # we need to give the camera some time to stabilize the image. 1.8 secs has been tested to work in low end cameras response = urllib2.urlopen('http://127.0.0.1:%d?action=snapshot' % self._httpPort) image = response.read() except urllib2.URLError: pass if image and text: decodedImage = cv2.imdecode(np.fromstring(image, np.uint8), cv2.CV_LOAD_IMAGE_COLOR) self._apply_watermark(decodedImage, text) image = cv2.cv.EncodeImage('.jpeg', cv2.cv.fromarray(decodedImage), [cv2.cv.CV_IMWRITE_JPEG_QUALITY, 80]).tostring() if stopAfterPhoto: self.stopVideo() if doneCb: doneCb(image) else: return image def _apply_watermark(self, img, text): if text and img != None: imgPortion = img[-(self._watermarkShape[0]+5):-5, -(self._watermarkShape[1]+5):-5] img[-(self._watermarkShape[0]+5):-5, -(self._watermarkShape[1]+5):-5] = (self._watermarkInverted * imgPortion) + self._watermakMaskWeighted img[:self._infoAreaShape[0], :self._infoAreaShape[1]] = self._infoArea cv2.putText(img, text, (30,17), cv2.FONT_HERSHEY_PLAIN, 1.0, (81,82,241), thickness=1) return True return False
class MJPEGStreamer(object): _httpPort = 8085 def __init__(self, videoDevice, size, fps, format): self._logger = logging.getLogger(__name__) self._device = '/dev/video%d' % videoDevice self._size = size self._fps = fps self._format = format self._videoRunning = False self._process = None self._streaming = False self._needsExposure = True self._infoArea = cv2.imread( os.path.join(app.static_folder, 'img', 'camera-info-overlay.jpg'), cv2.cv.CV_LOAD_IMAGE_COLOR) self._infoAreaShape = self._infoArea.shape #precalculated stuff watermark = cv2.imread( os.path.join(app.static_folder, 'img', 'astroprint_logo.png')) watermark = cv2.resize( watermark, (100, 100 * watermark.shape[0] / watermark.shape[1])) self._watermarkShape = watermark.shape watermarkMask = cv2.cvtColor(watermark, cv2.COLOR_BGR2GRAY) / 255.0 watermarkMask = np.repeat(watermarkMask, 3).reshape( (self._watermarkShape[0], self._watermarkShape[1], 3)) self._watermakMaskWeighted = watermarkMask * watermark self._watermarkInverted = 1.0 - watermarkMask def startStreamer(self): if not self._process: command = [ "/mjpeg_streamer/mjpg_streamer", "-i", "input_uvc.so -d %s -f %s -r %s --no_dynctrl%s" % (self._device, self._fps, self._size, ' -y' if self._format == 'x-raw' else ''), "-o", "output_http.so -p %d" % self._httpPort ] self._process = Command(command, env={'LD_LIBRARY_PATH': '/mjpeg_streamer'}, stderr=open(os.devnull, 'w')) if self._process: self._process.run(async=True) time.sleep(0.2) running = self._process.returncode is None return running return False def startVideo(self): if self._streaming: return True if self.startStreamer(): self._streaming = True return True return False def stop(self): if self._process: if self._process.returncode is None: self._process.terminate() tries = 4 while self._process.returncode is None: if tries > 0: tries -= 1 time.sleep(0.5) self._process.poll() else: break if self._process.returncode is None: self._logger.warn( 'Unable to terminate nicely, killing the process.') self._process.kill() self._process.wait() self._process = None self._streaming = False self._needsExposure = True def stopVideo(self): self._streaming = False def isVideoStreaming(self): return self._streaming def getPhoto(self, doneCb, text=None): image = None if not self._process: if not self.startStreamer(): self._logger.error('Unable to start MJPEG Streamer') doneCb(None) return try: if self._needsExposure and not self.isVideoStreaming(): time.sleep( 1.8 ) # we need to give the camera some time to stabilize the image. 1.8 secs has been tested to work in low end cameras self._needsExposure = False response = urllib2.urlopen('http://127.0.0.1:%d?action=snapshot' % self._httpPort) image = response.read() except urllib2.URLError as e: self._logger.error(e) if image and text: decodedImage = cv2.imdecode(np.fromstring(image, np.uint8), cv2.CV_LOAD_IMAGE_COLOR) self._apply_watermark(decodedImage, text) image = cv2.cv.EncodeImage( '.jpeg', cv2.cv.fromarray(decodedImage), [cv2.cv.CV_IMWRITE_JPEG_QUALITY, 80]).tostring() doneCb(image) def _apply_watermark(self, img, text): if text and img != None: imgPortion = img[-(self._watermarkShape[0] + 5):-5, -(self._watermarkShape[1] + 5):-5] img[-(self._watermarkShape[0] + 5):-5, -(self._watermarkShape[1] + 5):-5] = (self._watermarkInverted * imgPortion) + self._watermakMaskWeighted img[:self._infoAreaShape[0], :self. _infoAreaShape[1]] = self._infoArea cv2.putText(img, text, (30, 17), cv2.FONT_HERSHEY_PLAIN, 1.0, (81, 82, 241), thickness=1) return True return False
class Shell(AbstractContextManager): """A real shell running on the host machine, to be used in a context.""" def __init__(self, shell_binary, shell_argstr, return_code_echo_command, command_separator, is_interactive): """Sets up the context for a system shell process. Parameters ---------- shell_binary : str The shell binary, e.g. ``/bin/bash``, to start. shell_argstr : str Additional arguments to be passed to the shell at start. return_code_echo_command : str A command string in the shell's own language that prints to standard output the return code of the previously executed command. command_separator : str The character sequence to separate commands with. is_interactive : bool Whether the started shell is an interactive one. This does only change the behaviour of the context manager, to make the shell itself interactive, additional arguments in `shell_argstr` might need to be passed. """ self._args = shell_argstr self._binary = shell_binary # Note: The execution of the command and the reading of the output # has to happen BEFORE this timeout is hit, but a large timeout would # also mean waiting a lot for small commands, so this has to be # balanced carefully. self._capture = Capture(timeout=0.5, buffer_size=-1) self._command = Command(shell_binary + ' ' + shell_argstr, stdout=self._capture) self._echo = return_code_echo_command + command_separator self._interactive = is_interactive self._separator = command_separator self._started = False def __enter__(self): """Starts the shell in a context manager setting.""" return self.start() def __exit__(self, exc_type, exc_value, trace): """Destroys the shell and leaves the context.""" if self._interactive: self.kill() else: self.terminate() return False def start(self): """Starts the underlying shell process as configured in :py:func:`__init__`. """ if self._started: raise OSError(EEXIST, "The shell is already running!") print("[Shell] Starting '{0} {1}'...".format(self._binary, self._args), file=sys.stderr) try: self._command.run(input=PIPE, async_=True) except ValueError: raise FileNotFoundError("The shell binary '{0}' cannot be found " "on the system.".format(self._binary)) self._started = True return self @property def pid(self): """The PID of the started process.""" if not self._started: raise OSError(ESRCH, "The shell is not running!") return self._command.process.pid def kill(self): """Kills (by sending ``SIGKILL`` (``9``)) to the shell process.""" if not self._started: raise OSError(ESRCH, "The shell is not running!") self._command.kill() self._command.wait() self._capture.close(True) self._started = False def terminate(self): """Terminates (by sending ``SIGTERM`` (``15``)) to the shell process. Note ---- Interactive shells (:py:attr:`is_interactive`) usually catch and ignore this signal, and as such, :py:func:`kill` should be used to shut them down properly. """ if not self._started: raise OSError(ESRCH, "The shell is not running!") self._command.terminate() self._command.wait() self._capture.close(True) self._started = False def execute_command(self, cmd, timeout=None): """Execute `cmd` in the shell, wait `timeout` seconds, and read back the result. Parameters ---------- cmd : str The command to execute. This string will be written into the shell's standard input verbatim. timeout : int The time (in seconds) to wait before the output of the command is read. Returns ------- return_code : int The return code of the executed command. result : str The *standard output* of the executed command. Note ---- The command executed in the shell is extended with :py:attr:`command_separator` and :py:attr:`return_code_echo_command`, and written to the shell. In case of a conventional ``/bin/bash`` shell, for example, executing `cmd` ``echo "Foo"`` will actually execute: .. code-block:: bash echo "Foo"; echo $; Which will result in the output: .. code-block:: bash Foo 0 to be read as a result. Warning ------- The underlying library and the execution of piped shells does not allow a good method of "sensing" when the output became available while keeping interactivity. A too small `timeout` on a loaded system might result in output being lost, while a too big one will result in every command waiting for a considerable time. """ cmd = cmd + self._separator + self._echo print("[Shell '{0}'] Running command:\n{1}\n".format( self._binary, '\n'.join(list(map(lambda l: " > " + l, cmd.split('\n'))))), file=sys.stderr) self._command.stdin.write(cmd.encode('utf-8')) self._command.stdin.flush() stdout = self._capture.read(timeout=timeout) parts = stdout.decode().rstrip().split('\n') result, returncode = '\n'.join(parts[:-1]).rstrip(), parts[-1].rstrip() print("[Shell '{0}'] Command result #{1}:\n{2}".format( self._binary, returncode, result), file=sys.stderr) return int(returncode), result