Add event browsing to Reolink recordings (#144259)

This commit is contained in:
starkillerOG 2025-05-26 16:20:55 +02:00 committed by GitHub
parent 109bcf362a
commit 0d81694640
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 94 additions and 5 deletions

View File

@ -7,6 +7,7 @@ import logging
from reolink_aio.api import DUAL_LENS_MODELS
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.media_player import MediaClass, MediaType
@ -152,6 +153,26 @@ class ReolinkVODMediaSource(MediaSource):
int(month_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.")
@ -352,6 +373,7 @@ class ReolinkVODMediaSource(MediaSource):
year: int,
month: int,
day: int,
event: str | None = None,
) -> BrowseMediaSource:
"""Return all recording files on a specific day of a Reolink camera."""
host = get_host(self.hass, config_entry_id)
@ -368,9 +390,34 @@ class ReolinkVODMediaSource(MediaSource):
month,
day,
)
event_trigger = VOD_trigger[event] if event is not None else None
_, 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:
file_name = f"{file.start_time.time()} {file.duration}"
if file.triggers != file.triggers.NONE:
@ -397,6 +444,8 @@ class ReolinkVODMediaSource(MediaSource):
)
if host.api.model in DUAL_LENS_MODELS:
title = f"{host.api.camera_name(channel)} lens {channel} {res_name(stream)} {year}/{month}/{day}"
if event:
title = f"{title} {event.title()}"
return BrowseMediaSource(
domain=DOMAIN,

View File

@ -6,6 +6,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from reolink_aio.exceptions import ReolinkError
from reolink_aio.typings import VOD_trigger
from homeassistant.components.media_source import (
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.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.const import (
CONF_HOST,
@ -53,6 +55,8 @@ TEST_HOUR = 13
TEST_MINUTE = 12
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_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_MP4 = f"{TEST_START}00.mp4"
TEST_STREAM = "main"
@ -212,13 +216,12 @@ async def test_browsing(
# browse camera recording files on day
mock_vod_file = MagicMock()
mock_vod_file.start_time = datetime(
TEST_YEAR, TEST_MONTH, TEST_DAY, TEST_HOUR, TEST_MINUTE
)
mock_vod_file.start_time = TEST_START_TIME
mock_vod_file.start_time_id = TEST_START
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.triggers = VOD_trigger.PERSON
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}")
@ -232,9 +235,46 @@ async def test_browsing(
)
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=None,
)
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(
hass: HomeAssistant,