Skip to content

Commit

Permalink
Use the HA timezone setting for requesting recordings, provide timezo…
Browse files Browse the repository at this point in the history
…ne to event summary (#397)

* Remove tzlocal

* Use HA timezone option to feed timezone

* Fix tests

* Fix tests

* Fix test with tiemzone

* remove constructor and pass to func

* Add timezone to event summary call

* Use pytz to convert timezone to utc before sending

* Fix timezone access

* Fix string

* Fix event summary call

* Fix timezone

* Fix tests

* Fix month formatting

* Mypy fixes

* Fix mypy and tests

* Cleanup timezone conversion

* Cleanup

* Fix mypy

* Fix mypy
  • Loading branch information
NickM-27 authored Jan 7, 2023
1 parent 36f7c84 commit dddcd00
Show file tree
Hide file tree
Showing 10 changed files with 53 additions and 24 deletions.
6 changes: 4 additions & 2 deletions custom_components/frigate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,10 @@ async def async_setup(hass: HomeAssistant, config: Config) -> bool:

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up this integration using UI."""

client = FrigateApiClient(entry.data.get(CONF_URL), async_get_clientsession(hass))
client = FrigateApiClient(
entry.data.get(CONF_URL),
async_get_clientsession(hass),
)
coordinator = FrigateDataUpdateCoordinator(hass, client=client)
await coordinator.async_config_entry_first_refresh()

Expand Down
7 changes: 4 additions & 3 deletions custom_components/frigate/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import aiohttp
import async_timeout
from tzlocal import get_localzone_name
from yarl import URL

TIMEOUT = 10
Expand Down Expand Up @@ -94,12 +93,14 @@ async def async_get_event_summary(
self,
has_clip: bool | None = None,
has_snapshot: bool | None = None,
timezone: str | None = None,
decode_json: bool = True,
) -> list[dict[str, Any]]:
"""Get data from the API."""
params = {
"has_clip": int(has_clip) if has_clip is not None else None,
"has_snapshot": int(has_snapshot) if has_snapshot is not None else None,
"timezone": str(timezone) if timezone is not None else None,
}

return cast(
Expand Down Expand Up @@ -138,10 +139,10 @@ async def async_retain(
return cast(dict[str, Any], result) if decode_json else result

async def async_get_recordings_summary(
self, camera: str, decode_json: bool = True
self, camera: str, timezone: str, decode_json: bool = True
) -> list[dict[str, Any]] | str:
"""Get recordings summary."""
params = {"timezone": get_localzone_name()}
params = {"timezone": timezone}

result = await self.api_wrapper(
"get",
Expand Down
2 changes: 1 addition & 1 deletion custom_components/frigate/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
"codeowners": [
"@blakeblackshear"
],
"requirements": ["tzlocal==4.2"],
"requirements": ["pytz==2022.7"],
"iot_class": "local_push"
}
37 changes: 28 additions & 9 deletions custom_components/frigate/media_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import attr
from dateutil.relativedelta import relativedelta
import pytz

from homeassistant.components.media_player.const import (
MEDIA_CLASS_DIRECTORY,
Expand All @@ -26,6 +27,7 @@
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import system_info
from homeassistant.helpers.template import DATE_STR_FORMAT
from homeassistant.util.dt import DEFAULT_TIME_ZONE

Expand Down Expand Up @@ -123,7 +125,7 @@ def get_identifier_type(cls) -> str:
"""Get the identifier type."""
raise NotImplementedError

def get_integration_proxy_path(self) -> str:
def get_integration_proxy_path(self, timezone: str) -> str:
"""Get the proxy (Home Assistant view) path for this identifier."""
raise NotImplementedError

Expand Down Expand Up @@ -249,7 +251,7 @@ def get_identifier_type(cls) -> str:
"""Get the identifier type."""
return "event"

def get_integration_proxy_path(self) -> str:
def get_integration_proxy_path(self, timezone: str) -> str:
"""Get the equivalent Frigate server path."""
if self.frigate_media_type == FrigateMediaType.CLIPS:
return f"vod/event/{self.id}/index.{self.frigate_media_type.extension}"
Expand Down Expand Up @@ -452,7 +454,7 @@ def get_identifier_type(cls) -> str:
"""Get the identifier type."""
return "recordings"

def get_integration_proxy_path(self) -> str:
def get_integration_proxy_path(self, timezone: str) -> str:
"""Get the integration path that will proxy this identifier."""

if (
Expand All @@ -461,13 +463,24 @@ def get_integration_proxy_path(self) -> str:
and self.hour is not None
):
year, month, day = self.year_month_day.split("-")
# Take the selected time in users local time
# and find the offset to utc, convert to UTC
# then request the vod for that time.
start_date: dt.datetime = dt.datetime(
int(year),
int(month),
int(day),
int(self.hour),
tzinfo=dt.timezone.utc,
) - (dt.datetime.now(pytz.timezone(timezone)).utcoffset() or dt.timedelta())

parts = [
"vod",
f"{year}-{month}",
day,
f"{self.hour:02}",
f"{start_date.year}-{start_date.month:02}",
f"{start_date.day:02}",
f"{start_date.hour:02}",
self.camera,
"utc",
"index.m3u8",
]

Expand Down Expand Up @@ -564,7 +577,10 @@ async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
if identifier and self._is_allowed_as_media_source(
identifier.frigate_instance_id
):
server_path = identifier.get_integration_proxy_path()
info = await system_info.async_get_system_info(self.hass)
server_path = identifier.get_integration_proxy_path(
info.get("timezone", "utc")
)
return PlayMedia(
f"/api/frigate/{identifier.frigate_instance_id}/{server_path}",
identifier.mime_type,
Expand Down Expand Up @@ -688,10 +704,11 @@ async def async_browse_media(
config = await self._get_client(identifier).async_get_config()
return self._get_camera_recording_folders(identifier, config)

info = await system_info.async_get_system_info(self.hass)
recording_summary = cast(
list[dict[str, Any]],
await self._get_client(identifier).async_get_recordings_summary(
camera=identifier.camera
camera=identifier.camera, timezone=info.get("timezone", "utc")
),
)

Expand All @@ -710,12 +727,14 @@ async def _get_event_summary_data(
"""Get event summary data."""

try:
info = await system_info.async_get_system_info(self.hass)

if identifier.frigate_media_type == FrigateMediaType.CLIPS:
kwargs = {"has_clip": True}
else:
kwargs = {"has_snapshot": True}
summary_data = await self._get_client(identifier).async_get_event_summary(
**kwargs
timezone=info.get("timezone", "utc"), **kwargs
)
except FrigateApiClientError as exc:
raise MediaSourceError from exc
Expand Down
4 changes: 3 additions & 1 deletion custom_components/frigate/ws_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ async def ws_get_recordings_summary(
try:
connection.send_result(
msg["id"],
await client.async_get_recordings_summary(msg["camera"], decode_json=False),
await client.async_get_recordings_summary(
msg["camera"], msg.get("timezone", "utc"), decode_json=False
),
)
except FrigateApiClientError:
connection.send_error(
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ attr
homeassistant~=2022.7.0
paho-mqtt==1.6.1
python-dateutil
tzlocal==4.2
yarl
pytz==2022.7
2 changes: 1 addition & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ pylint-pytest
pylint==2.8.3
pytest-aiohttp==0.3.0
types-python-dateutil
types-tzlocal
types-pytz
5 changes: 4 additions & 1 deletion tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,10 @@ async def test_async_get_recordings_summary(
)

frigate_client = FrigateApiClient(str(server.make_url("/")), aiohttp_session)
assert await frigate_client.async_get_recordings_summary(camera) == summary_success
assert (
await frigate_client.async_get_recordings_summary(camera, "utc")
== summary_success
)
assert summary_handler.called


Expand Down
10 changes: 6 additions & 4 deletions tests/test_media_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ async def test_async_resolve_media(
)
assert media == PlayMedia(
url=(
f"/api/frigate/{TEST_FRIGATE_INSTANCE_ID}/vod/2021-05/30/15/front_door/index.m3u8"
f"/api/frigate/{TEST_FRIGATE_INSTANCE_ID}/vod/2021-05/30/23/front_door/utc/index.m3u8"
),
mime_type="application/x-mpegURL",
)
Expand Down Expand Up @@ -979,7 +979,7 @@ async def test_event_search_identifier() -> None:
# Event searches have no equivalent Frigate server path (searches result in
# EventIdentifiers, that do have a Frigate server path).
with pytest.raises(NotImplementedError):
identifier.get_integration_proxy_path()
identifier.get_integration_proxy_path("utc")

# Invalid "after" time.
assert (
Expand Down Expand Up @@ -1072,7 +1072,7 @@ async def test_recordings_identifier() -> None:
identifier_in = f"{TEST_FRIGATE_INSTANCE_ID}/recordings/front_door//15"
identifier = RecordingIdentifier.from_str(identifier_in)
assert identifier is not None
identifier.get_integration_proxy_path()
identifier.get_integration_proxy_path("utc")

# Verify a zero hour:
# https://github.com/blakeblackshear/frigate-hass-integration/issues/126
Expand Down Expand Up @@ -1247,7 +1247,9 @@ async def test_snapshots(hass: HomeAssistant) -> None:
],
}

assert client.async_get_event_summary.call_args == call(has_snapshot=True)
assert client.async_get_event_summary.call_args == call(
has_snapshot=True, timezone="US/Pacific"
)
assert client.async_get_events.call_args == call(
after=1622764800,
before=1622851200,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_ws_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ async def test_get_recordings_success(hass: HomeAssistant, hass_ws_client: Any)

response = await ws_client.receive_json()
mock_client.async_get_recordings_summary.assert_called_with(
TEST_CAMERA, decode_json=False
TEST_CAMERA, "utc", decode_json=False
)
assert response["success"]
assert response["result"] == recording_success
Expand Down

0 comments on commit dddcd00

Please sign in to comment.