forked from platypush/platypush
Fixed setting of output_file
on FfmpegWriter
.
Also, fixed parameters passed to camera writer objects.
This commit is contained in:
parent
a38ef6bc7a
commit
c59446fdb1
3 changed files with 86 additions and 50 deletions
|
@ -4,7 +4,6 @@ import pathlib
|
|||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import asdict
|
||||
|
@ -107,8 +106,8 @@ class CameraPlugin(Plugin, ABC):
|
|||
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 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
|
||||
|
@ -526,7 +525,7 @@ class CameraPlugin(Plugin, ABC):
|
|||
if video_file:
|
||||
self.fire_event(CameraVideoRenderedEvent(filename=video_file))
|
||||
|
||||
def start_camera(self, camera: Camera, preview: bool = False, *args, **kwargs):
|
||||
def start_camera(self, camera: Camera, *args, preview: bool = False, **kwargs):
|
||||
"""
|
||||
Start a camera capture session.
|
||||
|
||||
|
@ -548,6 +547,7 @@ class CameraPlugin(Plugin, ABC):
|
|||
@action
|
||||
def capture_video(
|
||||
self,
|
||||
device: Optional[Union[int, str]] = None,
|
||||
duration: Optional[float] = None,
|
||||
video_file: Optional[str] = None,
|
||||
preview: bool = False,
|
||||
|
@ -556,6 +556,7 @@ class CameraPlugin(Plugin, ABC):
|
|||
"""
|
||||
Capture a video.
|
||||
|
||||
:param device: Name/path/ID of the device to capture from (default: None, use the default device).
|
||||
:param duration: Record duration in seconds (default: None, record until ``stop_capture``).
|
||||
:param video_file: If set, the stream will be recorded to the specified video file (default: None).
|
||||
:param camera: Camera parameters override - see constructors parameters.
|
||||
|
@ -563,7 +564,7 @@ class CameraPlugin(Plugin, ABC):
|
|||
:return: If duration is specified, the method will wait until the recording is done and return the local path
|
||||
to the recorded resource. Otherwise, it will return the status of the camera device after starting it.
|
||||
"""
|
||||
camera = self.open_device(**camera)
|
||||
camera = self.open_device(device=device, **camera)
|
||||
self.start_camera(
|
||||
camera,
|
||||
duration=duration,
|
||||
|
@ -595,17 +596,24 @@ class CameraPlugin(Plugin, ABC):
|
|||
self.close_device(dev)
|
||||
|
||||
@action
|
||||
def capture_image(self, image_file: str, preview: bool = False, **camera) -> str:
|
||||
def capture_image(
|
||||
self,
|
||||
image_file: str,
|
||||
device: Optional[Union[int, str]] = None,
|
||||
preview: bool = False,
|
||||
**camera,
|
||||
) -> str:
|
||||
"""
|
||||
Capture an image.
|
||||
|
||||
:param image_file: Path where the output image will be stored.
|
||||
:param device: Name/path/ID of the device to capture from (default: None, use the default device).
|
||||
:param camera: Camera parameters override - see constructors parameters.
|
||||
:param preview: Show a preview of the camera frames.
|
||||
:return: The local path to the saved image.
|
||||
"""
|
||||
|
||||
with self.open(**camera) as camera:
|
||||
with self.open(device=device, **camera) as camera:
|
||||
warmup_frames = (
|
||||
camera.info.warmup_frames if camera.info.warmup_frames else 1
|
||||
)
|
||||
|
@ -617,20 +625,23 @@ class CameraPlugin(Plugin, ABC):
|
|||
return image_file
|
||||
|
||||
@action
|
||||
def take_picture(self, image_file: str, **camera) -> str:
|
||||
def take_picture(
|
||||
self, image_file: str, device: Optional[Union[int, str]] = None, **camera
|
||||
) -> str:
|
||||
"""
|
||||
Alias for :meth:`.capture_image`.
|
||||
|
||||
:param image_file: Path where the output image will be stored.
|
||||
:param device: Name/path/ID of the device to capture from (default: None, use the default device).
|
||||
:param camera: Camera parameters override - see constructors parameters.
|
||||
:param preview: Show a preview of the camera frames.
|
||||
:return: The local path to the saved image.
|
||||
"""
|
||||
return str(self.capture_image(image_file, **camera).output)
|
||||
return str(self.capture_image(image_file, device=device, **camera).output)
|
||||
|
||||
@action
|
||||
def capture_sequence(
|
||||
self,
|
||||
device: Optional[Union[int, str]] = None,
|
||||
duration: Optional[float] = None,
|
||||
n_frames: Optional[int] = None,
|
||||
preview: bool = False,
|
||||
|
@ -639,6 +650,7 @@ class CameraPlugin(Plugin, ABC):
|
|||
"""
|
||||
Capture a sequence of frames from a camera and store them to a directory.
|
||||
|
||||
:param device: Name/path/ID of the device to capture from (default: None, use the default device).
|
||||
:param duration: Duration of the sequence in seconds (default: until :meth:`.stop_capture` is called).
|
||||
:param n_frames: Number of images to be captured (default: until :meth:`.stop_capture` is called).
|
||||
:param camera: Camera parameters override - see constructors parameters. ``frames_dir`` and ``fps`` in
|
||||
|
@ -646,7 +658,7 @@ class CameraPlugin(Plugin, ABC):
|
|||
:param preview: Show a preview of the camera frames.
|
||||
:return: The directory where the image files have been stored.
|
||||
"""
|
||||
with self.open(**camera) as camera:
|
||||
with self.open(device=device, **camera) as camera:
|
||||
self.start_camera(
|
||||
camera, duration=duration, n_frames=n_frames, preview=preview
|
||||
)
|
||||
|
@ -655,17 +667,22 @@ class CameraPlugin(Plugin, ABC):
|
|||
|
||||
@action
|
||||
def capture_preview(
|
||||
self, duration: Optional[float] = None, n_frames: Optional[int] = None, **camera
|
||||
self,
|
||||
device: Optional[Union[int, str]] = None,
|
||||
duration: Optional[float] = None,
|
||||
n_frames: Optional[int] = None,
|
||||
**camera,
|
||||
) -> dict:
|
||||
"""
|
||||
Start a camera preview session.
|
||||
|
||||
:param device: Name/path/ID of the device to capture from (default: None, use the default device).
|
||||
:param duration: Preview duration (default: until :meth:`.stop_capture` is called).
|
||||
:param n_frames: Number of frames to display before closing (default: until :meth:`.stop_capture` is called).
|
||||
:param camera: Camera object properties.
|
||||
:return: The status of the device.
|
||||
"""
|
||||
camera = self.open_device(frames_dir=None, **camera)
|
||||
camera = self.open_device(device=device, frames_dir=None, **camera)
|
||||
self.start_camera(camera, duration=duration, n_frames=n_frames, preview=True)
|
||||
return self.status(camera.info.device) # type: ignore
|
||||
|
||||
|
@ -744,17 +761,24 @@ class CameraPlugin(Plugin, ABC):
|
|||
|
||||
@action
|
||||
def start_streaming(
|
||||
self, duration: Optional[float] = None, stream_format: str = 'mkv', **camera
|
||||
self,
|
||||
device: Optional[Union[int, str]] = None,
|
||||
duration: Optional[float] = None,
|
||||
stream_format: str = 'mkv',
|
||||
**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``).
|
||||
:param camera: Camera object properties - see constructor parameters.
|
||||
:return: The status of the device.
|
||||
"""
|
||||
camera = self.open_device(stream=True, stream_format=stream_format, **camera)
|
||||
camera = self.open_device(
|
||||
device=device, stream=True, stream_format=stream_format, **camera
|
||||
)
|
||||
return self._start_streaming(camera, duration, stream_format) # type: ignore
|
||||
|
||||
def _start_streaming(
|
||||
|
@ -900,7 +924,7 @@ class CameraPlugin(Plugin, ABC):
|
|||
return frame
|
||||
|
||||
size = (int(frame.size[0] * scale_x), int(frame.size[1] * scale_y))
|
||||
return frame.resize(size, Image.ANTIALIAS)
|
||||
return frame.resize(size, Image.BICUBIC)
|
||||
|
||||
@staticmethod
|
||||
def encode_frame(frame, encoding: str = 'jpeg') -> bytes:
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import signal
|
||||
import subprocess
|
||||
from typing import Optional, Tuple
|
||||
from typing import Iterable, Optional
|
||||
|
||||
from PIL import Image
|
||||
from PIL.Image import Image as ImageType
|
||||
|
||||
from platypush.plugins.camera import CameraPlugin
|
||||
from platypush.plugins.camera.ffmpeg.model import FFmpegCamera, FFmpegCameraInfo
|
||||
from platypush.plugins.camera.model.camera import Camera
|
||||
|
||||
|
||||
class CameraFfmpegPlugin(CameraPlugin):
|
||||
|
@ -21,75 +22,83 @@ class CameraFfmpegPlugin(CameraPlugin):
|
|||
self,
|
||||
device: Optional[str] = '/dev/video0',
|
||||
input_format: str = 'v4l2',
|
||||
ffmpeg_args: Tuple[str] = (),
|
||||
**opts
|
||||
ffmpeg_args: Iterable[str] = (),
|
||||
**opts,
|
||||
):
|
||||
"""
|
||||
:param device: Path to the camera device (default: ``/dev/video0``).
|
||||
:param input_format: FFmpeg input format for the the camera device (default: ``v4l2``).
|
||||
:param input_format: FFmpeg input format for the camera device (default: ``v4l2``).
|
||||
:param ffmpeg_args: Extra options to be passed to the FFmpeg executable.
|
||||
:param opts: Camera options - see constructor of :class:`platypush.plugins.camera.CameraPlugin`.
|
||||
"""
|
||||
super().__init__(device=device, input_format=input_format, **opts)
|
||||
self.camera_info.ffmpeg_args = ffmpeg_args or ()
|
||||
self.camera_info.ffmpeg_args = ffmpeg_args or () # type: ignore
|
||||
|
||||
def prepare_device(self, camera: FFmpegCamera) -> subprocess.Popen:
|
||||
warmup_seconds = self._get_warmup_seconds(camera)
|
||||
def prepare_device(self, device: Camera) -> subprocess.Popen:
|
||||
assert isinstance(device, FFmpegCamera)
|
||||
warmup_seconds = self._get_warmup_seconds(device)
|
||||
ffmpeg = [
|
||||
camera.info.ffmpeg_bin,
|
||||
device.info.ffmpeg_bin,
|
||||
'-y',
|
||||
'-f',
|
||||
camera.info.input_format,
|
||||
device.info.input_format,
|
||||
'-i',
|
||||
camera.info.device,
|
||||
device.info.device,
|
||||
'-s',
|
||||
'{}x{}'.format(*camera.info.resolution),
|
||||
*(
|
||||
(f'{device.info.resolution[0]}x{device.info.resolution[1]}',)
|
||||
if device.info.resolution
|
||||
else ()
|
||||
),
|
||||
'-ss',
|
||||
str(warmup_seconds),
|
||||
*(('-r', str(camera.info.fps)) if camera.info.fps else ()),
|
||||
*(('-r', str(device.info.fps)) if device.info.fps else ()),
|
||||
'-pix_fmt',
|
||||
'rgb24',
|
||||
'-f',
|
||||
'rawvideo',
|
||||
*camera.info.ffmpeg_args,
|
||||
*device.info.ffmpeg_args,
|
||||
'-',
|
||||
]
|
||||
|
||||
self.logger.info('Running FFmpeg: {}'.format(' '.join(ffmpeg)))
|
||||
self.logger.info('Running FFmpeg command: "%s"', ' '.join(ffmpeg))
|
||||
proc = subprocess.Popen(ffmpeg, stdout=subprocess.PIPE)
|
||||
# Start in suspended mode
|
||||
proc.send_signal(signal.SIGSTOP)
|
||||
return proc
|
||||
|
||||
def start_camera(
|
||||
self, camera: FFmpegCamera, preview: bool = False, *args, **kwargs
|
||||
):
|
||||
def start_camera(self, camera: Camera, *args, preview: bool = False, **kwargs):
|
||||
assert isinstance(camera, FFmpegCamera)
|
||||
super().start_camera(*args, camera=camera, preview=preview, **kwargs)
|
||||
if camera.object:
|
||||
camera.object.send_signal(signal.SIGCONT)
|
||||
|
||||
def release_device(self, camera: FFmpegCamera):
|
||||
if camera.object:
|
||||
camera.object.terminate()
|
||||
if camera.object.stdout:
|
||||
camera.object.stdout.close()
|
||||
camera.object = None
|
||||
def release_device(self, device: Camera):
|
||||
assert isinstance(device, FFmpegCamera)
|
||||
if device.object:
|
||||
device.object.terminate()
|
||||
if device.object.stdout:
|
||||
device.object.stdout.close()
|
||||
device.object = None # type: ignore
|
||||
|
||||
def wait_capture(self, camera: FFmpegCamera) -> None:
|
||||
def wait_capture(self, camera: Camera):
|
||||
assert isinstance(camera, FFmpegCamera)
|
||||
if camera.object and camera.object.poll() is None:
|
||||
try:
|
||||
camera.object.wait(timeout=camera.info.capture_timeout)
|
||||
except Exception as e:
|
||||
self.logger.warning('Error on FFmpeg capture wait: {}'.format(str(e)))
|
||||
self.logger.warning('Error on FFmpeg capture wait: %s', e)
|
||||
|
||||
def capture_frame(
|
||||
self, camera: FFmpegCamera, *args, **kwargs
|
||||
) -> Optional[ImageType]:
|
||||
raw_size = camera.info.resolution[0] * camera.info.resolution[1] * 3
|
||||
data = camera.object.stdout.read(raw_size)
|
||||
def capture_frame(self, device: Camera, *_, **__) -> Optional[ImageType]:
|
||||
assert isinstance(device, FFmpegCamera)
|
||||
assert device.info.resolution, 'Resolution not set'
|
||||
assert device.object.stdout, 'Camera not started'
|
||||
|
||||
raw_size = device.info.resolution[0] * device.info.resolution[1] * 3
|
||||
data = device.object.stdout.read(raw_size)
|
||||
if len(data) < raw_size:
|
||||
return
|
||||
return Image.frombytes('RGB', camera.info.resolution, data)
|
||||
return Image.frombytes('RGB', device.info.resolution, data)
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -3,7 +3,6 @@ import logging
|
|||
import multiprocessing
|
||||
import os
|
||||
import time
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, IO
|
||||
|
||||
|
@ -63,8 +62,12 @@ class FileVideoWriter(VideoWriter, ABC):
|
|||
"""
|
||||
|
||||
def __init__(self, *args, output_file: str, **kwargs):
|
||||
super().__init__(self, *args, **kwargs)
|
||||
self.output_file = os.path.abspath(os.path.expanduser(output_file))
|
||||
super().__init__(
|
||||
self,
|
||||
*args,
|
||||
output_file=os.path.abspath(os.path.expanduser(output_file)),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
class StreamWriter(VideoWriter, ABC):
|
||||
|
|
Loading…
Reference in a new issue