From d3338658c2fa714e993c3d668945b44a1e7ebd27 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 15 Jul 2024 22:39:11 -0500 Subject: [PATCH] feat: speed up camera snapshots (#152) --- src/uiprotect/api.py | 44 ++++++++++++++++++------------------- src/uiprotect/data/types.py | 20 +++++++++++++++++ src/uiprotect/data/user.py | 4 ++-- tests/conftest.py | 4 ++++ tests/test_api.py | 6 +++++ 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/uiprotect/api.py b/src/uiprotect/api.py index 013345d4..6b92623b 100644 --- a/src/uiprotect/api.py +++ b/src/uiprotect/api.py @@ -1401,21 +1401,20 @@ async def get_camera_snapshot( Datetime of screenshot is approximate. It may be +/- a few seconds. """ - params = { - "ts": to_js_time(dt or utc_now()), - "force": "true", - } + params: dict[str, Any] = {} + if dt is not None: + path = "recording-snapshot" + params["ts"] = to_js_time(dt) + else: + path = "snapshot" + params["ts"] = int(time.time() * 1000) + params["force"] = "true" if width is not None: - params.update({"w": width}) + params["w"] = width if height is not None: - params.update({"h": height}) - - path = "snapshot" - if dt is not None: - path = "recording-snapshot" - del params["force"] + params["h"] = height return await self.api_request_raw( f"cameras/{camera_id}/{path}", @@ -1435,22 +1434,21 @@ async def get_package_camera_snapshot( Datetime of screenshot is approximate. It may be +/- a few seconds. """ - params = { - "ts": to_js_time(dt or utc_now()), - "force": "true", - } + params: dict[str, Any] = {} + if dt is not None: + path = "recording-snapshot" + params["ts"] = to_js_time(dt) + params["lens"] = 2 + else: + path = "package-snapshot" + params["ts"] = int(time.time() * 1000) + params["force"] = "true" if width is not None: - params.update({"w": width}) + params["w"] = width if height is not None: - params.update({"h": height}) - - path = "package-snapshot" - if dt is not None: - path = "recording-snapshot" - del params["force"] - params.update({"lens": 2}) + params["h"] = height return await self.api_request_raw( f"cameras/{camera_id}/{path}", diff --git a/src/uiprotect/data/types.py b/src/uiprotect/data/types.py index 47b060f4..78eeab9f 100644 --- a/src/uiprotect/data/types.py +++ b/src/uiprotect/data/types.py @@ -111,6 +111,16 @@ def devices_key(self) -> str: """Return the devices key.""" return f"{self.value}s" + @cached_property + def name(self) -> str: + """Return the name.""" + return self._name_ + + @cached_property + def value(self) -> str: + """Return the value.""" + return self._value_ + @classmethod @cache def from_string(cls, value: str) -> ModelType: @@ -575,6 +585,16 @@ class PermissionNode(str, UnknownValuesEnumMixin, enum.Enum): READ_LIVE = "readlive" UNKNOWN = "unknown" + @cached_property + def name(self) -> str: + """Return the name.""" + return self._name_ + + @cached_property + def value(self) -> str: + """Return the value.""" + return self._value_ + @enum.unique class HDRMode(str, UnknownValuesEnumMixin, enum.Enum): diff --git a/src/uiprotect/data/user.py b/src/uiprotect/data/user.py index b9f55fb1..88722a13 100644 --- a/src/uiprotect/data/user.py +++ b/src/uiprotect/data/user.py @@ -210,7 +210,7 @@ def can( ) -> bool: """Checks if a user can do a specific action""" check_self = False - if model == self.model and obj is not None and obj.id == self.id: + if model is self.model and obj is not None and obj.id == self.id: perm_str = f"{model.value}:{node.value}:$" check_self = True else: @@ -221,7 +221,7 @@ def can( return self._perm_cache[perm_str] for perm in self.all_permissions: - if model != perm.model or node not in perm.nodes: + if model is not perm.model or node not in perm.nodes: continue if perm.obj_ids is None: self._perm_cache[perm_str] = True diff --git a/tests/conftest.py b/tests/conftest.py index c3d60379..bed638e8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -93,6 +93,10 @@ def get_now(): return datetime.fromisoformat(CONSTANTS["time"]).replace(microsecond=0) +def get_time(): + return datetime.fromisoformat(CONSTANTS["time"]).replace(microsecond=0).timestamp() + + def validate_video_file(filepath: Path, length: int): output = run( split(CHECK_CMD.format(filename=filepath)), diff --git a/tests/test_api.py b/tests/test_api.py index 8a0ba663..0b47d461 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -26,6 +26,7 @@ TEST_VIEWPORT_EXISTS, MockDatetime, compare_objs, + get_time, validate_video_file, ) from tests.sample_data.constants import CONSTANTS @@ -588,6 +589,7 @@ async def test_get_liveviews(protect_client: ProtectApiClient, liveviews): @pytest.mark.skipif(not TEST_SNAPSHOT_EXISTS, reason="Missing testdata") @patch("uiprotect.utils.datetime", MockDatetime) +@patch("uiprotect.api.time.time", get_time) @pytest.mark.asyncio() async def test_get_camera_snapshot(protect_client: ProtectApiClient, now): data = await protect_client.get_camera_snapshot("test_id") @@ -608,6 +610,7 @@ async def test_get_camera_snapshot(protect_client: ProtectApiClient, now): @pytest.mark.skipif(not TEST_SNAPSHOT_EXISTS, reason="Missing testdata") @patch("uiprotect.utils.datetime", MockDatetime) +@patch("uiprotect.api.time.time", get_time) @pytest.mark.asyncio() async def test_get_pacakge_camera_snapshot(protect_client: ProtectApiClient, now): data = await protect_client.get_package_camera_snapshot("test_id") @@ -628,6 +631,7 @@ async def test_get_pacakge_camera_snapshot(protect_client: ProtectApiClient, now @pytest.mark.skipif(not TEST_SNAPSHOT_EXISTS, reason="Missing testdata") @patch("uiprotect.utils.datetime", MockDatetime) +@patch("uiprotect.api.time.time", get_time) @pytest.mark.asyncio() async def test_get_camera_snapshot_args(protect_client: ProtectApiClient, now): data = await protect_client.get_camera_snapshot("test_id", 1920, 1080) @@ -650,6 +654,7 @@ async def test_get_camera_snapshot_args(protect_client: ProtectApiClient, now): @pytest.mark.skipif(not TEST_SNAPSHOT_EXISTS, reason="Missing testdata") @patch("uiprotect.utils.datetime", MockDatetime) +@patch("uiprotect.api.time.time", get_time) @pytest.mark.asyncio() async def test_get_package_camera_snapshot_args(protect_client: ProtectApiClient, now): data = await protect_client.get_package_camera_snapshot("test_id", 1920, 1080) @@ -672,6 +677,7 @@ async def test_get_package_camera_snapshot_args(protect_client: ProtectApiClient @pytest.mark.skipif(not TEST_VIDEO_EXISTS, reason="Missing testdata") @patch("uiprotect.api.datetime", MockDatetime) +@patch("uiprotect.api.time.time", get_time) @pytest.mark.asyncio() async def test_get_camera_video(protect_client: ProtectApiClient, now, tmp_binary_file): camera = next(iter(protect_client.bootstrap.cameras.values()))