1 """Support for FFmpeg."""
3 from __future__
import annotations
8 from haffmpeg.core
import HAFFmpeg
9 from haffmpeg.tools
import IMAGE_JPEG, FFVersion, ImageFrame
10 from propcache
import cached_property
11 import voluptuous
as vol
15 CONTENT_TYPE_MULTIPART,
16 EVENT_HOMEASSISTANT_START,
17 EVENT_HOMEASSISTANT_STOP,
22 async_dispatcher_connect,
23 async_dispatcher_send,
33 SERVICE_START =
"start"
35 SERVICE_RESTART =
"restart"
37 SIGNAL_FFMPEG_START = SignalType[list[str] |
None](
"ffmpeg.start")
38 SIGNAL_FFMPEG_STOP = SignalType[list[str] |
None](
"ffmpeg.stop")
39 SIGNAL_FFMPEG_RESTART = SignalType[list[str] |
None](
"ffmpeg.restart")
41 DATA_FFMPEG =
"ffmpeg"
43 CONF_INITIAL_STATE =
"initial_state"
45 CONF_FFMPEG_BIN =
"ffmpeg_bin"
46 CONF_EXTRA_ARGUMENTS =
"extra_arguments"
47 CONF_OUTPUT =
"output"
49 DEFAULT_BINARY =
"ffmpeg"
55 OFFICIAL_IMAGE_VERSION =
"6.0"
57 CONFIG_SCHEMA = vol.Schema(
60 {vol.Optional(CONF_FFMPEG_BIN, default=DEFAULT_BINARY): cv.string}
63 extra=vol.ALLOW_EXTRA,
66 SERVICE_FFMPEG_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids})
69 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
70 """Set up the FFmpeg component."""
71 conf = config.get(DOMAIN, {})
73 manager =
FFmpegManager(hass, conf.get(CONF_FFMPEG_BIN, DEFAULT_BINARY))
75 await manager.async_get_version()
78 async
def async_service_handle(service: ServiceCall) ->
None:
79 """Handle service ffmpeg process."""
80 entity_ids: list[str] |
None = service.data.get(ATTR_ENTITY_ID)
82 if service.service == SERVICE_START:
84 elif service.service == SERVICE_STOP:
89 hass.services.async_register(
90 DOMAIN, SERVICE_START, async_service_handle, schema=SERVICE_FFMPEG_SCHEMA
93 hass.services.async_register(
94 DOMAIN, SERVICE_STOP, async_service_handle, schema=SERVICE_FFMPEG_SCHEMA
97 hass.services.async_register(
98 DOMAIN, SERVICE_RESTART, async_service_handle, schema=SERVICE_FFMPEG_SCHEMA
101 hass.data[DATA_FFMPEG] = manager
107 """Return the FFmpegManager."""
108 if DATA_FFMPEG
not in hass.data:
109 raise ValueError(
"ffmpeg component not initialized")
110 return hass.data[DATA_FFMPEG]
117 output_format: str = IMAGE_JPEG,
118 extra_cmd: str |
None =
None,
119 width: int |
None =
None,
120 height: int |
None =
None,
122 """Get an image from a frame of an RTSP stream."""
123 manager = hass.data[DATA_FFMPEG]
124 ffmpeg = ImageFrame(manager.binary)
126 if width
and height
and (extra_cmd
is None or "-s" not in extra_cmd):
127 size_cmd = f
"-s {width}x{height}"
128 if extra_cmd
is None:
131 extra_cmd +=
" " + size_cmd
133 return await asyncio.shield(
134 ffmpeg.get_image(input_source, output_format=output_format, extra_cmd=extra_cmd)
139 """Helper for ha-ffmpeg."""
141 def __init__(self, hass: HomeAssistant, ffmpeg_bin: str) ->
None:
142 """Initialize helper."""
146 self.
_version_version: str |
None =
None
151 """Return ffmpeg binary from config."""
155 """Return ffmpeg version."""
161 (version := await FFVersion(self.
_bin_bin).get_version())
162 and (result := re.search(
r"(\d+)\.", version))
163 and (major_version :=
int(result.group(1)))
172 """Return HTTP content type for ffmpeg stream."""
174 return CONTENT_TYPE_MULTIPART.format(
"ffmpeg")
176 return CONTENT_TYPE_MULTIPART.format(
"ffserver")
180 """Interface object for FFmpeg."""
182 _attr_should_poll =
False
184 def __init__(self, ffmpeg: _HAFFmpegT, initial_state: bool =
True) ->
None:
185 """Initialize ffmpeg base object."""
187 self.initial_state = initial_state
190 """Register dispatcher & events.
192 This method is a coroutine.
194 self.async_on_remove(
196 self.hass, SIGNAL_FFMPEG_START, self._async_start_ffmpeg
199 self.async_on_remove(
201 self.hass, SIGNAL_FFMPEG_STOP, self._async_stop_ffmpeg
204 self.async_on_remove(
206 self.hass, SIGNAL_FFMPEG_RESTART, self._async_restart_ffmpeg
211 self._async_register_events()
215 """Return True if entity is available."""
216 return self.ffmpeg.is_running
219 """Start a FFmpeg process.
221 This method is a coroutine.
223 raise NotImplementedError
226 """Stop a FFmpeg process.
228 This method is a coroutine.
230 if entity_ids
is None or self.entity_id
in entity_ids:
231 await self.ffmpeg.close()
234 """Stop a FFmpeg process.
236 This method is a coroutine.
238 if entity_ids
is None or self.entity_id
in entity_ids:
239 await self._async_stop_ffmpeg(
None)
240 await self._async_start_ffmpeg(
None)
244 """Register a FFmpeg process/device."""
246 async
def async_shutdown_handle(event: Event) ->
None:
247 """Stop FFmpeg process."""
248 await self._async_stop_ffmpeg(
None)
250 self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_shutdown_handle)
253 if not self.initial_state:
256 async
def async_start_handle(event: Event) ->
None:
257 """Start FFmpeg process."""
258 await self._async_start_ffmpeg(
None)
259 self.async_write_ha_state()
261 self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, async_start_handle)
str ffmpeg_stream_content_type(self)
None __init__(self, HomeAssistant hass, str ffmpeg_bin)
tuple[str|None, int|None] async_get_version(self)
None _async_register_events(self)
None __init__(self, _HAFFmpegT ffmpeg, bool initial_state=True)
None _async_restart_ffmpeg(self, list[str]|None entity_ids)
None _async_stop_ffmpeg(self, list[str]|None entity_ids)
None async_added_to_hass(self)
bool async_setup(HomeAssistant hass, ConfigType config)
None _async_start_ffmpeg(self, list[str]|None entity_ids)
FFmpegManager get_ffmpeg_manager(HomeAssistant hass)
bytes|None async_get_image(HomeAssistant hass, str input_source, str output_format=IMAGE_JPEG, str|None extra_cmd=None, int|None width=None, int|None height=None)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)