"""Media browser for Emby Media Player integration.""" from __future__ import annotations import logging from typing import Any from homeassistant.components.media_player import BrowseMedia, MediaClass, MediaType from homeassistant.core import HomeAssistant from .api import EmbyApiClient from .const import ( ITEM_TYPE_AUDIO, ITEM_TYPE_COLLECTION_FOLDER, ITEM_TYPE_EPISODE, ITEM_TYPE_FOLDER, ITEM_TYPE_MOVIE, ITEM_TYPE_MUSIC_ALBUM, ITEM_TYPE_MUSIC_ARTIST, ITEM_TYPE_PLAYLIST, ITEM_TYPE_SEASON, ITEM_TYPE_SERIES, ITEM_TYPE_USER_VIEW, ) _LOGGER = logging.getLogger(__name__) # Map Emby item types to Home Assistant media classes ITEM_TYPE_TO_MEDIA_CLASS: dict[str, MediaClass] = { ITEM_TYPE_MOVIE: MediaClass.MOVIE, ITEM_TYPE_SERIES: MediaClass.TV_SHOW, ITEM_TYPE_SEASON: MediaClass.SEASON, ITEM_TYPE_EPISODE: MediaClass.EPISODE, ITEM_TYPE_AUDIO: MediaClass.TRACK, ITEM_TYPE_MUSIC_ALBUM: MediaClass.ALBUM, ITEM_TYPE_MUSIC_ARTIST: MediaClass.ARTIST, ITEM_TYPE_PLAYLIST: MediaClass.PLAYLIST, ITEM_TYPE_FOLDER: MediaClass.DIRECTORY, ITEM_TYPE_COLLECTION_FOLDER: MediaClass.DIRECTORY, ITEM_TYPE_USER_VIEW: MediaClass.DIRECTORY, } # Map Emby item types to Home Assistant media types ITEM_TYPE_TO_MEDIA_TYPE: dict[str, MediaType | str] = { ITEM_TYPE_MOVIE: MediaType.MOVIE, ITEM_TYPE_SERIES: MediaType.TVSHOW, ITEM_TYPE_SEASON: MediaType.SEASON, ITEM_TYPE_EPISODE: MediaType.EPISODE, ITEM_TYPE_AUDIO: MediaType.TRACK, ITEM_TYPE_MUSIC_ALBUM: MediaType.ALBUM, ITEM_TYPE_MUSIC_ARTIST: MediaType.ARTIST, ITEM_TYPE_PLAYLIST: MediaType.PLAYLIST, } # Item types that can be played directly PLAYABLE_ITEM_TYPES = { ITEM_TYPE_MOVIE, ITEM_TYPE_EPISODE, ITEM_TYPE_AUDIO, } # Item types that can be expanded (have children) EXPANDABLE_ITEM_TYPES = { ITEM_TYPE_SERIES, ITEM_TYPE_SEASON, ITEM_TYPE_MUSIC_ALBUM, ITEM_TYPE_MUSIC_ARTIST, ITEM_TYPE_PLAYLIST, ITEM_TYPE_FOLDER, ITEM_TYPE_COLLECTION_FOLDER, ITEM_TYPE_USER_VIEW, } async def async_browse_media( hass: HomeAssistant, api: EmbyApiClient, user_id: str, media_content_type: MediaType | str | None, media_content_id: str | None, ) -> BrowseMedia: """Browse Emby media library.""" if media_content_id is None or media_content_id == "": # Return root - library views return await _build_root_browse(api, user_id) # Browse specific item/folder return await _build_item_browse(api, user_id, media_content_id) async def _build_root_browse(api: EmbyApiClient, user_id: str) -> BrowseMedia: """Build root browse media structure (library views).""" views = await api.get_views(user_id) children = [] for view in views: item_id = view.get("Id") name = view.get("Name", "Unknown") item_type = view.get("Type", ITEM_TYPE_USER_VIEW) collection_type = view.get("CollectionType", "") # Determine media class based on collection type if collection_type == "movies": media_class = MediaClass.MOVIE elif collection_type == "tvshows": media_class = MediaClass.TV_SHOW elif collection_type == "music": media_class = MediaClass.MUSIC else: media_class = MediaClass.DIRECTORY thumbnail = api.get_image_url(item_id, max_width=300) if item_id else None children.append( BrowseMedia( media_class=media_class, media_content_id=item_id, media_content_type=MediaType.CHANNELS, # Library view title=name, can_play=False, can_expand=True, thumbnail=thumbnail, ) ) return BrowseMedia( media_class=MediaClass.DIRECTORY, media_content_id="", media_content_type=MediaType.CHANNELS, title="Emby Library", can_play=False, can_expand=True, children=children, ) async def _build_item_browse( api: EmbyApiClient, user_id: str, item_id: str ) -> BrowseMedia: """Build browse media structure for a specific item.""" # Get the item details item = await api.get_item(user_id, item_id) item_type = item.get("Type", "") item_name = item.get("Name", "Unknown") # Get children items children_data = await api.get_items( user_id=user_id, parent_id=item_id, limit=200, fields=["PrimaryImageAspectRatio", "BasicSyncInfo", "Overview"], ) children = [] for child in children_data.get("Items", []): child_media = _build_browse_media_item(api, child) if child_media: children.append(child_media) # Determine media class and type for parent media_class = ITEM_TYPE_TO_MEDIA_CLASS.get(item_type, MediaClass.DIRECTORY) media_type = ITEM_TYPE_TO_MEDIA_TYPE.get(item_type, MediaType.CHANNELS) thumbnail = api.get_image_url(item_id, max_width=300) return BrowseMedia( media_class=media_class, media_content_id=item_id, media_content_type=media_type, title=item_name, can_play=item_type in PLAYABLE_ITEM_TYPES, can_expand=True, children=children, thumbnail=thumbnail, ) def _build_browse_media_item(api: EmbyApiClient, item: dict[str, Any]) -> BrowseMedia | None: """Build a BrowseMedia item from Emby item data.""" item_id = item.get("Id") if not item_id: return None item_type = item.get("Type", "") name = item.get("Name", "Unknown") # Build title for episodes with season/episode numbers if item_type == ITEM_TYPE_EPISODE: season_num = item.get("ParentIndexNumber") episode_num = item.get("IndexNumber") if season_num is not None and episode_num is not None: name = f"S{season_num:02d}E{episode_num:02d} - {name}" elif episode_num is not None: name = f"E{episode_num:02d} - {name}" # Build title for tracks with track number if item_type == ITEM_TYPE_AUDIO: track_num = item.get("IndexNumber") artists = item.get("Artists", []) if track_num is not None: name = f"{track_num}. {name}" if artists: name = f"{name} - {', '.join(artists)}" # Get media class and type media_class = ITEM_TYPE_TO_MEDIA_CLASS.get(item_type, MediaClass.VIDEO) media_type = ITEM_TYPE_TO_MEDIA_TYPE.get(item_type, MediaType.VIDEO) # Determine if playable/expandable can_play = item_type in PLAYABLE_ITEM_TYPES can_expand = item_type in EXPANDABLE_ITEM_TYPES # Get thumbnail URL # For episodes, prefer series or season image image_item_id = item_id if item_type == ITEM_TYPE_EPISODE: image_item_id = item.get("SeriesId") or item.get("SeasonId") or item_id elif item_type == ITEM_TYPE_AUDIO: image_item_id = item.get("AlbumId") or item_id thumbnail = api.get_image_url(image_item_id, max_width=300) return BrowseMedia( media_class=media_class, media_content_id=item_id, media_content_type=media_type, title=name, can_play=can_play, can_expand=can_expand, thumbnail=thumbnail, )