From 98c77dc08f96e9ccfccb4c9dd505d399495b6904 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 9 Aug 2016 02:34:46 +0200 Subject: [PATCH] Add ffmpeg camera platform support (#2755) --- .coveragerc | 1 + homeassistant/components/camera/ffmpeg.py | 75 +++++++++++++++++++++++ homeassistant/components/camera/mjpeg.py | 25 ++++---- requirements_all.txt | 3 + 4 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 homeassistant/components/camera/ffmpeg.py diff --git a/.coveragerc b/.coveragerc index dd270beb5d7..388698f26f3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -100,6 +100,7 @@ omit = homeassistant/components/binary_sensor/rest.py homeassistant/components/browser.py homeassistant/components/camera/bloomsky.py + homeassistant/components/camera/ffmpeg.py homeassistant/components/camera/foscam.py homeassistant/components/camera/generic.py homeassistant/components/camera/mjpeg.py diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/camera/ffmpeg.py new file mode 100644 index 00000000000..38fb6d7328f --- /dev/null +++ b/homeassistant/components/camera/ffmpeg.py @@ -0,0 +1,75 @@ +""" +Support for Cameras with FFmpeg as decoder. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/camera.ffmpeg/ +""" +import logging +from contextlib import closing + +import voluptuous as vol + +from homeassistant.components.camera import Camera +from homeassistant.components.camera.mjpeg import extract_image_from_mjpeg +import homeassistant.helpers.config_validation as cv +from homeassistant.const import CONF_NAME, CONF_PLATFORM + +REQUIREMENTS = ["ha-ffmpeg==0.4"] + +CONF_INPUT = 'input' +CONF_FFMPEG_BIN = 'ffmpeg_bin' +CONF_EXTRA_ARGUMENTS = 'extra_arguments' + +PLATFORM_SCHEMA = vol.Schema({ + vol.Required(CONF_PLATFORM): "ffmpeg", + vol.Optional(CONF_NAME, default="FFmpeg"): cv.string, + vol.Required(CONF_INPUT): cv.string, + vol.Optional(CONF_FFMPEG_BIN, default="ffmpeg"): cv.string, + vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, +}) + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """Setup a FFmpeg Camera.""" + add_devices_callback([FFmpegCamera(config)]) + + +class FFmpegCamera(Camera): + """An implementation of an IP camera that is reachable over a URL.""" + + def __init__(self, config): + """Initialize a FFmpeg camera.""" + super().__init__() + self._name = config.get(CONF_NAME) + self._input = config.get(CONF_INPUT) + self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS) + self._ffmpeg_bin = config.get(CONF_FFMPEG_BIN) + + def _ffmpeg_stream(self): + """Return a FFmpeg process object.""" + from haffmpeg import CameraMjpeg + + ffmpeg = CameraMjpeg(self._ffmpeg_bin) + ffmpeg.open_camera(self._input, extra_cmd=self._extra_arguments) + return ffmpeg + + def camera_image(self): + """Return a still image response from the camera.""" + with closing(self._ffmpeg_stream()) as stream: + return extract_image_from_mjpeg(stream) + + def mjpeg_stream(self, response): + """Generate an HTTP MJPEG stream from the camera.""" + stream = self._ffmpeg_stream() + return response( + stream, + mimetype='multipart/x-mixed-replace;boundary=ffserver', + direct_passthrough=True + ) + + @property + def name(self): + """Return the name of this camera.""" + return self._name diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index b7f31404a8f..c14a3a6c69e 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -28,6 +28,18 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): add_devices_callback([MjpegCamera(config)]) +def extract_image_from_mjpeg(stream): + """Take in a mjpeg stream object, return the jpg from it.""" + data = b'' + for chunk in stream: + data += chunk + jpg_start = data.find(b'\xff\xd8') + jpg_end = data.find(b'\xff\xd9') + if jpg_start != -1 and jpg_end != -1: + jpg = data[jpg_start:jpg_end + 2] + return jpg + + # pylint: disable=too-many-instance-attributes class MjpegCamera(Camera): """An implementation of an IP camera that is reachable over a URL.""" @@ -52,19 +64,8 @@ class MjpegCamera(Camera): def camera_image(self): """Return a still image response from the camera.""" - def process_response(response): - """Take in a response object, return the jpg from it.""" - data = b'' - for chunk in response.iter_content(1024): - data += chunk - jpg_start = data.find(b'\xff\xd8') - jpg_end = data.find(b'\xff\xd9') - if jpg_start != -1 and jpg_end != -1: - jpg = data[jpg_start:jpg_end + 2] - return jpg - with closing(self.camera_stream()) as response: - return process_response(response) + return extract_image_from_mjpeg(response.iter_content(1024)) def mjpeg_stream(self, response): """Generate an HTTP MJPEG stream from the camera.""" diff --git a/requirements_all.txt b/requirements_all.txt index 715cd96d4d4..a06a01da2ec 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -88,6 +88,9 @@ gntp==1.0.3 # homeassistant.components.sensor.google_travel_time googlemaps==2.4.4 +# homeassistant.components.camera.ffmpeg +ha-ffmpeg==0.4 + # homeassistant.components.mqtt.server hbmqtt==0.7.1