mirror of
https://github.com/home-assistant/core.git
synced 2025-07-26 22:57:17 +00:00
Add event browsing to Reolink recordings (#144259)
This commit is contained in:
parent
109bcf362a
commit
0d81694640
@ -7,6 +7,7 @@ import logging
|
|||||||
|
|
||||||
from reolink_aio.api import DUAL_LENS_MODELS
|
from reolink_aio.api import DUAL_LENS_MODELS
|
||||||
from reolink_aio.enums import VodRequestType
|
from reolink_aio.enums import VodRequestType
|
||||||
|
from reolink_aio.typings import VOD_trigger
|
||||||
|
|
||||||
from homeassistant.components.camera import DOMAIN as CAM_DOMAIN, DynamicStreamSettings
|
from homeassistant.components.camera import DOMAIN as CAM_DOMAIN, DynamicStreamSettings
|
||||||
from homeassistant.components.media_player import MediaClass, MediaType
|
from homeassistant.components.media_player import MediaClass, MediaType
|
||||||
@ -152,6 +153,26 @@ class ReolinkVODMediaSource(MediaSource):
|
|||||||
int(month_str),
|
int(month_str),
|
||||||
int(day_str),
|
int(day_str),
|
||||||
)
|
)
|
||||||
|
if item_type == "EVE":
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
config_entry_id,
|
||||||
|
channel_str,
|
||||||
|
stream,
|
||||||
|
year_str,
|
||||||
|
month_str,
|
||||||
|
day_str,
|
||||||
|
event,
|
||||||
|
) = identifier
|
||||||
|
return await self._async_generate_camera_files(
|
||||||
|
config_entry_id,
|
||||||
|
int(channel_str),
|
||||||
|
stream,
|
||||||
|
int(year_str),
|
||||||
|
int(month_str),
|
||||||
|
int(day_str),
|
||||||
|
event,
|
||||||
|
)
|
||||||
|
|
||||||
raise Unresolvable(f"Unknown media item '{item.identifier}' during browsing.")
|
raise Unresolvable(f"Unknown media item '{item.identifier}' during browsing.")
|
||||||
|
|
||||||
@ -352,6 +373,7 @@ class ReolinkVODMediaSource(MediaSource):
|
|||||||
year: int,
|
year: int,
|
||||||
month: int,
|
month: int,
|
||||||
day: int,
|
day: int,
|
||||||
|
event: str | None = None,
|
||||||
) -> BrowseMediaSource:
|
) -> BrowseMediaSource:
|
||||||
"""Return all recording files on a specific day of a Reolink camera."""
|
"""Return all recording files on a specific day of a Reolink camera."""
|
||||||
host = get_host(self.hass, config_entry_id)
|
host = get_host(self.hass, config_entry_id)
|
||||||
@ -368,9 +390,34 @@ class ReolinkVODMediaSource(MediaSource):
|
|||||||
month,
|
month,
|
||||||
day,
|
day,
|
||||||
)
|
)
|
||||||
|
event_trigger = VOD_trigger[event] if event is not None else None
|
||||||
_, vod_files = await host.api.request_vod_files(
|
_, vod_files = await host.api.request_vod_files(
|
||||||
channel, start, end, stream=stream, split_time=VOD_SPLIT_TIME
|
channel,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
stream=stream,
|
||||||
|
split_time=VOD_SPLIT_TIME,
|
||||||
|
trigger=event_trigger,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if event is None and host.api.is_nvr and not host.api.is_hub:
|
||||||
|
triggers = VOD_trigger.NONE
|
||||||
|
for file in vod_files:
|
||||||
|
triggers |= file.triggers
|
||||||
|
|
||||||
|
children.extend(
|
||||||
|
BrowseMediaSource(
|
||||||
|
domain=DOMAIN,
|
||||||
|
identifier=f"EVE|{config_entry_id}|{channel}|{stream}|{year}|{month}|{day}|{trigger.name}",
|
||||||
|
media_class=MediaClass.DIRECTORY,
|
||||||
|
media_content_type=MediaType.PLAYLIST,
|
||||||
|
title=str(trigger.name).title(),
|
||||||
|
can_play=False,
|
||||||
|
can_expand=True,
|
||||||
|
)
|
||||||
|
for trigger in triggers
|
||||||
|
)
|
||||||
|
|
||||||
for file in vod_files:
|
for file in vod_files:
|
||||||
file_name = f"{file.start_time.time()} {file.duration}"
|
file_name = f"{file.start_time.time()} {file.duration}"
|
||||||
if file.triggers != file.triggers.NONE:
|
if file.triggers != file.triggers.NONE:
|
||||||
@ -397,6 +444,8 @@ class ReolinkVODMediaSource(MediaSource):
|
|||||||
)
|
)
|
||||||
if host.api.model in DUAL_LENS_MODELS:
|
if host.api.model in DUAL_LENS_MODELS:
|
||||||
title = f"{host.api.camera_name(channel)} lens {channel} {res_name(stream)} {year}/{month}/{day}"
|
title = f"{host.api.camera_name(channel)} lens {channel} {res_name(stream)} {year}/{month}/{day}"
|
||||||
|
if event:
|
||||||
|
title = f"{title} {event.title()}"
|
||||||
|
|
||||||
return BrowseMediaSource(
|
return BrowseMediaSource(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
|
@ -6,6 +6,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from reolink_aio.exceptions import ReolinkError
|
from reolink_aio.exceptions import ReolinkError
|
||||||
|
from reolink_aio.typings import VOD_trigger
|
||||||
|
|
||||||
from homeassistant.components.media_source import (
|
from homeassistant.components.media_source import (
|
||||||
DOMAIN as MEDIA_SOURCE_DOMAIN,
|
DOMAIN as MEDIA_SOURCE_DOMAIN,
|
||||||
@ -16,6 +17,7 @@ from homeassistant.components.media_source import (
|
|||||||
)
|
)
|
||||||
from homeassistant.components.reolink.config_flow import DEFAULT_PROTOCOL
|
from homeassistant.components.reolink.config_flow import DEFAULT_PROTOCOL
|
||||||
from homeassistant.components.reolink.const import CONF_BC_PORT, CONF_USE_HTTPS, DOMAIN
|
from homeassistant.components.reolink.const import CONF_BC_PORT, CONF_USE_HTTPS, DOMAIN
|
||||||
|
from homeassistant.components.reolink.media_source import VOD_SPLIT_TIME
|
||||||
from homeassistant.components.stream import DOMAIN as MEDIA_STREAM_DOMAIN
|
from homeassistant.components.stream import DOMAIN as MEDIA_STREAM_DOMAIN
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
@ -53,6 +55,8 @@ TEST_HOUR = 13
|
|||||||
TEST_MINUTE = 12
|
TEST_MINUTE = 12
|
||||||
TEST_START = f"{TEST_YEAR}{TEST_MONTH}{TEST_DAY}{TEST_HOUR}{TEST_MINUTE}"
|
TEST_START = f"{TEST_YEAR}{TEST_MONTH}{TEST_DAY}{TEST_HOUR}{TEST_MINUTE}"
|
||||||
TEST_END = f"{TEST_YEAR}{TEST_MONTH}{TEST_DAY}{TEST_HOUR}{TEST_MINUTE + 5}"
|
TEST_END = f"{TEST_YEAR}{TEST_MONTH}{TEST_DAY}{TEST_HOUR}{TEST_MINUTE + 5}"
|
||||||
|
TEST_START_TIME = datetime(TEST_YEAR, TEST_MONTH, TEST_DAY, 0, 0)
|
||||||
|
TEST_END_TIME = datetime(TEST_YEAR, TEST_MONTH, TEST_DAY, 23, 59, 59)
|
||||||
TEST_FILE_NAME = f"{TEST_START}00"
|
TEST_FILE_NAME = f"{TEST_START}00"
|
||||||
TEST_FILE_NAME_MP4 = f"{TEST_START}00.mp4"
|
TEST_FILE_NAME_MP4 = f"{TEST_START}00.mp4"
|
||||||
TEST_STREAM = "main"
|
TEST_STREAM = "main"
|
||||||
@ -212,13 +216,12 @@ async def test_browsing(
|
|||||||
|
|
||||||
# browse camera recording files on day
|
# browse camera recording files on day
|
||||||
mock_vod_file = MagicMock()
|
mock_vod_file = MagicMock()
|
||||||
mock_vod_file.start_time = datetime(
|
mock_vod_file.start_time = TEST_START_TIME
|
||||||
TEST_YEAR, TEST_MONTH, TEST_DAY, TEST_HOUR, TEST_MINUTE
|
|
||||||
)
|
|
||||||
mock_vod_file.start_time_id = TEST_START
|
mock_vod_file.start_time_id = TEST_START
|
||||||
mock_vod_file.end_time_id = TEST_END
|
mock_vod_file.end_time_id = TEST_END
|
||||||
mock_vod_file.duration = timedelta(minutes=15)
|
mock_vod_file.duration = timedelta(minutes=5)
|
||||||
mock_vod_file.file_name = TEST_FILE_NAME
|
mock_vod_file.file_name = TEST_FILE_NAME
|
||||||
|
mock_vod_file.triggers = VOD_trigger.PERSON
|
||||||
reolink_connect.request_vod_files.return_value = ([mock_status], [mock_vod_file])
|
reolink_connect.request_vod_files.return_value = ([mock_status], [mock_vod_file])
|
||||||
|
|
||||||
browse = await async_browse_media(hass, f"{URI_SCHEME}{DOMAIN}/{browse_day_0_id}")
|
browse = await async_browse_media(hass, f"{URI_SCHEME}{DOMAIN}/{browse_day_0_id}")
|
||||||
@ -232,9 +235,46 @@ async def test_browsing(
|
|||||||
)
|
)
|
||||||
assert browse.identifier == browse_files_id
|
assert browse.identifier == browse_files_id
|
||||||
assert browse.children[0].identifier == browse_file_id
|
assert browse.children[0].identifier == browse_file_id
|
||||||
|
reolink_connect.request_vod_files.assert_called_with(
|
||||||
|
int(TEST_CHANNEL),
|
||||||
|
TEST_START_TIME,
|
||||||
|
TEST_END_TIME,
|
||||||
|
stream=TEST_STREAM,
|
||||||
|
split_time=VOD_SPLIT_TIME,
|
||||||
|
trigger=None,
|
||||||
|
)
|
||||||
|
|
||||||
reolink_connect.model = TEST_HOST_MODEL
|
reolink_connect.model = TEST_HOST_MODEL
|
||||||
|
|
||||||
|
# browse event trigger person on a NVR
|
||||||
|
reolink_connect.is_nvr = True
|
||||||
|
browse_event_person_id = f"EVE|{entry_id}|{TEST_CHANNEL}|{TEST_STREAM}|{TEST_YEAR}|{TEST_MONTH}|{TEST_DAY}|{VOD_trigger.PERSON.name}"
|
||||||
|
|
||||||
|
browse = await async_browse_media(hass, f"{URI_SCHEME}{DOMAIN}/{browse_day_0_id}")
|
||||||
|
assert browse.children[0].identifier == browse_event_person_id
|
||||||
|
|
||||||
|
browse = await async_browse_media(
|
||||||
|
hass, f"{URI_SCHEME}{DOMAIN}/{browse_event_person_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert browse.domain == DOMAIN
|
||||||
|
assert (
|
||||||
|
browse.title
|
||||||
|
== f"{TEST_NVR_NAME} High res. {TEST_YEAR}/{TEST_MONTH}/{TEST_DAY} Person"
|
||||||
|
)
|
||||||
|
assert browse.identifier == browse_files_id
|
||||||
|
assert browse.children[0].identifier == browse_file_id
|
||||||
|
reolink_connect.request_vod_files.assert_called_with(
|
||||||
|
int(TEST_CHANNEL),
|
||||||
|
TEST_START_TIME,
|
||||||
|
TEST_END_TIME,
|
||||||
|
stream=TEST_STREAM,
|
||||||
|
split_time=VOD_SPLIT_TIME,
|
||||||
|
trigger=VOD_trigger.PERSON,
|
||||||
|
)
|
||||||
|
|
||||||
|
reolink_connect.is_nvr = False
|
||||||
|
|
||||||
|
|
||||||
async def test_browsing_h265_encoding(
|
async def test_browsing_h265_encoding(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user