Skip to content

Implement NVR download for Reolink recordings #144121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions homeassistant/components/reolink/media_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

_LOGGER = logging.getLogger(__name__)

VOD_SPLIT_TIME = dt.timedelta(minutes=5)


async def async_get_media_source(hass: HomeAssistant) -> ReolinkVODMediaSource:
"""Set up camera media source."""
Expand Down Expand Up @@ -60,11 +62,13 @@
"""Resolve media to a url."""
identifier = ["UNKNOWN"]
if item.identifier is not None:
identifier = item.identifier.split("|", 5)
identifier = item.identifier.split("|", 6)
if identifier[0] != "FILE":
raise Unresolvable(f"Unknown media item '{item.identifier}'.")

_, config_entry_id, channel_str, stream_res, filename = identifier
_, config_entry_id, channel_str, stream_res, filename, start_time, end_time = (
identifier
)
channel = int(channel_str)

host = get_host(self.hass, config_entry_id)
Expand All @@ -75,12 +79,19 @@
return VodRequestType.DOWNLOAD
return VodRequestType.PLAYBACK
if host.api.is_nvr:
return VodRequestType.FLV
return VodRequestType.NVR_DOWNLOAD

Check failure on line 82 in homeassistant/components/reolink/media_source.py

View workflow job for this annotation

GitHub Actions / Check mypy

Returning Any from function declared to return "VodRequestType" [no-any-return]

Check failure on line 82 in homeassistant/components/reolink/media_source.py

View workflow job for this annotation

GitHub Actions / Check mypy

"type[VodRequestType]" has no attribute "NVR_DOWNLOAD" [attr-defined]
return VodRequestType.RTMP

vod_type = get_vod_type()

if vod_type in [VodRequestType.DOWNLOAD, VodRequestType.PLAYBACK]:
if vod_type == VodRequestType.NVR_DOWNLOAD:

Check failure on line 87 in homeassistant/components/reolink/media_source.py

View workflow job for this annotation

GitHub Actions / Check mypy

"type[VodRequestType]" has no attribute "NVR_DOWNLOAD" [attr-defined]
filename = f"{start_time}_{end_time}"

if vod_type in {
VodRequestType.DOWNLOAD,
VodRequestType.NVR_DOWNLOAD,

Check failure on line 92 in homeassistant/components/reolink/media_source.py

View workflow job for this annotation

GitHub Actions / Check mypy

"type[VodRequestType]" has no attribute "NVR_DOWNLOAD" [attr-defined]
VodRequestType.PLAYBACK,
}:
proxy_url = async_generate_playback_proxy_url(
config_entry_id, channel, filename, stream_res, vod_type.value
)
Expand Down Expand Up @@ -357,8 +368,8 @@
month,
day,
)
_, vod_files = await host.api.request_vod_files(

Check failure on line 371 in homeassistant/components/reolink/media_source.py

View workflow job for this annotation

GitHub Actions / Check mypy

Unexpected keyword argument "split_time" for "request_vod_files" of "Host" [call-arg]
channel, start, end, stream=stream
channel, start, end, stream=stream, split_time=VOD_SPLIT_TIME
)
for file in vod_files:
file_name = f"{file.start_time.time()} {file.duration}"
Expand All @@ -372,7 +383,7 @@
children.append(
BrowseMediaSource(
domain=DOMAIN,
identifier=f"FILE|{config_entry_id}|{channel}|{stream}|{file.file_name}",
identifier=f"FILE|{config_entry_id}|{channel}|{stream}|{file.file_name}|{file.start_time_id}|{file.end_time_id}",

Check failure on line 386 in homeassistant/components/reolink/media_source.py

View workflow job for this annotation

GitHub Actions / Check mypy

"VOD_file" has no attribute "end_time_id"; maybe "end_time"? [attr-defined]
media_class=MediaClass.VIDEO,
media_content_type=MediaType.VIDEO,
title=file_name,
Expand Down
24 changes: 12 additions & 12 deletions tests/components/reolink/test_media_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@
TEST_DAY2 = 15
TEST_HOUR = 13
TEST_MINUTE = 12
TEST_FILE_NAME = f"{TEST_YEAR}{TEST_MONTH}{TEST_DAY}{TEST_HOUR}{TEST_MINUTE}00"
TEST_FILE_NAME_MP4 = f"{TEST_YEAR}{TEST_MONTH}{TEST_DAY}{TEST_HOUR}{TEST_MINUTE}00.mp4"
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_FILE_NAME = f"{TEST_START}00"
TEST_FILE_NAME_MP4 = f"{TEST_START}00.mp4"
TEST_STREAM = "main"
TEST_CHANNEL = "0"
TEST_CAM_NAME = "Cam new name"
Expand Down Expand Up @@ -92,17 +94,15 @@ async def test_resolve(
await hass.async_block_till_done()
caplog.set_level(logging.DEBUG)

file_id = (
f"FILE|{config_entry.entry_id}|{TEST_CHANNEL}|{TEST_STREAM}|{TEST_FILE_NAME}"
)
reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE, TEST_URL)
file_id = f"FILE|{config_entry.entry_id}|{TEST_CHANNEL}|{TEST_STREAM}|{TEST_FILE_NAME}|{TEST_START}|{TEST_END}"
reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL)

play_media = await async_resolve_media(
hass, f"{URI_SCHEME}{DOMAIN}/{file_id}", None
)
assert play_media.mime_type == TEST_MIME_TYPE
assert play_media.mime_type == TEST_MIME_TYPE_MP4

file_id = f"FILE|{config_entry.entry_id}|{TEST_CHANNEL}|{TEST_STREAM}|{TEST_FILE_NAME_MP4}"
file_id = f"FILE|{config_entry.entry_id}|{TEST_CHANNEL}|{TEST_STREAM}|{TEST_FILE_NAME_MP4}|{TEST_START}|{TEST_END}"
reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL2)

play_media = await async_resolve_media(
Expand All @@ -117,9 +117,7 @@ async def test_resolve(
)
assert play_media.mime_type == TEST_MIME_TYPE_MP4

file_id = (
f"FILE|{config_entry.entry_id}|{TEST_CHANNEL}|{TEST_STREAM}|{TEST_FILE_NAME}"
)
file_id = f"FILE|{config_entry.entry_id}|{TEST_CHANNEL}|{TEST_STREAM}|{TEST_FILE_NAME}|{TEST_START}|{TEST_END}"
reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE, TEST_URL)

play_media = await async_resolve_media(
Expand Down Expand Up @@ -217,14 +215,16 @@ async def test_browsing(
mock_vod_file.start_time = datetime(
TEST_YEAR, TEST_MONTH, TEST_DAY, TEST_HOUR, TEST_MINUTE
)
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.file_name = TEST_FILE_NAME
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_files_id = f"FILES|{entry_id}|{TEST_CHANNEL}|{TEST_STREAM}"
browse_file_id = f"FILE|{entry_id}|{TEST_CHANNEL}|{TEST_STREAM}|{TEST_FILE_NAME}"
browse_file_id = f"FILE|{entry_id}|{TEST_CHANNEL}|{TEST_STREAM}|{TEST_FILE_NAME}|{TEST_START}|{TEST_END}"
assert browse.domain == DOMAIN
assert (
browse.title
Expand Down
Loading