Improve ffmpeg* typing (#108092)
This commit is contained in:
parent
25b7bb4a4f
commit
bc2acb3c0e
5 changed files with 71 additions and 53 deletions
|
@ -3,7 +3,9 @@ from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import re
|
import re
|
||||||
|
from typing import Generic, TypeVar
|
||||||
|
|
||||||
|
from haffmpeg.core import HAFFmpeg
|
||||||
from haffmpeg.tools import IMAGE_JPEG, FFVersion, ImageFrame
|
from haffmpeg.tools import IMAGE_JPEG, FFVersion, ImageFrame
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
@ -13,9 +15,10 @@ from homeassistant.const import (
|
||||||
EVENT_HOMEASSISTANT_START,
|
EVENT_HOMEASSISTANT_START,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
from homeassistant.core import Event, HomeAssistant, ServiceCall, callback
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.dispatcher import (
|
from homeassistant.helpers.dispatcher import (
|
||||||
|
SignalType,
|
||||||
async_dispatcher_connect,
|
async_dispatcher_connect,
|
||||||
async_dispatcher_send,
|
async_dispatcher_send,
|
||||||
)
|
)
|
||||||
|
@ -23,15 +26,17 @@ from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
|
|
||||||
|
_HAFFmpegT = TypeVar("_HAFFmpegT", bound=HAFFmpeg)
|
||||||
|
|
||||||
DOMAIN = "ffmpeg"
|
DOMAIN = "ffmpeg"
|
||||||
|
|
||||||
SERVICE_START = "start"
|
SERVICE_START = "start"
|
||||||
SERVICE_STOP = "stop"
|
SERVICE_STOP = "stop"
|
||||||
SERVICE_RESTART = "restart"
|
SERVICE_RESTART = "restart"
|
||||||
|
|
||||||
SIGNAL_FFMPEG_START = "ffmpeg.start"
|
SIGNAL_FFMPEG_START = SignalType[list[str] | None]("ffmpeg.start")
|
||||||
SIGNAL_FFMPEG_STOP = "ffmpeg.stop"
|
SIGNAL_FFMPEG_STOP = SignalType[list[str] | None]("ffmpeg.stop")
|
||||||
SIGNAL_FFMPEG_RESTART = "ffmpeg.restart"
|
SIGNAL_FFMPEG_RESTART = SignalType[list[str] | None]("ffmpeg.restart")
|
||||||
|
|
||||||
DATA_FFMPEG = "ffmpeg"
|
DATA_FFMPEG = "ffmpeg"
|
||||||
|
|
||||||
|
@ -66,7 +71,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
# Register service
|
# Register service
|
||||||
async def async_service_handle(service: ServiceCall) -> None:
|
async def async_service_handle(service: ServiceCall) -> None:
|
||||||
"""Handle service ffmpeg process."""
|
"""Handle service ffmpeg process."""
|
||||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
entity_ids: list[str] | None = service.data.get(ATTR_ENTITY_ID)
|
||||||
|
|
||||||
if service.service == SERVICE_START:
|
if service.service == SERVICE_START:
|
||||||
async_dispatcher_send(hass, SIGNAL_FFMPEG_START, entity_ids)
|
async_dispatcher_send(hass, SIGNAL_FFMPEG_START, entity_ids)
|
||||||
|
@ -128,20 +133,20 @@ async def async_get_image(
|
||||||
class FFmpegManager:
|
class FFmpegManager:
|
||||||
"""Helper for ha-ffmpeg."""
|
"""Helper for ha-ffmpeg."""
|
||||||
|
|
||||||
def __init__(self, hass, ffmpeg_bin):
|
def __init__(self, hass: HomeAssistant, ffmpeg_bin: str) -> None:
|
||||||
"""Initialize helper."""
|
"""Initialize helper."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self._cache = {}
|
self._cache = {} # type: ignore[var-annotated]
|
||||||
self._bin = ffmpeg_bin
|
self._bin = ffmpeg_bin
|
||||||
self._version = None
|
self._version: str | None = None
|
||||||
self._major_version = None
|
self._major_version: int | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def binary(self):
|
def binary(self) -> str:
|
||||||
"""Return ffmpeg binary from config."""
|
"""Return ffmpeg binary from config."""
|
||||||
return self._bin
|
return self._bin
|
||||||
|
|
||||||
async def async_get_version(self):
|
async def async_get_version(self) -> tuple[str | None, int | None]:
|
||||||
"""Return ffmpeg version."""
|
"""Return ffmpeg version."""
|
||||||
|
|
||||||
ffversion = FFVersion(self._bin)
|
ffversion = FFVersion(self._bin)
|
||||||
|
@ -156,7 +161,7 @@ class FFmpegManager:
|
||||||
return self._version, self._major_version
|
return self._version, self._major_version
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ffmpeg_stream_content_type(self):
|
def ffmpeg_stream_content_type(self) -> str:
|
||||||
"""Return HTTP content type for ffmpeg stream."""
|
"""Return HTTP content type for ffmpeg stream."""
|
||||||
if self._major_version is not None and self._major_version > 3:
|
if self._major_version is not None and self._major_version > 3:
|
||||||
return CONTENT_TYPE_MULTIPART.format("ffmpeg")
|
return CONTENT_TYPE_MULTIPART.format("ffmpeg")
|
||||||
|
@ -164,17 +169,17 @@ class FFmpegManager:
|
||||||
return CONTENT_TYPE_MULTIPART.format("ffserver")
|
return CONTENT_TYPE_MULTIPART.format("ffserver")
|
||||||
|
|
||||||
|
|
||||||
class FFmpegBase(Entity):
|
class FFmpegBase(Entity, Generic[_HAFFmpegT]):
|
||||||
"""Interface object for FFmpeg."""
|
"""Interface object for FFmpeg."""
|
||||||
|
|
||||||
_attr_should_poll = False
|
_attr_should_poll = False
|
||||||
|
|
||||||
def __init__(self, initial_state=True):
|
def __init__(self, ffmpeg: _HAFFmpegT, initial_state: bool = True) -> None:
|
||||||
"""Initialize ffmpeg base object."""
|
"""Initialize ffmpeg base object."""
|
||||||
self.ffmpeg = None
|
self.ffmpeg = ffmpeg
|
||||||
self.initial_state = initial_state
|
self.initial_state = initial_state
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Register dispatcher & events.
|
"""Register dispatcher & events.
|
||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
|
@ -199,18 +204,18 @@ class FFmpegBase(Entity):
|
||||||
self._async_register_events()
|
self._async_register_events()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return self.ffmpeg.is_running
|
return self.ffmpeg.is_running
|
||||||
|
|
||||||
async def _async_start_ffmpeg(self, entity_ids):
|
async def _async_start_ffmpeg(self, entity_ids: list[str] | None) -> None:
|
||||||
"""Start a FFmpeg process.
|
"""Start a FFmpeg process.
|
||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
async def _async_stop_ffmpeg(self, entity_ids):
|
async def _async_stop_ffmpeg(self, entity_ids: list[str] | None) -> None:
|
||||||
"""Stop a FFmpeg process.
|
"""Stop a FFmpeg process.
|
||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
|
@ -218,7 +223,7 @@ class FFmpegBase(Entity):
|
||||||
if entity_ids is None or self.entity_id in entity_ids:
|
if entity_ids is None or self.entity_id in entity_ids:
|
||||||
await self.ffmpeg.close()
|
await self.ffmpeg.close()
|
||||||
|
|
||||||
async def _async_restart_ffmpeg(self, entity_ids):
|
async def _async_restart_ffmpeg(self, entity_ids: list[str] | None) -> None:
|
||||||
"""Stop a FFmpeg process.
|
"""Stop a FFmpeg process.
|
||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
|
@ -228,10 +233,10 @@ class FFmpegBase(Entity):
|
||||||
await self._async_start_ffmpeg(None)
|
await self._async_start_ffmpeg(None)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_register_events(self):
|
def _async_register_events(self) -> None:
|
||||||
"""Register a FFmpeg process/device."""
|
"""Register a FFmpeg process/device."""
|
||||||
|
|
||||||
async def async_shutdown_handle(event):
|
async def async_shutdown_handle(event: Event) -> None:
|
||||||
"""Stop FFmpeg process."""
|
"""Stop FFmpeg process."""
|
||||||
await self._async_stop_ffmpeg(None)
|
await self._async_stop_ffmpeg(None)
|
||||||
|
|
||||||
|
@ -241,7 +246,7 @@ class FFmpegBase(Entity):
|
||||||
if not self.initial_state:
|
if not self.initial_state:
|
||||||
return
|
return
|
||||||
|
|
||||||
async def async_start_handle(event):
|
async def async_start_handle(event: Event) -> None:
|
||||||
"""Start FFmpeg process."""
|
"""Start FFmpeg process."""
|
||||||
await self._async_start_ffmpeg(None)
|
await self._async_start_ffmpeg(None)
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
|
@ -60,7 +60,7 @@ class FFmpegCamera(Camera):
|
||||||
self._input: str = config[CONF_INPUT]
|
self._input: str = config[CONF_INPUT]
|
||||||
self._extra_arguments: str = config[CONF_EXTRA_ARGUMENTS]
|
self._extra_arguments: str = config[CONF_EXTRA_ARGUMENTS]
|
||||||
|
|
||||||
async def stream_source(self):
|
async def stream_source(self) -> str:
|
||||||
"""Return the stream source."""
|
"""Return the stream source."""
|
||||||
return self._input.split(" ")[-1]
|
return self._input.split(" ")[-1]
|
||||||
|
|
||||||
|
@ -95,6 +95,6 @@ class FFmpegCamera(Camera):
|
||||||
await stream.close()
|
await stream.close()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
"""Return the name of this camera."""
|
"""Return the name of this camera."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
"""Provides a binary sensor which is a collection of ffmpeg tools."""
|
"""Provides a binary sensor which is a collection of ffmpeg tools."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, TypeVar
|
||||||
|
|
||||||
|
from haffmpeg.core import HAFFmpeg
|
||||||
import haffmpeg.sensor as ffmpeg_sensor
|
import haffmpeg.sensor as ffmpeg_sensor
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
@ -14,6 +17,7 @@ from homeassistant.components.ffmpeg import (
|
||||||
CONF_INITIAL_STATE,
|
CONF_INITIAL_STATE,
|
||||||
CONF_INPUT,
|
CONF_INPUT,
|
||||||
FFmpegBase,
|
FFmpegBase,
|
||||||
|
FFmpegManager,
|
||||||
get_ffmpeg_manager,
|
get_ffmpeg_manager,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_NAME, CONF_REPEAT
|
from homeassistant.const import CONF_NAME, CONF_REPEAT
|
||||||
|
@ -22,6 +26,8 @@ import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
|
_HAFFmpegT = TypeVar("_HAFFmpegT", bound=HAFFmpeg)
|
||||||
|
|
||||||
CONF_RESET = "reset"
|
CONF_RESET = "reset"
|
||||||
CONF_CHANGES = "changes"
|
CONF_CHANGES = "changes"
|
||||||
CONF_REPEAT_TIME = "repeat_time"
|
CONF_REPEAT_TIME = "repeat_time"
|
||||||
|
@ -63,43 +69,45 @@ async def async_setup_platform(
|
||||||
async_add_entities([entity])
|
async_add_entities([entity])
|
||||||
|
|
||||||
|
|
||||||
class FFmpegBinarySensor(FFmpegBase, BinarySensorEntity):
|
class FFmpegBinarySensor(FFmpegBase[_HAFFmpegT], BinarySensorEntity):
|
||||||
"""A binary sensor which use FFmpeg for noise detection."""
|
"""A binary sensor which use FFmpeg for noise detection."""
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, ffmpeg: _HAFFmpegT, config: dict[str, Any]) -> None:
|
||||||
"""Init for the binary sensor noise detection."""
|
"""Init for the binary sensor noise detection."""
|
||||||
super().__init__(config.get(CONF_INITIAL_STATE))
|
super().__init__(ffmpeg, config[CONF_INITIAL_STATE])
|
||||||
|
|
||||||
self._state = False
|
self._state: bool | None = False
|
||||||
self._config = config
|
self._config = config
|
||||||
self._name = config.get(CONF_NAME)
|
self._name: str = config[CONF_NAME]
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_callback(self, state):
|
def _async_callback(self, state: bool | None) -> None:
|
||||||
"""HA-FFmpeg callback for noise detection."""
|
"""HA-FFmpeg callback for noise detection."""
|
||||||
self._state = state
|
self._state = state
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self) -> bool | None:
|
||||||
"""Return true if the binary sensor is on."""
|
"""Return true if the binary sensor is on."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
"""Return the name of the entity."""
|
"""Return the name of the entity."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
|
|
||||||
class FFmpegMotion(FFmpegBinarySensor):
|
class FFmpegMotion(FFmpegBinarySensor[ffmpeg_sensor.SensorMotion]):
|
||||||
"""A binary sensor which use FFmpeg for noise detection."""
|
"""A binary sensor which use FFmpeg for noise detection."""
|
||||||
|
|
||||||
def __init__(self, hass, manager, config):
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, manager: FFmpegManager, config: dict[str, Any]
|
||||||
|
) -> None:
|
||||||
"""Initialize FFmpeg motion binary sensor."""
|
"""Initialize FFmpeg motion binary sensor."""
|
||||||
super().__init__(config)
|
ffmpeg = ffmpeg_sensor.SensorMotion(manager.binary, self._async_callback)
|
||||||
self.ffmpeg = ffmpeg_sensor.SensorMotion(manager.binary, self._async_callback)
|
super().__init__(ffmpeg, config)
|
||||||
|
|
||||||
async def _async_start_ffmpeg(self, entity_ids):
|
async def _async_start_ffmpeg(self, entity_ids: list[str] | None) -> None:
|
||||||
"""Start a FFmpeg instance.
|
"""Start a FFmpeg instance.
|
||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
|
@ -109,19 +117,19 @@ class FFmpegMotion(FFmpegBinarySensor):
|
||||||
|
|
||||||
# init config
|
# init config
|
||||||
self.ffmpeg.set_options(
|
self.ffmpeg.set_options(
|
||||||
time_reset=self._config.get(CONF_RESET),
|
time_reset=self._config[CONF_RESET],
|
||||||
time_repeat=self._config.get(CONF_REPEAT_TIME, 0),
|
time_repeat=self._config.get(CONF_REPEAT_TIME, 0),
|
||||||
repeat=self._config.get(CONF_REPEAT, 0),
|
repeat=self._config.get(CONF_REPEAT, 0),
|
||||||
changes=self._config.get(CONF_CHANGES),
|
changes=self._config[CONF_CHANGES],
|
||||||
)
|
)
|
||||||
|
|
||||||
# run
|
# run
|
||||||
await self.ffmpeg.open_sensor(
|
await self.ffmpeg.open_sensor(
|
||||||
input_source=self._config.get(CONF_INPUT),
|
input_source=self._config[CONF_INPUT],
|
||||||
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
|
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self) -> BinarySensorDeviceClass:
|
||||||
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
||||||
return BinarySensorDeviceClass.MOTION
|
return BinarySensorDeviceClass.MOTION
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
"""Provides a binary sensor which is a collection of ffmpeg tools."""
|
"""Provides a binary sensor which is a collection of ffmpeg tools."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import haffmpeg.sensor as ffmpeg_sensor
|
import haffmpeg.sensor as ffmpeg_sensor
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
@ -13,6 +15,7 @@ from homeassistant.components.ffmpeg import (
|
||||||
CONF_INITIAL_STATE,
|
CONF_INITIAL_STATE,
|
||||||
CONF_INPUT,
|
CONF_INPUT,
|
||||||
CONF_OUTPUT,
|
CONF_OUTPUT,
|
||||||
|
FFmpegManager,
|
||||||
get_ffmpeg_manager,
|
get_ffmpeg_manager,
|
||||||
)
|
)
|
||||||
from homeassistant.components.ffmpeg_motion.binary_sensor import FFmpegBinarySensor
|
from homeassistant.components.ffmpeg_motion.binary_sensor import FFmpegBinarySensor
|
||||||
|
@ -59,16 +62,18 @@ async def async_setup_platform(
|
||||||
async_add_entities([entity])
|
async_add_entities([entity])
|
||||||
|
|
||||||
|
|
||||||
class FFmpegNoise(FFmpegBinarySensor):
|
class FFmpegNoise(FFmpegBinarySensor[ffmpeg_sensor.SensorNoise]):
|
||||||
"""A binary sensor which use FFmpeg for noise detection."""
|
"""A binary sensor which use FFmpeg for noise detection."""
|
||||||
|
|
||||||
def __init__(self, hass, manager, config):
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, manager: FFmpegManager, config: dict[str, Any]
|
||||||
|
) -> None:
|
||||||
"""Initialize FFmpeg noise binary sensor."""
|
"""Initialize FFmpeg noise binary sensor."""
|
||||||
|
|
||||||
super().__init__(config)
|
ffmpeg = ffmpeg_sensor.SensorNoise(manager.binary, self._async_callback)
|
||||||
self.ffmpeg = ffmpeg_sensor.SensorNoise(manager.binary, self._async_callback)
|
super().__init__(ffmpeg, config)
|
||||||
|
|
||||||
async def _async_start_ffmpeg(self, entity_ids):
|
async def _async_start_ffmpeg(self, entity_ids: list[str] | None) -> None:
|
||||||
"""Start a FFmpeg instance.
|
"""Start a FFmpeg instance.
|
||||||
|
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
|
@ -77,18 +82,18 @@ class FFmpegNoise(FFmpegBinarySensor):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.ffmpeg.set_options(
|
self.ffmpeg.set_options(
|
||||||
time_duration=self._config.get(CONF_DURATION),
|
time_duration=self._config[CONF_DURATION],
|
||||||
time_reset=self._config.get(CONF_RESET),
|
time_reset=self._config[CONF_RESET],
|
||||||
peak=self._config.get(CONF_PEAK),
|
peak=self._config[CONF_PEAK],
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.ffmpeg.open_sensor(
|
await self.ffmpeg.open_sensor(
|
||||||
input_source=self._config.get(CONF_INPUT),
|
input_source=self._config[CONF_INPUT],
|
||||||
output_dest=self._config.get(CONF_OUTPUT),
|
output_dest=self._config.get(CONF_OUTPUT),
|
||||||
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
|
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self) -> BinarySensorDeviceClass:
|
||||||
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
||||||
return BinarySensorDeviceClass.SOUND
|
return BinarySensorDeviceClass.SOUND
|
||||||
|
|
|
@ -54,7 +54,7 @@ class MockFFmpegDev(ffmpeg.FFmpegBase):
|
||||||
|
|
||||||
def __init__(self, hass, initial_state=True, entity_id="test.ffmpeg_device"):
|
def __init__(self, hass, initial_state=True, entity_id="test.ffmpeg_device"):
|
||||||
"""Initialize mock."""
|
"""Initialize mock."""
|
||||||
super().__init__(initial_state)
|
super().__init__(None, initial_state)
|
||||||
|
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.entity_id = entity_id
|
self.entity_id = entity_id
|
||||||
|
|
Loading…
Add table
Reference in a new issue