mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Implement NVR download for Reolink recordings (#144121)
This commit is contained in:
parent
dafda420e5
commit
a3b7cd7b4d
@ -27,6 +27,8 @@ from .views import async_generate_playback_proxy_url
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
VOD_SPLIT_TIME = dt.timedelta(minutes=5)
|
||||||
|
|
||||||
|
|
||||||
async def async_get_media_source(hass: HomeAssistant) -> ReolinkVODMediaSource:
|
async def async_get_media_source(hass: HomeAssistant) -> ReolinkVODMediaSource:
|
||||||
"""Set up camera media source."""
|
"""Set up camera media source."""
|
||||||
@ -60,11 +62,13 @@ class ReolinkVODMediaSource(MediaSource):
|
|||||||
"""Resolve media to a url."""
|
"""Resolve media to a url."""
|
||||||
identifier = ["UNKNOWN"]
|
identifier = ["UNKNOWN"]
|
||||||
if item.identifier is not None:
|
if item.identifier is not None:
|
||||||
identifier = item.identifier.split("|", 5)
|
identifier = item.identifier.split("|", 6)
|
||||||
if identifier[0] != "FILE":
|
if identifier[0] != "FILE":
|
||||||
raise Unresolvable(f"Unknown media item '{item.identifier}'.")
|
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)
|
channel = int(channel_str)
|
||||||
|
|
||||||
host = get_host(self.hass, config_entry_id)
|
host = get_host(self.hass, config_entry_id)
|
||||||
@ -75,12 +79,19 @@ class ReolinkVODMediaSource(MediaSource):
|
|||||||
return VodRequestType.DOWNLOAD
|
return VodRequestType.DOWNLOAD
|
||||||
return VodRequestType.PLAYBACK
|
return VodRequestType.PLAYBACK
|
||||||
if host.api.is_nvr:
|
if host.api.is_nvr:
|
||||||
return VodRequestType.FLV
|
return VodRequestType.NVR_DOWNLOAD
|
||||||
return VodRequestType.RTMP
|
return VodRequestType.RTMP
|
||||||
|
|
||||||
vod_type = get_vod_type()
|
vod_type = get_vod_type()
|
||||||
|
|
||||||
if vod_type in [VodRequestType.DOWNLOAD, VodRequestType.PLAYBACK]:
|
if vod_type == VodRequestType.NVR_DOWNLOAD:
|
||||||
|
filename = f"{start_time}_{end_time}"
|
||||||
|
|
||||||
|
if vod_type in {
|
||||||
|
VodRequestType.DOWNLOAD,
|
||||||
|
VodRequestType.NVR_DOWNLOAD,
|
||||||
|
VodRequestType.PLAYBACK,
|
||||||
|
}:
|
||||||
proxy_url = async_generate_playback_proxy_url(
|
proxy_url = async_generate_playback_proxy_url(
|
||||||
config_entry_id, channel, filename, stream_res, vod_type.value
|
config_entry_id, channel, filename, stream_res, vod_type.value
|
||||||
)
|
)
|
||||||
@ -358,7 +369,7 @@ class ReolinkVODMediaSource(MediaSource):
|
|||||||
day,
|
day,
|
||||||
)
|
)
|
||||||
_, vod_files = await host.api.request_vod_files(
|
_, vod_files = await host.api.request_vod_files(
|
||||||
channel, start, end, stream=stream
|
channel, start, end, stream=stream, split_time=VOD_SPLIT_TIME
|
||||||
)
|
)
|
||||||
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}"
|
||||||
@ -372,7 +383,7 @@ class ReolinkVODMediaSource(MediaSource):
|
|||||||
children.append(
|
children.append(
|
||||||
BrowseMediaSource(
|
BrowseMediaSource(
|
||||||
domain=DOMAIN,
|
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}",
|
||||||
media_class=MediaClass.VIDEO,
|
media_class=MediaClass.VIDEO,
|
||||||
media_content_type=MediaType.VIDEO,
|
media_content_type=MediaType.VIDEO,
|
||||||
title=file_name,
|
title=file_name,
|
||||||
|
@ -51,8 +51,10 @@ TEST_DAY = 14
|
|||||||
TEST_DAY2 = 15
|
TEST_DAY2 = 15
|
||||||
TEST_HOUR = 13
|
TEST_HOUR = 13
|
||||||
TEST_MINUTE = 12
|
TEST_MINUTE = 12
|
||||||
TEST_FILE_NAME = f"{TEST_YEAR}{TEST_MONTH}{TEST_DAY}{TEST_HOUR}{TEST_MINUTE}00"
|
TEST_START = f"{TEST_YEAR}{TEST_MONTH}{TEST_DAY}{TEST_HOUR}{TEST_MINUTE}"
|
||||||
TEST_FILE_NAME_MP4 = f"{TEST_YEAR}{TEST_MONTH}{TEST_DAY}{TEST_HOUR}{TEST_MINUTE}00.mp4"
|
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_STREAM = "main"
|
||||||
TEST_CHANNEL = "0"
|
TEST_CHANNEL = "0"
|
||||||
TEST_CAM_NAME = "Cam new name"
|
TEST_CAM_NAME = "Cam new name"
|
||||||
@ -92,17 +94,15 @@ async def test_resolve(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
|
|
||||||
file_id = (
|
file_id = f"FILE|{config_entry.entry_id}|{TEST_CHANNEL}|{TEST_STREAM}|{TEST_FILE_NAME}|{TEST_START}|{TEST_END}"
|
||||||
f"FILE|{config_entry.entry_id}|{TEST_CHANNEL}|{TEST_STREAM}|{TEST_FILE_NAME}"
|
reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL)
|
||||||
)
|
|
||||||
reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE, TEST_URL)
|
|
||||||
|
|
||||||
play_media = await async_resolve_media(
|
play_media = await async_resolve_media(
|
||||||
hass, f"{URI_SCHEME}{DOMAIN}/{file_id}", None
|
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)
|
reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL2)
|
||||||
|
|
||||||
play_media = await async_resolve_media(
|
play_media = await async_resolve_media(
|
||||||
@ -117,9 +117,7 @@ async def test_resolve(
|
|||||||
)
|
)
|
||||||
assert play_media.mime_type == TEST_MIME_TYPE_MP4
|
assert play_media.mime_type == TEST_MIME_TYPE_MP4
|
||||||
|
|
||||||
file_id = (
|
file_id = f"FILE|{config_entry.entry_id}|{TEST_CHANNEL}|{TEST_STREAM}|{TEST_FILE_NAME}|{TEST_START}|{TEST_END}"
|
||||||
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)
|
reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE, TEST_URL)
|
||||||
|
|
||||||
play_media = await async_resolve_media(
|
play_media = await async_resolve_media(
|
||||||
@ -217,6 +215,8 @@ async def test_browsing(
|
|||||||
mock_vod_file.start_time = datetime(
|
mock_vod_file.start_time = datetime(
|
||||||
TEST_YEAR, TEST_MONTH, TEST_DAY, TEST_HOUR, TEST_MINUTE
|
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.duration = timedelta(minutes=15)
|
||||||
mock_vod_file.file_name = TEST_FILE_NAME
|
mock_vod_file.file_name = TEST_FILE_NAME
|
||||||
reolink_connect.request_vod_files.return_value = ([mock_status], [mock_vod_file])
|
reolink_connect.request_vod_files.return_value = ([mock_status], [mock_vod_file])
|
||||||
@ -224,7 +224,7 @@ async def test_browsing(
|
|||||||
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}")
|
||||||
|
|
||||||
browse_files_id = f"FILES|{entry_id}|{TEST_CHANNEL}|{TEST_STREAM}"
|
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.domain == DOMAIN
|
||||||
assert (
|
assert (
|
||||||
browse.title
|
browse.title
|
||||||
|
Loading…
x
Reference in New Issue
Block a user