diff --git a/platypush/plugins/camera/__init__.py b/platypush/plugins/camera/__init__.py index f41e2b89..79bcd3ac 100644 --- a/platypush/plugins/camera/__init__.py +++ b/platypush/plugins/camera/__init__.py @@ -86,6 +86,7 @@ class CameraPlugin(RunnablePlugin, ABC): stream_format: str = 'mjpeg', listen_port: Optional[int] = 5000, bind_address: str = '0.0.0.0', + stream_on_start: bool = False, ffmpeg_bin: str = 'ffmpeg', input_codec: Optional[str] = None, output_codec: Optional[str] = None, @@ -94,41 +95,57 @@ class CameraPlugin(RunnablePlugin, ABC): """ :param device: Identifier of the default capturing device. :param resolution: Default resolution, as a tuple of two integers. - :param frames_dir: Directory where the camera frames will be stored (default: - ``~/.local/share/platypush//frames``) + :param frames_dir: Directory where the camera frames will be stored + (default: ``~/.local/share/platypush//frames``) :param warmup_frames: Cameras usually take a while to adapt their luminosity and focus to the environment when taking a picture. This parameter allows you to specify the number of "warmup" frames to capture upon picture command before actually capturing a frame (default: 5 but you may want to calibrate this parameter for your camera) - :param warmup_seconds: Number of seconds to wait before a picture is taken or the first frame of a - video/sequence is captured (default: 0). - :param capture_timeout: Maximum number of seconds to wait between the programmed termination of a capture - session and the moment the device is released. - :param scale_x: If set, the images will be scaled along the x-axis by the specified factor - :param scale_y: If set, the images will be scaled along the y-axis by the specified factor + :param warmup_seconds: Number of seconds to wait before a picture is + taken or the first frame of a video/sequence is captured (default: + 0). + :param capture_timeout: Maximum number of seconds to wait between the + programmed termination of a capture session and the moment the + device is released. + :param scale_x: If set, the images will be scaled along the x-axis by + the specified factor + :param scale_y: If set, the images will be scaled along the y-axis by + the specified factor :param color_transform: Color transformation to apply to the images. :param grayscale: Whether the output should be converted to grayscale. - :param rotate: If set, the images will be rotated by the specified number of degrees + :param rotate: If set, the images will be rotated by the specified + number of degrees :param fps: Frames per second (default: 25). - :param horizontal_flip: If set, the images will be flipped on the horizontal axis. - :param vertical_flip: If set, the images will be flipped on the vertical axis. - :param listen_port: Default port to be used for streaming over TCP (default: 5000). - :param bind_address: Default bind address for TCP streaming (default: 0.0.0.0, accept any connections). - :param input_codec: Specify the ffmpeg video codec (``-vcodec``) used for the input. - :param output_codec: Specify the ffmpeg video codec (``-vcodec``) to be used for encoding the output. For some - ffmpeg output formats (e.g. ``h264`` and ``rtp``) this may default to ``libxvid``. + :param horizontal_flip: If set, the images will be flipped on the + horizontal axis. + :param vertical_flip: If set, the images will be flipped on the vertical + axis. + :param listen_port: Default port to be used for streaming over TCP + (default: 5000). + :param bind_address: Default bind address for TCP streaming (default: + 0.0.0.0, accept connections on any network interface). + :param stream_on_start: If set, the camera will start streaming on the + specified ``bind_address`` and ``listen_port`` as soon as the plugin + is started. Otherwise, the stream will be started only when the + :meth:`.start_streaming` method is called. Default: False. + :param input_codec: Specify the ffmpeg video codec (``-vcodec``) used + for the input. + :param output_codec: Specify the ffmpeg video codec (``-vcodec``) to be + used for encoding the output. For some ffmpeg output formats (e.g. + ``h264`` and ``rtp``) this may default to ``libxvid``. :param input_format: Plugin-specific format/type for the input stream. :param output_format: Plugin-specific format/type for the output videos. :param ffmpeg_bin: Path to the ffmpeg binary (default: ``ffmpeg``). - :param stream_format: Default format for the output when streamed to a network device. Available: + :param stream_format: Default format for the output when streamed to a + network device. Available: - - ``MJPEG`` (default) - - ``H264`` (over ``ffmpeg``) - - ``H265`` (over ``ffmpeg``) - - ``MKV`` (over ``ffmpeg``) - - ``MP4`` (over ``ffmpeg``) + - ``MJPEG`` (default) + - ``H264`` (over ``ffmpeg``) + - ``H265`` (over ``ffmpeg``) + - ``MKV`` (over ``ffmpeg``) + - ``MP4`` (over ``ffmpeg``) """ super().__init__(**kwargs) @@ -137,6 +154,7 @@ class CameraPlugin(RunnablePlugin, ABC): plugin_name = get_plugin_name_by_class(self) assert isinstance(workdir, str) and plugin_name self.workdir = os.path.join(workdir, plugin_name) + self._stream_on_start = stream_on_start pathlib.Path(self.workdir).mkdir(mode=0o755, exist_ok=True, parents=True) self.camera_info = self._camera_info_class( @@ -322,7 +340,9 @@ class CameraPlugin(RunnablePlugin, ABC): raise NotImplementedError() @staticmethod - def store_frame(frame, filepath: str, format: Optional[str] = None): + def store_frame( # pylint: disable=redefined-builtin + frame, filepath: str, format: Optional[str] = None + ): """ Capture a frame to the filesystem using the ``PIL`` library - it can be overridden by derived classes. @@ -346,9 +366,9 @@ class CameraPlugin(RunnablePlugin, ABC): def _store_frame( self, frame, + *args, frames_dir: Optional[str] = None, image_file: Optional[str] = None, - *args, **kwargs, ) -> str: """ @@ -792,21 +812,39 @@ class CameraPlugin(RunnablePlugin, ABC): self, device: Optional[Union[int, str]] = None, duration: Optional[float] = None, - stream_format: str = 'mkv', + stream_format: Optional[str] = None, **camera, ) -> dict: """ Expose the video stream of a camera over a TCP connection. - :param device: Name/path/ID of the device to capture from (default: None, use the default device). - :param duration: Streaming thread duration (default: until :meth:`.stop_streaming` is called). - :param stream_format: Format of the output stream - e.g. ``h264``, ``mjpeg``, ``mkv`` etc. (default: ``mkv``). + When the streaming is started, the plugin will listen on the specified + ``bind_address`` and ``listen_port`` and stream camera frames to + connected clients. If ``stream_format`` is a video format (H264, H265, + MKV, MP4 etc.) then the camera stream can be viewed using a video + player - for example, using ``vlc``: + + .. code-block:: bash + + vlc tcp://: + + :param device: Name/path/ID of the device to capture from (default: + None, use the default device). + :param duration: Streaming thread duration (default: until + :meth:`.stop_streaming` is called). + :param stream_format: Format of the output stream - e.g. ``h264``, + ``mjpeg``, ``mkv`` etc. If not specified, the ``stream_format`` + configured on the plugin will be used. :param camera: Camera object properties - see constructor parameters. :return: The status of the device. """ camera = self.open_device( - device=device, stream=True, stream_format=stream_format, **camera + device=device, + stream=True, + stream_format=stream_format or self.camera_info.stream_format, + **camera, ) + return self._start_streaming(camera, duration, stream_format) # type: ignore def _start_streaming( @@ -981,7 +1019,23 @@ class CameraPlugin(RunnablePlugin, ABC): return 0 def main(self): - self.wait_stop() + if not self._stream_on_start: + self.wait_stop() + return + + while not self.should_stop(): + if self._stream_on_start: + self.start_streaming() + + cameras = list(self._streams.values()) + if not cameras: + self.logger.warning('No camera devices could be streamed') + self.wait_stop() + break + + camera = cameras[0] + wait_for_either(self._should_stop, camera.stop_stream_event) + self.stop_streaming() # vim:sw=4:ts=4:et: