Skip to content

Commit

Permalink
feat: speed up camera snapshots (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco authored Jul 16, 2024
1 parent f2eee76 commit d333865
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 25 deletions.
44 changes: 21 additions & 23 deletions src/uiprotect/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}",
Expand All @@ -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}",
Expand Down
20 changes: 20 additions & 0 deletions src/uiprotect/data/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions src/uiprotect/data/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
6 changes: 6 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
TEST_VIEWPORT_EXISTS,
MockDatetime,
compare_objs,
get_time,
validate_video_file,
)
from tests.sample_data.constants import CONSTANTS
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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()))
Expand Down

0 comments on commit d333865

Please sign in to comment.