async def _build_recent( self, data: ProtectData, camera_id: str, event_type: SimpleEventType, days: int, build_children: bool = False, ) -> BrowseMediaSource: """Build media source for events in relative days.""" base_id = f"{data.api.bootstrap.nvr.id}:browse:{camera_id}:{event_type.value}" title = f"Last {days} Days" if days == 1: title = "Last 24 Hours" source = BrowseMediaSource( domain=DOMAIN, identifier=f"{base_id}:recent:{days}", media_class=MEDIA_CLASS_DIRECTORY, media_content_type="video/mp4", title=title, can_play=False, can_expand=True, children_media_class=MEDIA_CLASS_VIDEO, ) if not build_children: return source now = dt_util.now() args = { "data": data, "start": now - timedelta(days=days), "end": now, "reserve": True, } if event_type != SimpleEventType.ALL: args["event_type"] = get_ufp_event(event_type) camera: Camera | None = None if camera_id != "all": camera = data.api.bootstrap.cameras.get(camera_id) args["camera_id"] = camera_id events = await self._build_events(**args) # type: ignore[arg-type] source.children = events source.title = self._breadcrumb( data, title, camera=camera, event_type=event_type, count=len(events), ) return source
async def _build_month( self, data: ProtectData, camera_id: str, event_type: SimpleEventType, start: date, build_children: bool = False, ) -> BrowseMediaSource: """Build media source for selectors for a given month.""" base_id = f"{data.api.bootstrap.nvr.id}:browse:{camera_id}:{event_type.value}" title = f"{start.strftime('%B %Y')}" source = BrowseMediaSource( domain=DOMAIN, identifier=f"{base_id}:range:{start.year}:{start.month}", media_class=MEDIA_CLASS_DIRECTORY, media_content_type=VIDEO_FORMAT, title=title, can_play=False, can_expand=True, children_media_class=MEDIA_CLASS_VIDEO, ) if not build_children: return source month = start.month children = [ self._build_days(data, camera_id, event_type, start, is_all=True) ] while start.month == month: children.append( self._build_days(data, camera_id, event_type, start, is_all=False)) start = start + timedelta(hours=24) camera: Camera | None = None if camera_id != "all": camera = data.api.bootstrap.cameras.get(camera_id) source.children = await asyncio.gather(*children) source.title = self._breadcrumb( data, title, camera=camera, event_type=event_type, ) return source
async def _build_events_type( self, data: ProtectData, camera_id: str, event_type: SimpleEventType, build_children: bool = False, ) -> BrowseMediaSource: """Build folder media source for a selectors for a given event type.""" base_id = f"{data.api.bootstrap.nvr.id}:browse:{camera_id}:{event_type.value}" title = EVENT_NAME_MAP[event_type].title() source = BrowseMediaSource( domain=DOMAIN, identifier=base_id, media_class=MEDIA_CLASS_DIRECTORY, media_content_type=VIDEO_FORMAT, title=title, can_play=False, can_expand=True, children_media_class=MEDIA_CLASS_VIDEO, ) if not build_children or data.api.bootstrap.recording_start is None: return source children = [ self._build_recent(data, camera_id, event_type, 1), self._build_recent(data, camera_id, event_type, 7), self._build_recent(data, camera_id, event_type, 30), ] start, end = _get_start_end(self.hass, data.api.bootstrap.recording_start) while end > start: children.append( self._build_month(data, camera_id, event_type, end.date())) end = (end - timedelta(days=1)).replace(day=1) camera: Camera | None = None if camera_id != "all": camera = data.api.bootstrap.cameras.get(camera_id) source.children = await asyncio.gather(*children) source.title = self._breadcrumb(data, title, camera=camera) return source
async def async_library_items(jelly_cm: JellyfinClientManager, media_content_type_in=None, media_content_id_in=None, canPlayList=True) -> BrowseMediaSource: """ Create response payload to describe contents of a specific library. Used by async_browse_media. """ _LOGGER.debug( f'>> async_library_items: {media_content_id_in} / {canPlayList}') library_info = None query = None if (media_content_type_in is None): media_content_type = None media_content_id = None else: media_content_type, media_content_id = JellyfinSource.parse_mediasource_identifier( media_content_id_in) _LOGGER.debug( f'-- async_library_items: {media_content_type} / {media_content_id}') if media_content_type in [None, "library"]: library_info = BrowseMediaSource( domain=DOMAIN, identifier=f'library{IDENTIFIER_SPLIT}library', media_class=MEDIA_CLASS_DIRECTORY, media_content_type="library", title="Media Library", can_play=False, can_expand=True, children=[], ) elif media_content_type in [ MEDIA_CLASS_DIRECTORY, MEDIA_TYPE_ARTIST, MEDIA_TYPE_ALBUM, MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_SEASON ]: query = { "ParentId": media_content_id, "sortBy": "SortName", "sortOrder": "Ascending" } parent_item = await jelly_cm.get_item(media_content_id) library_info = BrowseMediaSource( domain=DOMAIN, identifier= f'{media_content_type}{IDENTIFIER_SPLIT}{media_content_id}', media_class=media_content_type, media_content_type=media_content_type, title=parent_item["Name"], can_play=IsPlayable(parent_item["Type"], canPlayList), can_expand=True, thumbnail=jelly_cm.get_artwork_url(media_content_id), children=[], ) else: query = {"Id": media_content_id} library_info = BrowseMediaSource( domain=DOMAIN, identifier= f'{media_content_type}{IDENTIFIER_SPLIT}{media_content_id}', media_class=MEDIA_CLASS_DIRECTORY, media_content_type=media_content_type, title="", can_play=True, can_expand=False, thumbnail=jelly_cm.get_artwork_url(media_content_id), children=[], ) _LOGGER.debug(f'-- async_library_items: 1') items = await jelly_cm.get_items(query) for item in items: if media_content_type in [ None, "library", MEDIA_CLASS_DIRECTORY, MEDIA_TYPE_ARTIST, MEDIA_TYPE_ALBUM, MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_SEASON ]: if item["IsFolder"]: library_info.children_media_class = MEDIA_CLASS_DIRECTORY library_info.children.append( BrowseMediaSource( domain=DOMAIN, identifier= f'{Type2Mediatype(item["Type"])}{IDENTIFIER_SPLIT}{item["Id"]}', media_class=Type2Mediaclass(item["Type"]), media_content_type=Type2Mimetype(item["Type"]), title=item["Name"], can_play=IsPlayable(item["Type"], canPlayList), can_expand=True, children=[], thumbnail=jelly_cm.get_artwork_url(item["Id"]))) else: library_info.children_media_class = Type2Mediaclass( item["Type"]) library_info.children.append( BrowseMediaSource( domain=DOMAIN, identifier= f'{Type2Mediatype(item["Type"])}{IDENTIFIER_SPLIT}{item["Id"]}', media_class=Type2Mediaclass(item["Type"]), media_content_type=Type2Mimetype(item["Type"]), title=item["Name"], can_play=IsPlayable(item["Type"], canPlayList), can_expand=False, children=[], thumbnail=jelly_cm.get_artwork_url(item["Id"]))) else: library_info.domain = DOMAIN library_info.identifier = f'{Type2Mediatype(item["Type"])}{IDENTIFIER_SPLIT}{item["Id"]}', library_info.title = item["Name"] library_info.media_content_type = Type2Mimetype(item["Type"]) library_info.media_class = Type2Mediaclass(item["Type"]) library_info.can_expand = False library_info.can_play = IsPlayable(item["Type"], canPlayList), break _LOGGER.debug(f'<< async_library_items {library_info.as_dict()}') return library_info
async def _build_camera(self, data: ProtectData, camera_id: str, build_children: bool = False) -> BrowseMediaSource: """Build media source for selectors for a UniFi Protect camera.""" name = "All Cameras" is_doorbell = data.api.bootstrap.has_doorbell has_smart = data.api.bootstrap.has_smart_detections camera: Camera | None = None if camera_id != "all": camera = data.api.bootstrap.cameras.get(camera_id) if camera is None: raise BrowseError(f"Unknown Camera ID: {camera_id}") name = camera.name or camera.market_name or camera.type is_doorbell = camera.feature_flags.has_chime has_smart = camera.feature_flags.has_smart_detect thumbnail_url: str | None = None if camera is not None: thumbnail_url = await self._get_camera_thumbnail_url(camera) source = BrowseMediaSource( domain=DOMAIN, identifier=f"{data.api.bootstrap.nvr.id}:browse:{camera_id}", media_class=MEDIA_CLASS_DIRECTORY, media_content_type=VIDEO_FORMAT, title=name, can_play=False, can_expand=True, thumbnail=thumbnail_url, children_media_class=MEDIA_CLASS_VIDEO, ) if not build_children: return source source.children = [ await self._build_events_type(data, camera_id, SimpleEventType.MOTION), ] if is_doorbell: source.children.insert( 0, await self._build_events_type(data, camera_id, SimpleEventType.RING), ) if has_smart: source.children.append(await self._build_events_type( data, camera_id, SimpleEventType.SMART)) if is_doorbell or has_smart: source.children.insert( 0, await self._build_events_type(data, camera_id, SimpleEventType.ALL), ) source.title = self._breadcrumb(data, name) return source
async def _build_days( self, data: ProtectData, camera_id: str, event_type: SimpleEventType, start: date, is_all: bool = True, build_children: bool = False, ) -> BrowseMediaSource: """Build media source for events for a given day or whole month.""" base_id = f"{data.api.bootstrap.nvr.id}:browse:{camera_id}:{event_type.value}" if is_all: title = "Whole Month" identifier = f"{base_id}:range:{start.year}:{start.month}:all" else: title = f"{start.strftime('%x')}" identifier = f"{base_id}:range:{start.year}:{start.month}:{start.day}" source = BrowseMediaSource( domain=DOMAIN, identifier=identifier, media_class=MEDIA_CLASS_DIRECTORY, media_content_type=VIDEO_FORMAT, title=title, can_play=False, can_expand=True, children_media_class=MEDIA_CLASS_VIDEO, ) if not build_children: return source start_dt = datetime( year=start.year, month=start.month, day=start.day, hour=0, minute=0, second=0, tzinfo=dt_util.DEFAULT_TIME_ZONE, ) if is_all: if start_dt.month < 12: end_dt = start_dt.replace(month=start_dt.month + 1) else: end_dt = start_dt.replace(year=start_dt.year + 1, month=1) else: end_dt = start_dt + timedelta(hours=24) args = { "data": data, "start": start_dt, "end": end_dt, "reserve": False, } if event_type != SimpleEventType.ALL: args["event_type"] = get_ufp_event(event_type) camera: Camera | None = None if camera_id != "all": camera = data.api.bootstrap.cameras.get(camera_id) args["camera_id"] = camera_id title = f"{start.strftime('%B %Y')} > {title}" events = await self._build_events(**args) # type: ignore[arg-type] source.children = events source.title = self._breadcrumb( data, title, camera=camera, event_type=event_type, count=len(events), ) return source