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 socket
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
|
@ -107,8 +106,8 @@ class CameraPlugin(Plugin, ABC):
|
||||||
video/sequence is captured (default: 0).
|
video/sequence is captured (default: 0).
|
||||||
:param capture_timeout: Maximum number of seconds to wait between the programmed termination of a capture
|
:param capture_timeout: Maximum number of seconds to wait between the programmed termination of a capture
|
||||||
session and the moment the device is released.
|
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_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_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 color_transform: Color transformation to apply to the images.
|
||||||
:param grayscale: Whether the output should be converted to grayscale.
|
: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
|
||||||
|
@ -526,7 +525,7 @@ class CameraPlugin(Plugin, ABC):
|
||||||
if video_file:
|
if video_file:
|
||||||
self.fire_event(CameraVideoRenderedEvent(filename=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.
|
Start a camera capture session.
|
||||||
|
|
||||||
|
@ -548,6 +547,7 @@ class CameraPlugin(Plugin, ABC):
|
||||||
@action
|
@action
|
||||||
def capture_video(
|
def capture_video(
|
||||||
self,
|
self,
|
||||||
|
device: Optional[Union[int, str]] = None,
|
||||||
duration: Optional[float] = None,
|
duration: Optional[float] = None,
|
||||||
video_file: Optional[str] = None,
|
video_file: Optional[str] = None,
|
||||||
preview: bool = False,
|
preview: bool = False,
|
||||||
|
@ -556,6 +556,7 @@ class CameraPlugin(Plugin, ABC):
|
||||||
"""
|
"""
|
||||||
Capture a video.
|
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 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 video_file: If set, the stream will be recorded to the specified video file (default: None).
|
||||||
:param camera: Camera parameters override - see constructors parameters.
|
: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
|
: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.
|
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(
|
self.start_camera(
|
||||||
camera,
|
camera,
|
||||||
duration=duration,
|
duration=duration,
|
||||||
|
@ -595,17 +596,24 @@ class CameraPlugin(Plugin, ABC):
|
||||||
self.close_device(dev)
|
self.close_device(dev)
|
||||||
|
|
||||||
@action
|
@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.
|
Capture an image.
|
||||||
|
|
||||||
:param image_file: Path where the output image will be stored.
|
: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 camera: Camera parameters override - see constructors parameters.
|
||||||
:param preview: Show a preview of the camera frames.
|
:param preview: Show a preview of the camera frames.
|
||||||
:return: The local path to the saved image.
|
:return: The local path to the saved image.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with self.open(**camera) as camera:
|
with self.open(device=device, **camera) as camera:
|
||||||
warmup_frames = (
|
warmup_frames = (
|
||||||
camera.info.warmup_frames if camera.info.warmup_frames else 1
|
camera.info.warmup_frames if camera.info.warmup_frames else 1
|
||||||
)
|
)
|
||||||
|
@ -617,20 +625,23 @@ class CameraPlugin(Plugin, ABC):
|
||||||
return image_file
|
return image_file
|
||||||
|
|
||||||
@action
|
@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`.
|
Alias for :meth:`.capture_image`.
|
||||||
|
|
||||||
:param image_file: Path where the output image will be stored.
|
: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 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: 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
|
@action
|
||||||
def capture_sequence(
|
def capture_sequence(
|
||||||
self,
|
self,
|
||||||
|
device: Optional[Union[int, str]] = None,
|
||||||
duration: Optional[float] = None,
|
duration: Optional[float] = None,
|
||||||
n_frames: Optional[int] = None,
|
n_frames: Optional[int] = None,
|
||||||
preview: bool = False,
|
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.
|
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 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 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
|
: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.
|
:param preview: Show a preview of the camera frames.
|
||||||
:return: The directory where the image files have been stored.
|
: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(
|
self.start_camera(
|
||||||
camera, duration=duration, n_frames=n_frames, preview=preview
|
camera, duration=duration, n_frames=n_frames, preview=preview
|
||||||
)
|
)
|
||||||
|
@ -655,17 +667,22 @@ class CameraPlugin(Plugin, ABC):
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def capture_preview(
|
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:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Start a camera preview session.
|
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 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 n_frames: Number of frames to display before closing (default: until :meth:`.stop_capture` is called).
|
||||||
:param camera: Camera object properties.
|
:param camera: Camera object properties.
|
||||||
:return: The status of the device.
|
: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)
|
self.start_camera(camera, duration=duration, n_frames=n_frames, preview=True)
|
||||||
return self.status(camera.info.device) # type: ignore
|
return self.status(camera.info.device) # type: ignore
|
||||||
|
|
||||||
|
@ -744,17 +761,24 @@ class CameraPlugin(Plugin, ABC):
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def start_streaming(
|
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:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Expose the video stream of a camera over a TCP connection.
|
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 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 stream_format: Format of the output stream - e.g. ``h264``, ``mjpeg``, ``mkv`` etc. (default: ``mkv``).
|
||||||
:param camera: Camera object properties - see constructor parameters.
|
:param camera: Camera object properties - see constructor parameters.
|
||||||
:return: The status of the device.
|
: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
|
return self._start_streaming(camera, duration, stream_format) # type: ignore
|
||||||
|
|
||||||
def _start_streaming(
|
def _start_streaming(
|
||||||
|
@ -900,7 +924,7 @@ class CameraPlugin(Plugin, ABC):
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
size = (int(frame.size[0] * scale_x), int(frame.size[1] * scale_y))
|
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
|
@staticmethod
|
||||||
def encode_frame(frame, encoding: str = 'jpeg') -> bytes:
|
def encode_frame(frame, encoding: str = 'jpeg') -> bytes:
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional, Tuple
|
from typing import Iterable, Optional
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from PIL.Image import Image as ImageType
|
from PIL.Image import Image as ImageType
|
||||||
|
|
||||||
from platypush.plugins.camera import CameraPlugin
|
from platypush.plugins.camera import CameraPlugin
|
||||||
from platypush.plugins.camera.ffmpeg.model import FFmpegCamera, FFmpegCameraInfo
|
from platypush.plugins.camera.ffmpeg.model import FFmpegCamera, FFmpegCameraInfo
|
||||||
|
from platypush.plugins.camera.model.camera import Camera
|
||||||
|
|
||||||
|
|
||||||
class CameraFfmpegPlugin(CameraPlugin):
|
class CameraFfmpegPlugin(CameraPlugin):
|
||||||
|
@ -21,75 +22,83 @@ class CameraFfmpegPlugin(CameraPlugin):
|
||||||
self,
|
self,
|
||||||
device: Optional[str] = '/dev/video0',
|
device: Optional[str] = '/dev/video0',
|
||||||
input_format: str = 'v4l2',
|
input_format: str = 'v4l2',
|
||||||
ffmpeg_args: Tuple[str] = (),
|
ffmpeg_args: Iterable[str] = (),
|
||||||
**opts
|
**opts,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
:param device: Path to the camera device (default: ``/dev/video0``).
|
: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 ffmpeg_args: Extra options to be passed to the FFmpeg executable.
|
||||||
:param opts: Camera options - see constructor of :class:`platypush.plugins.camera.CameraPlugin`.
|
:param opts: Camera options - see constructor of :class:`platypush.plugins.camera.CameraPlugin`.
|
||||||
"""
|
"""
|
||||||
super().__init__(device=device, input_format=input_format, **opts)
|
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:
|
def prepare_device(self, device: Camera) -> subprocess.Popen:
|
||||||
warmup_seconds = self._get_warmup_seconds(camera)
|
assert isinstance(device, FFmpegCamera)
|
||||||
|
warmup_seconds = self._get_warmup_seconds(device)
|
||||||
ffmpeg = [
|
ffmpeg = [
|
||||||
camera.info.ffmpeg_bin,
|
device.info.ffmpeg_bin,
|
||||||
'-y',
|
'-y',
|
||||||
'-f',
|
'-f',
|
||||||
camera.info.input_format,
|
device.info.input_format,
|
||||||
'-i',
|
'-i',
|
||||||
camera.info.device,
|
device.info.device,
|
||||||
'-s',
|
'-s',
|
||||||
'{}x{}'.format(*camera.info.resolution),
|
*(
|
||||||
|
(f'{device.info.resolution[0]}x{device.info.resolution[1]}',)
|
||||||
|
if device.info.resolution
|
||||||
|
else ()
|
||||||
|
),
|
||||||
'-ss',
|
'-ss',
|
||||||
str(warmup_seconds),
|
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',
|
'-pix_fmt',
|
||||||
'rgb24',
|
'rgb24',
|
||||||
'-f',
|
'-f',
|
||||||
'rawvideo',
|
'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)
|
proc = subprocess.Popen(ffmpeg, stdout=subprocess.PIPE)
|
||||||
# Start in suspended mode
|
# Start in suspended mode
|
||||||
proc.send_signal(signal.SIGSTOP)
|
proc.send_signal(signal.SIGSTOP)
|
||||||
return proc
|
return proc
|
||||||
|
|
||||||
def start_camera(
|
def start_camera(self, camera: Camera, *args, preview: bool = False, **kwargs):
|
||||||
self, camera: FFmpegCamera, preview: bool = False, *args, **kwargs
|
assert isinstance(camera, FFmpegCamera)
|
||||||
):
|
|
||||||
super().start_camera(*args, camera=camera, preview=preview, **kwargs)
|
super().start_camera(*args, camera=camera, preview=preview, **kwargs)
|
||||||
if camera.object:
|
if camera.object:
|
||||||
camera.object.send_signal(signal.SIGCONT)
|
camera.object.send_signal(signal.SIGCONT)
|
||||||
|
|
||||||
def release_device(self, camera: FFmpegCamera):
|
def release_device(self, device: Camera):
|
||||||
if camera.object:
|
assert isinstance(device, FFmpegCamera)
|
||||||
camera.object.terminate()
|
if device.object:
|
||||||
if camera.object.stdout:
|
device.object.terminate()
|
||||||
camera.object.stdout.close()
|
if device.object.stdout:
|
||||||
camera.object = None
|
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:
|
if camera.object and camera.object.poll() is None:
|
||||||
try:
|
try:
|
||||||
camera.object.wait(timeout=camera.info.capture_timeout)
|
camera.object.wait(timeout=camera.info.capture_timeout)
|
||||||
except Exception as e:
|
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(
|
def capture_frame(self, device: Camera, *_, **__) -> Optional[ImageType]:
|
||||||
self, camera: FFmpegCamera, *args, **kwargs
|
assert isinstance(device, FFmpegCamera)
|
||||||
) -> Optional[ImageType]:
|
assert device.info.resolution, 'Resolution not set'
|
||||||
raw_size = camera.info.resolution[0] * camera.info.resolution[1] * 3
|
assert device.object.stdout, 'Camera not started'
|
||||||
data = camera.object.stdout.read(raw_size)
|
|
||||||
|
raw_size = device.info.resolution[0] * device.info.resolution[1] * 3
|
||||||
|
data = device.object.stdout.read(raw_size)
|
||||||
if len(data) < raw_size:
|
if len(data) < raw_size:
|
||||||
return
|
return
|
||||||
return Image.frombytes('RGB', camera.info.resolution, data)
|
return Image.frombytes('RGB', device.info.resolution, data)
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -3,7 +3,6 @@ import logging
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Optional, IO
|
from typing import Optional, IO
|
||||||
|
|
||||||
|
@ -63,8 +62,12 @@ class FileVideoWriter(VideoWriter, ABC):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, output_file: str, **kwargs):
|
def __init__(self, *args, output_file: str, **kwargs):
|
||||||
super().__init__(self, *args, **kwargs)
|
super().__init__(
|
||||||
self.output_file = os.path.abspath(os.path.expanduser(output_file))
|
self,
|
||||||
|
*args,
|
||||||
|
output_file=os.path.abspath(os.path.expanduser(output_file)),
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StreamWriter(VideoWriter, ABC):
|
class StreamWriter(VideoWriter, ABC):
|
||||||
|
|
Loading…
Reference in a new issue