def _build_root_paths( entry: ConfigEntry, media_directories: MediaDirectories, ) -> BrowseMediaSource: """Build base categories for System Bridge media.""" return BrowseMediaSource( domain=DOMAIN, identifier="", media_class=MEDIA_CLASS_DIRECTORY, media_content_type="", title=entry.title, can_play=False, can_expand=True, children=[ BrowseMediaSource( domain=DOMAIN, identifier=f"{entry.entry_id}~~{directory.key}", media_class=MEDIA_CLASS_DIRECTORY, media_content_type="", title=f"{directory.key[:1].capitalize()}{directory.key[1:]}", can_play=False, can_expand=True, children=[], children_media_class=MEDIA_CLASS_DIRECTORY, ) for directory in media_directories.directories ], children_media_class=MEDIA_CLASS_DIRECTORY, )
def _build_bridges(self) -> BrowseMediaSource: """Build bridges for System Bridge media.""" children = [] for entry in self.hass.config_entries.async_entries(DOMAIN): if entry.entry_id is not None: children.append( BrowseMediaSource( domain=DOMAIN, identifier=entry.entry_id, media_class=MEDIA_CLASS_DIRECTORY, media_content_type="", title=entry.title, can_play=False, can_expand=True, children=[], children_media_class=MEDIA_CLASS_DIRECTORY, )) return BrowseMediaSource( domain=DOMAIN, identifier="", media_class=MEDIA_CLASS_DIRECTORY, media_content_type="", title=self.name, can_play=False, can_expand=True, children=children, children_media_class=MEDIA_CLASS_DIRECTORY, )
def _browse_recording_folders(self, identifier, folders): base = BrowseMediaSource( domain=DOMAIN, identifier=identifier["original"], media_class=MEDIA_CLASS_DIRECTORY, children_media_class=MEDIA_CLASS_VIDEO, media_content_type=None, title=self._generate_recording_title(identifier), can_play=False, can_expand=True, thumbnail=None, children=[ BrowseMediaSource( domain=DOMAIN, identifier=self._create_recordings_folder_identifier(identifier, folder), media_class=MEDIA_CLASS_DIRECTORY, children_media_class=MEDIA_CLASS_VIDEO, media_content_type=None, title=self._generate_recording_title(identifier, folder), can_play=False, can_expand=True, thumbnail=None ) for folder in folders if not folder['name'].endswith('.mp4') ] ) return base
def _build_main_listing(self): """Build main browse listing.""" parent_source = BrowseMediaSource( domain=DOMAIN, identifier=None, title=self.entry.title, media_class=MEDIA_CLASS_CHANNEL, media_content_type=MEDIA_TYPE_MUSIC, can_play=False, can_expand=True, children_media_class=MEDIA_CLASS_DIRECTORY, children=[], ) for library, media_class in LIBRARY_MEDIA_CLASS_MAP.items(): child_source = BrowseMediaSource( domain=DOMAIN, identifier=library, title=LIBRARY_TITLE_MAP[library], media_class=MEDIA_CLASS_DIRECTORY, media_content_type=MEDIA_TYPE_MUSIC, children_media_class=media_class, can_play=False, can_expand=True, ) parent_source.children.append(child_source) return parent_source
async def async_browse_search(self, query: str) -> BrowseMediaSource: """Return all media items found by the query string.""" assert self._device result = await self._device.async_search_directory( container_id=ROOT_OBJECT_ID, search_criteria=query, metadata_filter=DLNA_BROWSE_FILTER, ) children = [ self._didl_to_media_source(child) for child in result.result if isinstance(child, didl_lite.DidlObject) ] media_source = BrowseMediaSource( domain=DOMAIN, identifier=self._make_identifier(Action.SEARCH, query), media_class=MEDIA_CLASS_DIRECTORY, media_content_type="", title="Search results", can_play=False, can_expand=True, children=children, ) if media_source.children: media_source.calculate_children_class() return media_source
def _browse_recordings(self, identifier, recordings): base = BrowseMediaSource( domain=DOMAIN, identifier=identifier["original"], media_class=MEDIA_CLASS_DIRECTORY, children_media_class=MEDIA_CLASS_VIDEO, media_content_type=None, title=self._generate_recording_title(identifier), can_play=False, can_expand=True, thumbnail=None, children=[ BrowseMediaSource( domain=DOMAIN, identifier=f"{identifier['original']}/{recording['name']}", media_class=MEDIA_CLASS_VIDEO, media_content_type=MEDIA_TYPE_VIDEO, title=self._generate_recording_title(identifier, recording), can_play=True, can_expand=False, thumbnail=None, ) for recording in recordings ] ) return base
def _build_categories(title): """Build base categories for Xbox media.""" _, name, thumbnail = title.split("#", 2) base = BrowseMediaSource( domain=DOMAIN, identifier=f"{title}", media_class=MEDIA_CLASS_GAME, media_content_type="", title=name, can_play=False, can_expand=True, children=[], children_media_class=MEDIA_CLASS_DIRECTORY, thumbnail=thumbnail, ) owners = ["my", "community"] kinds = ["gameclips", "screenshots"] for owner in owners: for kind in kinds: base.children.append( BrowseMediaSource( domain=DOMAIN, identifier=f"{title}~~{owner}#{kind}", media_class=MEDIA_CLASS_DIRECTORY, media_content_type="", title=f"{owner.title()} {kind.title()}", can_play=False, can_expand=True, children_media_class=MEDIA_CLASS_MAP[kind], ) ) return base
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 _async_build_by_tag( self, item: MediaSourceItem ) -> list[BrowseMediaSource]: """Handle browsing radio stations by tags.""" category, _, tag = (item.identifier or "").partition("/") if category == "tag" and tag: stations = await self.radios.stations( filter_by=FilterBy.TAG_EXACT, filter_term=tag, hide_broken=True, order=Order.NAME, reverse=False, ) return self._async_build_stations(stations) if category == "tag": tags = await self.radios.tags( hide_broken=True, limit=100, order=Order.STATION_COUNT, reverse=True, ) # Now we have the top tags, reorder them by name tags.sort(key=lambda tag: tag.name) return [ BrowseMediaSource( domain=DOMAIN, identifier=f"tag/{tag.name}", media_class=MEDIA_CLASS_DIRECTORY, media_content_type=MEDIA_TYPE_MUSIC, title=tag.name.title(), can_play=False, can_expand=True, ) for tag in tags ] if not item.identifier: return [ BrowseMediaSource( domain=DOMAIN, identifier="tag", media_class=MEDIA_CLASS_DIRECTORY, media_content_type=MEDIA_TYPE_MUSIC, title="By Category", can_play=False, can_expand=True, ) ] return []
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 async_browse_media( self, item: MediaSourceItem, ) -> BrowseMediaSource: """Return media.""" if item.identifier: raise BrowseError("Unknown item") can_stream_hls = "stream" in self.hass.config.components # Root. List cameras. component: EntityComponent = self.hass.data[DOMAIN] children = [] not_shown = 0 for camera in component.entities: camera = cast(Camera, camera) stream_type = camera.frontend_stream_type if stream_type is None: content_type = camera.content_type elif can_stream_hls and stream_type == StreamType.HLS: content_type = FORMAT_CONTENT_TYPE[HLS_PROVIDER] else: not_shown += 1 continue children.append( BrowseMediaSource( domain=DOMAIN, identifier=camera.entity_id, media_class=MEDIA_CLASS_VIDEO, media_content_type=content_type, title=camera.name, thumbnail=f"/api/camera_proxy/{camera.entity_id}", can_play=True, can_expand=False, )) return BrowseMediaSource( domain=DOMAIN, identifier=None, media_class=MEDIA_CLASS_APP, media_content_type="", title="Camera", can_play=False, can_expand=True, children_media_class=MEDIA_CLASS_VIDEO, children=children, not_shown=not_shown, )
async def async_browse_media(self, item: MediaSourceItem) -> BrowseMediaSource: """Browse media.""" dms_data = get_domain_data(self.hass) if not dms_data.sources: raise BrowseError("No sources have been configured") source_id, media_id = _parse_identifier(item) LOGGER.debug("Browsing for %s / %s", source_id, media_id) if not source_id and len(dms_data.sources) > 1: # Browsing the root of dlna_dms with more than one server, return # all known servers. base = BrowseMediaSource( domain=DOMAIN, identifier="", media_class=MEDIA_CLASS_DIRECTORY, media_content_type=MEDIA_TYPE_CHANNELS, title=self.name, can_play=False, can_expand=True, children_media_class=MEDIA_CLASS_CHANNEL, ) base.children = [ BrowseMediaSource( domain=DOMAIN, identifier= f"{source_id}/{PATH_OBJECT_ID_FLAG}{ROOT_OBJECT_ID}", media_class=MEDIA_CLASS_CHANNEL, media_content_type=MEDIA_TYPE_CHANNEL, title=source.name, can_play=False, can_expand=True, thumbnail=source.icon, ) for source_id, source in dms_data.sources.items() ] return base if not source_id: # No source specified, default to the first registered source_id = next(iter(dms_data.sources)) try: source = dms_data.sources[source_id] except KeyError as err: raise BrowseError(f"Unknown source ID: {source_id}") from err return await source.async_browse_media(media_id)
def _didl_to_media_source( self, item: didl_lite.DidlObject, browsed_children: DmsDevice.BrowseResult | None = None, ) -> BrowseMediaSource: """Convert a DIDL-Lite object to a browse media source.""" children: list[BrowseMediaSource] | None = None if browsed_children: children = [ self._didl_to_media_source(child) for child in browsed_children.result if isinstance(child, didl_lite.DidlObject) ] # Can expand if it has children (even if we don't have them yet), or its # a container type. Otherwise the front-end will try to play it (even if # can_play is False). try: child_count = int(item.child_count) except (AttributeError, TypeError, ValueError): child_count = 0 can_expand = (bool(children) or child_count > 0 or isinstance(item, didl_lite.Container)) # Can play if item has any resource that can be streamed over the network can_play = any(_resource_is_streaming(res) for res in item.res) # Use server name for root object, not "root" title = self.name if item.id == ROOT_OBJECT_ID else item.title mime_type = _resource_mime_type(item.res[0]) if item.res else None media_content_type = mime_type or item.upnp_class media_source = BrowseMediaSource( domain=DOMAIN, identifier=self._make_identifier(Action.OBJECT, item.id), media_class=MEDIA_CLASS_MAP.get(item.upnp_class, ""), media_content_type=media_content_type, title=title, can_play=can_play, can_expand=can_expand, children=children, thumbnail=self._didl_thumbnail_url(item), ) if media_source.children: media_source.calculate_children_class() return media_source
def _build_item_response(self, iteminfo: ItemInfo, path: Path, is_child=False): mime_type, _ = mimetypes.guess_type(str(path)) is_file = path.is_file() is_dir = path.is_dir() # Make sure it's a file or directory if not is_file and not is_dir: return None # Check that it's a media file if is_file and (not mime_type or mime_type.split("/")[0] not in MEDIA_MIME_TYPES): return None title = path.name if is_dir: title += "/" media_class = MEDIA_CLASS_MAP.get( mime_type and mime_type.split("/")[0], MEDIA_CLASS_DIRECTORY) media = BrowseMediaSource( domain=DOMAIN, identifier= f"{iteminfo.entry_id}/{path.relative_to(iteminfo.target_dir)}", media_class=media_class, media_content_type=mime_type or "", title=title, can_play=is_file, can_expand=is_dir, ) if is_file or is_child: return media # Append first level children media.children = [] for child_path in path.iterdir(): child = self._build_item_response(iteminfo, child_path, True) if child: media.children.append(child) # Sort children showing directories first, then by name media.children.sort(key=lambda child: (child.can_play, child.title)) return media
async def _async_build_by_country( self, radios: RadioBrowser, item: MediaSourceItem) -> list[BrowseMediaSource]: """Handle browsing radio stations by country.""" category, _, country_code = (item.identifier or "").partition("/") if country_code: stations = await radios.stations( filter_by=FilterBy.COUNTRY_CODE_EXACT, filter_term=country_code, hide_broken=True, order=Order.NAME, reverse=False, ) return self._async_build_stations(radios, stations) # We show country in the root additionally, when there is no item if not item.identifier or category == "country": countries = await radios.countries(order=Order.NAME) return [ BrowseMediaSource( domain=DOMAIN, identifier=f"country/{country.code}", media_class=MEDIA_CLASS_DIRECTORY, media_content_type=MEDIA_TYPE_MUSIC, title=country.name, can_play=False, can_expand=True, thumbnail=country.favicon, ) for country in countries ] return []
def _browse_recordings( self, identifier: RecordingIdentifier, recordings: list[dict[str, Any]]) -> BrowseMediaSource: """Browse Frigate recordings.""" base = self._get_recording_base_media_source(identifier) for recording in recordings: title = self._generate_recording_title(identifier, recording) if not title: _LOGGER.warning("Skipping non-standard recording name: %s", recording["name"]) continue base.children.append( BrowseMediaSource( domain=DOMAIN, identifier=attr.evolve(identifier, recording_name=recording["name"]), media_class=identifier.media_class, media_content_type=identifier.media_type, title=title, can_play=True, can_expand=False, thumbnail=None, )) return base
async def async_browse_media( self, item: MediaSourceItem, ) -> BrowseMediaSource: """Return media.""" radios = self.radios if radios is None: raise BrowseError("Radio Browser not initialized") return BrowseMediaSource( domain=DOMAIN, identifier=None, media_class=MEDIA_CLASS_CHANNEL, media_content_type=MEDIA_TYPE_MUSIC, title=self.entry.title, can_play=False, can_expand=True, children_media_class=MEDIA_CLASS_DIRECTORY, children=[ *await self._async_build_popular(radios, item), *await self._async_build_by_tag(radios, item), *await self._async_build_by_language(radios, item), *await self._async_build_by_country(radios, item), ], )
async def _async_build_popular( self, radios: RadioBrowser, item: MediaSourceItem) -> list[BrowseMediaSource]: """Handle browsing popular radio stations.""" if item.identifier == "popular": stations = await radios.stations( hide_broken=True, limit=250, order=Order.CLICK_COUNT, reverse=True, ) return self._async_build_stations(radios, stations) if not item.identifier: return [ BrowseMediaSource( domain=DOMAIN, identifier="popular", media_class=MEDIA_CLASS_DIRECTORY, media_content_type=MEDIA_TYPE_MUSIC, title="Popular", can_play=False, can_expand=True, ) ] return []
async def async_browse_media( self, item: MediaSourceItem, ) -> BrowseMediaSource: """Return media.""" if item.identifier: provider, _, params = item.identifier.partition("?") return self._provider_item(provider, params) # Root. List providers. manager: SpeechManager = self.hass.data[DOMAIN] children = [ self._provider_item(provider) for provider in manager.providers ] return BrowseMediaSource( domain=DOMAIN, identifier=None, media_class=MEDIA_CLASS_APP, media_content_type="", title=self.name, can_play=False, can_expand=True, children_media_class=MEDIA_CLASS_APP, children=children, )
async def _build_item(mass: MusicAssistant, item: MediaItemType, can_expand=True, media_class=None): """Return BrowseMediaSource for MediaItem.""" if hasattr(item, "artists"): title = f"{item.artists[0].name} - {item.name}" else: title = item.name url = await mass.metadata.get_image_url_for_item(item, allow_local=False, local_as_base64=False) if url and url.startswith("http"): url = f"https://images.weserv.nl/?w={THUMB_SIZE}&url={url}" # disable image proxy due to 'authSig' bug in HA frontend # elif url: # url = f"/api/mass/image_proxy?size={THUMB_SIZE}&url={url}" return BrowseMediaSource( domain=DOMAIN, identifier=item.uri, title=title, media_class=media_class or item.media_type.value, media_content_type=MEDIA_CONTENT_TYPE_FLAC, can_play=True, can_expand=can_expand, thumbnail=url, )
def _browse_recording_folders( self, identifier: RecordingIdentifier, folders: list[dict[str, Any]] ) -> BrowseMediaSource: """Browse Frigate recording folders.""" base = self._get_recording_base_media_source(identifier) for folder in folders: if folder["name"].endswith(".mp4"): continue title = self._generate_recording_title(identifier, folder) if not title: _LOGGER.warning("Skipping non-standard folder name: %s", folder["name"]) continue base.children.append( BrowseMediaSource( domain=DOMAIN, identifier=attr.evolve( identifier, **identifier.get_changes_to_set_next_empty(folder["name"]), ), media_class=MEDIA_CLASS_DIRECTORY, children_media_class=MEDIA_CLASS_DIRECTORY, media_content_type=identifier.media_type, title=title, can_play=False, can_expand=True, thumbnail=None, ) ) return base
def _build_media_kind( cls, config: ConfigEntry, device: dr.DeviceEntry, kind: str, full_title: bool = True, ) -> BrowseMediaSource: return BrowseMediaSource( domain=DOMAIN, identifier=f"{config.entry_id}#{device.id}#{kind}", media_class=MEDIA_CLASS_DIRECTORY, media_content_type=( MEDIA_TYPE_VIDEO if kind == "movies" else MEDIA_TYPE_IMAGE ), title=( f"{config.title} {device.name} {kind.title()}" if full_title else kind.title() ), can_play=False, can_expand=True, children_media_class=( MEDIA_CLASS_VIDEO if kind == "movies" else MEDIA_CLASS_IMAGE ), )
def _build_label_sources(self, identifier, shown_event_count) -> BrowseMediaSource: sources = [] for l in self.labels: after = int( identifier['after']) if not identifier['after'] == '' else None before = int(identifier['before'] ) if not identifier['before'] == '' else None count = self._count_by(after=after, before=before, camera=identifier['camera'], label=l, zone=identifier['zone']) if count == 0 or count == shown_event_count: continue sources.append( BrowseMediaSource( domain=DOMAIN, identifier= f"clips/{identifier['name']}.{l}/{identifier['after']}/{identifier['before']}/{identifier['camera']}/{l}/{identifier['zone']}", media_class=MEDIA_CLASS_DIRECTORY, children_media_class=MEDIA_CLASS_VIDEO, media_content_type=None, title=f"{l.replace('_', ' ').title()} ({count})", can_play=False, can_expand=True, thumbnail=None)) return sources
def _build_zone_sources( self, summary_data: EventSummaryData, identifier: EventSearchIdentifier, shown_event_count: int, ) -> BrowseMediaSource: """Build zone media sources.""" sources = [] for zone in summary_data.zones: count = self._count_by(summary_data, attr.evolve(identifier, zone=zone)) if count in (0, shown_event_count): continue sources.append( BrowseMediaSource( domain=DOMAIN, identifier=attr.evolve( identifier, name=f"{identifier.name}.{zone}", zone=zone, ), media_class=MEDIA_CLASS_DIRECTORY, children_media_class=MEDIA_CLASS_DIRECTORY, media_content_type=identifier.media_type, title=f"{get_friendly_name(zone)} ({count})", can_play=False, can_expand=True, thumbnail=None, ) ) return sources
async def async_browse_media( self, media_content_type: str = None, media_content_id: str = None, ) -> BrowseMedia: if not MediaBrowser.media_cache: conf = self.hass.data[DOMAIN][DATA_CONFIG] conf = conf.get("media_source") or MEDIA_DEFAULT MediaBrowser.media_cache = [YandexSource(**item) for item in conf] for media in MediaBrowser.media_cache: if (media.media_content_id == media_content_id and media.media_content_type == media_content_type): return media return BrowseMediaSource( title=self.name, children=MediaBrowser.media_cache, domain=None, identifier=None, media_class=None, media_content_type=None, can_play=False, can_expand=True, )
def _build_zone_sources(self, identifier, shown_event_count) -> BrowseMediaSource: sources = [] for z in self.zones: after = int(identifier["after"]) if not identifier["after"] == "" else None before = ( int(identifier["before"]) if not identifier["before"] == "" else None ) count = self._count_by( after=after, before=before, camera=identifier["camera"], label=identifier["label"], zone=z, ) if count == 0 or count == shown_event_count: continue sources.append( BrowseMediaSource( domain=DOMAIN, identifier=f"clips/{identifier['name']}.{z}/{identifier['after']}/{identifier['before']}/{identifier['camera']}/{identifier['label']}/{z}", media_class=MEDIA_CLASS_DIRECTORY, children_media_class=MEDIA_CLASS_VIDEO, media_content_type=MEDIA_CLASS_VIDEO, title=f"{z.replace('_', ' ').title()} ({count})", can_play=False, can_expand=True, thumbnail=None, ) ) return sources
def _build_event_response( cls, identifier: EventSearchIdentifier, events: list[dict[str, Any]] ) -> BrowseMediaSource: children = [] for event in events: children.append( BrowseMediaSource( domain=DOMAIN, identifier=EventIdentifier( identifier.frigate_instance_id, frigate_media_type=identifier.frigate_media_type, name=( f"{event['camera']}-{event['id']}." + identifier.frigate_media_type.extension ), ), media_class=identifier.media_class, media_content_type=identifier.media_type, title=f"{dt.datetime.fromtimestamp(event['start_time'], DEFAULT_TIME_ZONE).strftime(DATE_STR_FORMAT)} [{int(event['end_time']-event['start_time'])}s, {event['label'].capitalize()} {int(event['top_score']*100)}%]", can_play=identifier.media_type == MEDIA_TYPE_VIDEO, can_expand=False, thumbnail=f"data:image/jpeg;base64,{event['thumbnail']}", ) ) return children
async def _build_game_library(self): """Display installed games across all consoles.""" apps = await self.client.smartglass.get_installed_apps() games = { game.one_store_product_id: game for game in apps.result if game.is_game and game.title_id } app_details = await self.client.catalog.get_products( games.keys(), FieldsTemplate.BROWSE, ) images = { prod.product_id: prod.localized_properties[0].images for prod in app_details.products } return BrowseMediaSource( domain=DOMAIN, identifier="", media_class=MEDIA_CLASS_DIRECTORY, media_content_type="", title="Xbox Game Media", can_play=False, can_expand=True, children=[_build_game_item(game, images) for game in games.values()], children_media_class=MEDIA_CLASS_GAME, )
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_browse_media( self, item: MediaSourceItem, ) -> BrowseMediaSource: """Return media.""" if item.identifier: raise BrowseError("Unknown item") supported_stream_types: list[str | None] = [None] if "stream" in self.hass.config.components: supported_stream_types.append(STREAM_TYPE_HLS) # Root. List cameras. component: EntityComponent = self.hass.data[DOMAIN] children = [] for camera in component.entities: camera = cast(Camera, camera) stream_type = camera.frontend_stream_type if stream_type not in supported_stream_types: continue children.append( BrowseMediaSource( domain=DOMAIN, identifier=camera.entity_id, media_class=MEDIA_CLASS_VIDEO, media_content_type=FORMAT_CONTENT_TYPE[HLS_PROVIDER], title=camera.name, thumbnail=f"/api/camera_proxy/{camera.entity_id}", can_play=True, can_expand=False, )) return BrowseMediaSource( domain=DOMAIN, identifier=None, media_class=MEDIA_CLASS_APP, media_content_type="", title="Camera", can_play=False, can_expand=True, children_media_class=MEDIA_CLASS_VIDEO, children=children, )