Speed up ffmpeg setup (#113496)

* Speed up ffmpeg setup

Only check for old versions of ffmpeg if is_official_image
is False

* Speed up ffmpeg setup

Only check for old versions of ffmpeg if is_official_image
is False

* Speed up ffmpeg setup

Only check for old versions of ffmpeg if is_official_image
is False

* Speed up ffmpeg setup

Only check for old versions of ffmpeg if is_official_image
is False

* Speed up ffmpeg setup

Only check for old versions of ffmpeg if is_official_image
is False

* Speed up ffmpeg setup

Only check for old versions of ffmpeg if is_official_image
is False

* adjust

* adjust

* twea

* Update homeassistant/components/ffmpeg/__init__.py

* forgot about the mock in conftest for comps
pull/113540/head
J. Nick Koston 2024-03-15 03:05:42 -10:00 committed by GitHub
parent 1c938f6422
commit c69ab425c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 65 additions and 12 deletions

View File

@ -4,7 +4,7 @@ from __future__ import annotations
import asyncio import asyncio
import re import re
from typing import Generic, TypeVar from typing import TYPE_CHECKING, Generic, TypeVar
from haffmpeg.core import HAFFmpeg from haffmpeg.core import HAFFmpeg
from haffmpeg.tools import IMAGE_JPEG, FFVersion, ImageFrame from haffmpeg.tools import IMAGE_JPEG, FFVersion, ImageFrame
@ -24,9 +24,16 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_send, async_dispatcher_send,
) )
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.system_info import is_official_image
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
if TYPE_CHECKING:
from functools import cached_property
else:
from homeassistant.backports.functools import cached_property
_HAFFmpegT = TypeVar("_HAFFmpegT", bound=HAFFmpeg) _HAFFmpegT = TypeVar("_HAFFmpegT", bound=HAFFmpeg)
DOMAIN = "ffmpeg" DOMAIN = "ffmpeg"
@ -49,6 +56,12 @@ CONF_OUTPUT = "output"
DEFAULT_BINARY = "ffmpeg" DEFAULT_BINARY = "ffmpeg"
# Currently we only care if the version is < 3
# because we use a different content-type
# It is only important to update this version if the
# content-type changes again in the future
OFFICIAL_IMAGE_VERSION = "6.0"
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: vol.Schema( DOMAIN: vol.Schema(
@ -142,26 +155,28 @@ class FFmpegManager:
self._version: str | None = None self._version: str | None = None
self._major_version: int | None = None self._major_version: int | None = None
@property @cached_property
def binary(self) -> str: 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) -> tuple[str | None, int | None]: async def async_get_version(self) -> tuple[str | None, int | None]:
"""Return ffmpeg version.""" """Return ffmpeg version."""
if self._version is None:
ffversion = FFVersion(self._bin) if is_official_image():
self._version = await ffversion.get_version() self._version = OFFICIAL_IMAGE_VERSION
self._major_version = int(self._version.split(".")[0])
self._major_version = None elif (
if self._version is not None: (version := await FFVersion(self._bin).get_version())
result = re.search(r"(\d+)\.", self._version) and (result := re.search(r"(\d+)\.", version))
if result is not None: and (major_version := int(result.group(1)))
self._major_version = int(result.group(1)) ):
self._version = version
self._major_version = major_version
return self._version, self._major_version return self._version, self._major_version
@property @cached_property
def ffmpeg_stream_content_type(self) -> str: 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:

View File

@ -8,6 +8,7 @@ from homeassistant.components.ffmpeg import (
SERVICE_RESTART, SERVICE_RESTART,
SERVICE_START, SERVICE_START,
SERVICE_STOP, SERVICE_STOP,
get_ffmpeg_manager,
) )
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
@ -251,3 +252,40 @@ async def test_async_get_image_with_extra_cmd_width_height(hass: HomeAssistant)
assert get_image_mock.call_args_list == [ assert get_image_mock.call_args_list == [
call("rtsp://fake", output_format="mjpeg", extra_cmd="-vf any -s 640x480") call("rtsp://fake", output_format="mjpeg", extra_cmd="-vf any -s 640x480")
] ]
async def test_modern_ffmpeg(
hass: HomeAssistant,
) -> None:
"""Test modern ffmpeg uses the new ffmpeg content type."""
with assert_setup_component(1):
await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
manager = get_ffmpeg_manager(hass)
assert "ffmpeg" in manager.ffmpeg_stream_content_type
async def test_legacy_ffmpeg(
hass: HomeAssistant,
) -> None:
"""Test legacy ffmpeg uses the old ffserver content type."""
with assert_setup_component(1), patch(
"homeassistant.components.ffmpeg.FFVersion.get_version", return_value="3.0"
), patch("homeassistant.components.ffmpeg.is_official_image", return_value=False):
await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
manager = get_ffmpeg_manager(hass)
assert "ffserver" in manager.ffmpeg_stream_content_type
async def test_ffmpeg_using_official_image(
hass: HomeAssistant,
) -> None:
"""Test ffmpeg using official image is the new ffmpeg content type."""
with assert_setup_component(1), patch(
"homeassistant.components.ffmpeg.is_official_image", return_value=True
):
await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}})
manager = get_ffmpeg_manager(hass)
assert "ffmpeg" in manager.ffmpeg_stream_content_type