From c69ab425c5edb4f7cd56404a6a6e96d4b7320e14 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 03:05:42 -1000 Subject: [PATCH] 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 --- homeassistant/components/ffmpeg/__init__.py | 39 ++++++++++++++------- tests/components/ffmpeg/test_init.py | 38 ++++++++++++++++++++ 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/ffmpeg/__init__.py b/homeassistant/components/ffmpeg/__init__.py index f16aed7b607..ec19df5fb6f 100644 --- a/homeassistant/components/ffmpeg/__init__.py +++ b/homeassistant/components/ffmpeg/__init__.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio import re -from typing import Generic, TypeVar +from typing import TYPE_CHECKING, Generic, TypeVar from haffmpeg.core import HAFFmpeg from haffmpeg.tools import IMAGE_JPEG, FFVersion, ImageFrame @@ -24,9 +24,16 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_send, ) from homeassistant.helpers.entity import Entity +from homeassistant.helpers.system_info import is_official_image from homeassistant.helpers.typing import ConfigType 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) DOMAIN = "ffmpeg" @@ -49,6 +56,12 @@ CONF_OUTPUT = "output" 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( { DOMAIN: vol.Schema( @@ -142,26 +155,28 @@ class FFmpegManager: self._version: str | None = None self._major_version: int | None = None - @property + @cached_property def binary(self) -> str: """Return ffmpeg binary from config.""" return self._bin async def async_get_version(self) -> tuple[str | None, int | None]: """Return ffmpeg version.""" - - ffversion = FFVersion(self._bin) - self._version = await ffversion.get_version() - - self._major_version = None - if self._version is not None: - result = re.search(r"(\d+)\.", self._version) - if result is not None: - self._major_version = int(result.group(1)) + if self._version is None: + if is_official_image(): + self._version = OFFICIAL_IMAGE_VERSION + self._major_version = int(self._version.split(".")[0]) + elif ( + (version := await FFVersion(self._bin).get_version()) + and (result := re.search(r"(\d+)\.", version)) + and (major_version := int(result.group(1))) + ): + self._version = version + self._major_version = major_version return self._version, self._major_version - @property + @cached_property def ffmpeg_stream_content_type(self) -> str: """Return HTTP content type for ffmpeg stream.""" if self._major_version is not None and self._major_version > 3: diff --git a/tests/components/ffmpeg/test_init.py b/tests/components/ffmpeg/test_init.py index 23d00d7e46b..2d79e593d2c 100644 --- a/tests/components/ffmpeg/test_init.py +++ b/tests/components/ffmpeg/test_init.py @@ -8,6 +8,7 @@ from homeassistant.components.ffmpeg import ( SERVICE_RESTART, SERVICE_START, SERVICE_STOP, + get_ffmpeg_manager, ) from homeassistant.const import ( 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 == [ 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