[ffmpeg] Use new 1.0 version / migrate all asyncio (#5464)

* [ffmpeg] Use new 1.0 version / migrate all asyncio

* fix lint

* fix import

* Add new service to binary_sensors

* fix lint
This commit is contained in:
Pascal Vizeli 2017-01-21 06:56:22 +01:00 committed by Paulus Schoutsen
parent dec2ddb393
commit b2203f7f41
5 changed files with 190 additions and 122 deletions

View File

@ -4,8 +4,9 @@ Provides a binary sensor which is a collection of ffmpeg tools.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ffmpeg/
"""
import asyncio
import logging
from os import path
import os
import voluptuous as vol
@ -13,17 +14,22 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, DOMAIN)
from homeassistant.components.ffmpeg import (
get_binary, run_test, CONF_INPUT, CONF_OUTPUT, CONF_EXTRA_ARGUMENTS)
DATA_FFMPEG, CONF_INPUT, CONF_OUTPUT, CONF_EXTRA_ARGUMENTS)
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_NAME,
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START, CONF_NAME,
ATTR_ENTITY_ID)
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
SERVICE_START = 'ffmpeg_start'
SERVICE_STOP = 'ffmpeg_stop'
SERVICE_RESTART = 'ffmpeg_restart'
DATA_FFMPEG_DEVICE = 'ffmpeg_binary_sensor'
FFMPEG_SENSOR_NOISE = 'noise'
FFMPEG_SENSOR_MOTION = 'motion'
@ -32,6 +38,7 @@ MAP_FFMPEG_BIN = [
FFMPEG_SENSOR_MOTION
]
CONF_INITIAL_STATE = 'initial_state'
CONF_TOOL = 'tool'
CONF_PEAK = 'peak'
CONF_DURATION = 'duration'
@ -41,10 +48,12 @@ CONF_REPEAT = 'repeat'
CONF_REPEAT_TIME = 'repeat_time'
DEFAULT_NAME = 'FFmpeg'
DEFAULT_INIT_STATE = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TOOL): vol.In(MAP_FFMPEG_BIN),
vol.Required(CONF_INPUT): cv.string,
vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
vol.Optional(CONF_OUTPUT): cv.string,
@ -61,7 +70,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.All(vol.Coerce(int), vol.Range(min=0)),
})
SERVICE_RESTART_SCHEMA = vol.Schema({
SERVICE_FFMPEG_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
@ -72,86 +81,125 @@ def restart(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_RESTART, data)
# list of all ffmpeg sensors
DEVICES = []
def setup_platform(hass, config, add_entities, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Create the binary sensor."""
from haffmpeg import SensorNoise, SensorMotion
# check source
if not run_test(hass, config.get(CONF_INPUT)):
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)):
return
# generate sensor object
if config.get(CONF_TOOL) == FFMPEG_SENSOR_NOISE:
entity = FFmpegNoise(SensorNoise, config)
entity = FFmpegNoise(hass, SensorNoise, config)
else:
entity = FFmpegMotion(SensorMotion, config)
entity = FFmpegMotion(hass, SensorMotion, config)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, entity.shutdown_ffmpeg)
@asyncio.coroutine
def async_shutdown(event):
"""Stop ffmpeg."""
yield from entity.async_shutdown_ffmpeg()
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, async_shutdown)
# start on startup
if config.get(CONF_INITIAL_STATE):
@asyncio.coroutine
def async_start(event):
"""Start ffmpeg."""
yield from entity.async_start_ffmpeg()
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_start)
# add to system
add_entities([entity])
DEVICES.append(entity)
yield from async_add_devices([entity])
# exists service?
if hass.services.has_service(DOMAIN, SERVICE_RESTART):
hass.data[DATA_FFMPEG_DEVICE].append(entity)
return
hass.data[DATA_FFMPEG_DEVICE] = [entity]
descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml'))
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file,
os.path.join(os.path.dirname(__file__), 'services.yaml'))
# register service
def _service_handle_restart(service):
@asyncio.coroutine
def async_service_handle(service):
"""Handle service binary_sensor.ffmpeg_restart."""
entity_ids = service.data.get('entity_id')
if entity_ids:
_devices = [device for device in DEVICES
_devices = [device for device in hass.data[DATA_FFMPEG_DEVICE]
if device.entity_id in entity_ids]
else:
_devices = DEVICES
_devices = hass.data[DATA_FFMPEG_DEVICE]
tasks = []
for device in _devices:
device.restart_ffmpeg()
if service.service == SERVICE_START:
tasks.append(device.async_start_ffmpeg())
elif service.service == SERVICE_STOP:
tasks.append(device.async_shutdown_ffmpeg())
else:
tasks.append(device.async_restart_ffmpeg())
hass.services.register(DOMAIN, SERVICE_RESTART,
_service_handle_restart,
descriptions.get(SERVICE_RESTART),
schema=SERVICE_RESTART_SCHEMA)
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_START, async_service_handle,
descriptions.get(SERVICE_START), schema=SERVICE_FFMPEG_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_STOP, async_service_handle,
descriptions.get(SERVICE_STOP), schema=SERVICE_FFMPEG_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_RESTART, async_service_handle,
descriptions.get(SERVICE_RESTART), schema=SERVICE_FFMPEG_SCHEMA)
class FFmpegBinarySensor(BinarySensorDevice):
"""A binary sensor which use ffmpeg for noise detection."""
def __init__(self, ffobj, config):
def __init__(self, hass, ffobj, config):
"""Constructor for binary sensor noise detection."""
self._manager = hass.data[DATA_FFMPEG]
self._state = False
self._config = config
self._name = config.get(CONF_NAME)
self._ffmpeg = ffobj(get_binary(), self._callback)
self._ffmpeg = ffobj(
self._manager.binary, hass.loop, self._async_callback)
self._start_ffmpeg(config)
def _callback(self, state):
def _async_callback(self, state):
"""HA-FFmpeg callback for noise detection."""
self._state = state
self.schedule_update_ha_state()
self.hass.async_add_job(self.async_update_ha_state())
def _start_ffmpeg(self, config):
"""Start a FFmpeg instance."""
raise NotImplementedError
def async_start_ffmpeg(self):
"""Start a FFmpeg instance.
def shutdown_ffmpeg(self, event):
"""For STOP event to shutdown ffmpeg."""
self._ffmpeg.close()
This method must be run in the event loop and returns a coroutine.
"""
raise NotImplementedError()
def restart_ffmpeg(self):
"""Restart ffmpeg with new config."""
self._ffmpeg.close()
self._start_ffmpeg(self._config)
def async_shutdown_ffmpeg(self):
"""For STOP event to shutdown ffmpeg.
This method must be run in the event loop and returns a coroutine.
"""
return self._ffmpeg.close()
@asyncio.coroutine
def async_restart_ffmpeg(self):
"""Restart processing."""
yield from self.async_shutdown_ffmpeg()
yield from self.async_start_ffmpeg()
@property
def is_on(self):
@ -177,20 +225,23 @@ class FFmpegBinarySensor(BinarySensorDevice):
class FFmpegNoise(FFmpegBinarySensor):
"""A binary sensor which use ffmpeg for noise detection."""
def _start_ffmpeg(self, config):
"""Start a FFmpeg instance."""
def async_start_ffmpeg(self):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
"""
# init config
self._ffmpeg.set_options(
time_duration=config.get(CONF_DURATION),
time_reset=config.get(CONF_RESET),
peak=config.get(CONF_PEAK),
time_duration=self._config.get(CONF_DURATION),
time_reset=self._config.get(CONF_RESET),
peak=self._config.get(CONF_PEAK),
)
# run
self._ffmpeg.open_sensor(
input_source=config.get(CONF_INPUT),
output_dest=config.get(CONF_OUTPUT),
extra_cmd=config.get(CONF_EXTRA_ARGUMENTS),
return self._ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
output_dest=self._config.get(CONF_OUTPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
)
@property
@ -202,20 +253,23 @@ class FFmpegNoise(FFmpegBinarySensor):
class FFmpegMotion(FFmpegBinarySensor):
"""A binary sensor which use ffmpeg for noise detection."""
def _start_ffmpeg(self, config):
"""Start a FFmpeg instance."""
def async_start_ffmpeg(self):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
"""
# init config
self._ffmpeg.set_options(
time_reset=config.get(CONF_RESET),
time_repeat=config.get(CONF_REPEAT_TIME),
repeat=config.get(CONF_REPEAT),
changes=config.get(CONF_CHANGES),
time_reset=self._config.get(CONF_RESET),
time_repeat=self._config.get(CONF_REPEAT_TIME),
repeat=self._config.get(CONF_REPEAT),
changes=self._config.get(CONF_CHANGES),
)
# run
self._ffmpeg.open_sensor(
input_source=config.get(CONF_INPUT),
extra_cmd=config.get(CONF_EXTRA_ARGUMENTS),
return self._ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
)
@property

View File

@ -1,7 +1,23 @@
# Describes the format for available binary_sensor services
ffmpeg_start:
description: Send a start command to a ffmpeg based sensor.
fields:
entity_id:
description: Name(s) of entites that will start. Platform dependent.
example: 'binary_sensor.ffmpeg_noise'
ffmpeg_stop:
description: Send a stop command to a ffmpeg based sensor.
fields:
entity_id:
description: Name(s) of entites that will stop. Platform dependent.
example: 'binary_sensor.ffmpeg_noise'
ffmpeg_restart:
description: Send a restart command to a ffmpeg based sensor (party mode).
description: Send a restart command to a ffmpeg based sensor.
fields:
entity_id:

View File

@ -12,10 +12,9 @@ from aiohttp import web
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import (
async_run_test, get_binary, CONF_INPUT, CONF_EXTRA_ARGUMENTS)
DATA_FFMPEG, CONF_INPUT, CONF_EXTRA_ARGUMENTS)
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME
from homeassistant.util.async import run_coroutine_threadsafe
DEPENDENCIES = ['ffmpeg']
@ -33,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a FFmpeg Camera."""
if not async_run_test(hass, config.get(CONF_INPUT)):
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)):
return
yield from async_add_devices([FFmpegCamera(hass, config)])
@ -44,20 +43,17 @@ class FFmpegCamera(Camera):
def __init__(self, hass, config):
"""Initialize a FFmpeg camera."""
super().__init__()
self._manager = hass.data[DATA_FFMPEG]
self._name = config.get(CONF_NAME)
self._input = config.get(CONF_INPUT)
self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS)
def camera_image(self):
"""Return bytes of camera image."""
return run_coroutine_threadsafe(
self.async_camera_image(), self.hass.loop).result()
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageSingleAsync, IMAGE_JPEG
ffmpeg = ImageSingleAsync(get_binary(), loop=self.hass.loop)
from haffmpeg import ImageFrame, IMAGE_JPEG
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
image = yield from ffmpeg.get_image(
self._input, output_format=IMAGE_JPEG,
@ -67,9 +63,9 @@ class FFmpegCamera(Camera):
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpegAsync
from haffmpeg import CameraMjpeg
stream = CameraMjpegAsync(get_binary(), loop=self.hass.loop)
stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop)
yield from stream.open_camera(
self._input, extra_cmd=self._extra_arguments)

View File

@ -10,13 +10,14 @@ import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe
DOMAIN = 'ffmpeg'
REQUIREMENTS = ["ha-ffmpeg==0.15"]
REQUIREMENTS = ["ha-ffmpeg==1.0"]
_LOGGER = logging.getLogger(__name__)
DATA_FFMPEG = 'ffmpeg'
CONF_INPUT = 'input'
CONF_FFMPEG_BIN = 'ffmpeg_bin'
CONF_EXTRA_ARGUMENTS = 'extra_arguments'
@ -34,53 +35,54 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
FFMPEG_CONFIG = {
CONF_FFMPEG_BIN: DEFAULT_BINARY,
CONF_RUN_TEST: DEFAULT_RUN_TEST,
}
FFMPEG_TEST_CACHE = {}
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Setup the FFmpeg component."""
if DOMAIN in config:
FFMPEG_CONFIG.update(config.get(DOMAIN))
conf = config.get(DOMAIN, {})
hass.data[DATA_FFMPEG] = FFmpegManager(
hass,
conf.get(CONF_FFMPEG_BIN, DEFAULT_BINARY),
conf.get(CONF_RUN_TEST, DEFAULT_RUN_TEST)
)
return True
def get_binary():
"""Return ffmpeg binary from config.
class FFmpegManager(object):
"""Helper for ha-ffmpeg."""
Async friendly.
"""
return FFMPEG_CONFIG.get(CONF_FFMPEG_BIN)
def run_test(hass, input_source):
"""Run test on this input. TRUE is deactivate or run correct."""
return run_coroutine_threadsafe(
async_run_test(hass, input_source), hass.loop).result()
def __init__(self, hass, ffmpeg_bin, run_test):
"""Initialize helper."""
self.hass = hass
self._cache = {}
self._bin = ffmpeg_bin
self._run_test = run_test
@property
def binary(self):
"""Return ffmpeg binary from config."""
return self._bin
@asyncio.coroutine
def async_run_test(hass, input_source):
def async_run_test(self, input_source):
"""Run test on this input. TRUE is deactivate or run correct.
This method must be run in the event loop.
"""
from haffmpeg import TestAsync
from haffmpeg import Test
if FFMPEG_CONFIG.get(CONF_RUN_TEST):
if self._run_test:
# if in cache
if input_source in FFMPEG_TEST_CACHE:
return FFMPEG_TEST_CACHE[input_source]
if input_source in self._cache:
return self._cache[input_source]
# run test
ffmpeg_test = TestAsync(get_binary(), loop=hass.loop)
ffmpeg_test = Test(self.binary, loop=self.hass.loop)
success = yield from ffmpeg_test.run_test(input_source)
if not success:
_LOGGER.error("FFmpeg '%s' test fails!", input_source)
FFMPEG_TEST_CACHE[input_source] = False
self._cache[input_source] = False
return False
FFMPEG_TEST_CACHE[input_source] = True
self._cache[input_source] = True
return True

View File

@ -170,7 +170,7 @@ googlemaps==2.4.4
gps3==0.33.3
# homeassistant.components.ffmpeg
ha-ffmpeg==0.15
ha-ffmpeg==1.0
# homeassistant.components.media_player.philips_js
ha-philipsjs==0.0.1