Home Assistant Unofficial Reference 2024.12.1
browse_media.py
Go to the documentation of this file.
1 """Support for media browsing."""
2 
3 import asyncio
4 import contextlib
5 import logging
6 
7 from homeassistant.components import media_source
9  BrowseError,
10  BrowseMedia,
11  MediaClass,
12  MediaType,
13 )
14 
15 PLAYABLE_MEDIA_TYPES = [
16  MediaType.ALBUM,
17  MediaType.ARTIST,
18  MediaType.TRACK,
19 ]
20 
21 CONTAINER_TYPES_SPECIFIC_MEDIA_CLASS = {
22  MediaType.ALBUM: MediaClass.ALBUM,
23  MediaType.ARTIST: MediaClass.ARTIST,
24  MediaType.PLAYLIST: MediaClass.PLAYLIST,
25  MediaType.SEASON: MediaClass.SEASON,
26  MediaType.TVSHOW: MediaClass.TV_SHOW,
27 }
28 
29 CHILD_TYPE_MEDIA_CLASS = {
30  MediaType.SEASON: MediaClass.SEASON,
31  MediaType.ALBUM: MediaClass.ALBUM,
32  MediaType.ARTIST: MediaClass.ARTIST,
33  MediaType.MOVIE: MediaClass.MOVIE,
34  MediaType.PLAYLIST: MediaClass.PLAYLIST,
35  MediaType.TRACK: MediaClass.TRACK,
36  MediaType.TVSHOW: MediaClass.TV_SHOW,
37  MediaType.CHANNEL: MediaClass.CHANNEL,
38  MediaType.EPISODE: MediaClass.EPISODE,
39 }
40 
41 _LOGGER = logging.getLogger(__name__)
42 
43 
44 class UnknownMediaType(BrowseError):
45  """Unknown media type."""
46 
47 
48 async def build_item_response(media_library, payload, get_thumbnail_url=None):
49  """Create response payload for the provided media query."""
50  search_id = payload["search_id"]
51  search_type = payload["search_type"]
52 
53  _, title, media = await get_media_info(media_library, search_id, search_type)
54  thumbnail = await get_thumbnail_url(search_type, search_id)
55 
56  if media is None:
57  return None
58 
59  children = await asyncio.gather(
60  *(item_payload(item, get_thumbnail_url) for item in media)
61  )
62 
63  if search_type in (MediaType.TVSHOW, MediaType.MOVIE) and search_id == "":
64  children.sort(key=lambda x: x.title.replace("The ", "", 1), reverse=False)
65 
66  response = BrowseMedia(
67  media_class=CONTAINER_TYPES_SPECIFIC_MEDIA_CLASS.get(
68  search_type, MediaClass.DIRECTORY
69  ),
70  media_content_id=search_id,
71  media_content_type=search_type,
72  title=title,
73  can_play=search_type in PLAYABLE_MEDIA_TYPES and search_id,
74  can_expand=True,
75  children=children,
76  thumbnail=thumbnail,
77  )
78 
79  if search_type == "library_music":
80  response.children_media_class = MediaClass.MUSIC
81  else:
82  response.calculate_children_class()
83 
84  return response
85 
86 
87 async def item_payload(item, get_thumbnail_url=None):
88  """Create response payload for a single media item.
89 
90  Used by async_browse_media.
91  """
92  title = item["label"]
93 
94  media_class = None
95 
96  if "songid" in item:
97  media_content_type = MediaType.TRACK
98  media_content_id = f"{item['songid']}"
99  can_play = True
100  can_expand = False
101  elif "albumid" in item:
102  media_content_type = MediaType.ALBUM
103  media_content_id = f"{item['albumid']}"
104  can_play = True
105  can_expand = True
106  elif "artistid" in item:
107  media_content_type = MediaType.ARTIST
108  media_content_id = f"{item['artistid']}"
109  can_play = True
110  can_expand = True
111  elif "movieid" in item:
112  media_content_type = MediaType.MOVIE
113  media_content_id = f"{item['movieid']}"
114  can_play = True
115  can_expand = False
116  elif "episodeid" in item:
117  media_content_type = MediaType.EPISODE
118  media_content_id = f"{item['episodeid']}"
119  can_play = True
120  can_expand = False
121  elif "seasonid" in item:
122  media_content_type = MediaType.SEASON
123  media_content_id = f"{item['tvshowid']}/{item['season']}"
124  can_play = False
125  can_expand = True
126  elif "tvshowid" in item:
127  media_content_type = MediaType.TVSHOW
128  media_content_id = f"{item['tvshowid']}"
129  can_play = False
130  can_expand = True
131  elif "channelid" in item:
132  media_content_type = MediaType.CHANNEL
133  media_content_id = f"{item['channelid']}"
134  if broadcasting := item.get("broadcastnow"):
135  show = broadcasting.get("title")
136  title = f"{title} - {show}"
137  can_play = True
138  can_expand = False
139  else:
140  # this case is for the top folder of each type
141  # possible content types: album, artist, movie, library_music, tvshow, channel
142  media_class = MediaClass.DIRECTORY
143  media_content_type = item["type"]
144  media_content_id = ""
145  can_play = False
146  can_expand = True
147 
148  if media_class is None:
149  try:
150  media_class = CHILD_TYPE_MEDIA_CLASS[media_content_type]
151  except KeyError as err:
152  _LOGGER.debug("Unknown media type received: %s", media_content_type)
153  raise UnknownMediaType from err
154 
155  thumbnail = item.get("thumbnail")
156  if thumbnail is not None and get_thumbnail_url is not None:
157  thumbnail = await get_thumbnail_url(
158  media_content_type, media_content_id, thumbnail_url=thumbnail
159  )
160 
161  return BrowseMedia(
162  title=title,
163  media_class=media_class,
164  media_content_type=media_content_type,
165  media_content_id=media_content_id,
166  can_play=can_play,
167  can_expand=can_expand,
168  thumbnail=thumbnail,
169  )
170 
171 
172 def media_source_content_filter(item: BrowseMedia) -> bool:
173  """Content filter for media sources."""
174  # Filter out cameras using PNG over MJPEG. They don't work in Kodi.
175  return not (
176  item.media_content_id.startswith("media-source://camera/")
177  and item.media_content_type == "image/png"
178  )
179 
180 
181 async def library_payload(hass):
182  """Create response payload to describe contents of a specific library.
183 
184  Used by async_browse_media.
185  """
186  library_info = BrowseMedia(
187  media_class=MediaClass.DIRECTORY,
188  media_content_id="library",
189  media_content_type="library",
190  title="Media Library",
191  can_play=False,
192  can_expand=True,
193  children=[],
194  )
195 
196  library = {
197  "library_music": "Music",
198  MediaType.MOVIE: "Movies",
199  MediaType.TVSHOW: "TV shows",
200  MediaType.CHANNEL: "Channels",
201  }
202 
203  library_info.children = await asyncio.gather(
204  *(
205  item_payload(
206  {
207  "label": item["label"],
208  "type": item["type"],
209  "uri": item["type"],
210  },
211  )
212  for item in [
213  {"label": name, "type": type_} for type_, name in library.items()
214  ]
215  )
216  )
217 
218  for child in library_info.children:
219  child.thumbnail = "https://brands.home-assistant.io/_/kodi/logo.png"
220 
221  with contextlib.suppress(media_source.BrowseError):
222  item = await media_source.async_browse_media(
223  hass, None, content_filter=media_source_content_filter
224  )
225  # If domain is None, it's overview of available sources
226  if item.domain is None:
227  library_info.children.extend(item.children)
228  else:
229  library_info.children.append(item)
230 
231  return library_info
232 
233 
234 async def get_media_info(media_library, search_id, search_type):
235  """Fetch media/album."""
236  thumbnail = None
237  title = None
238  media = None
239 
240  properties = ["thumbnail"]
241  if search_type == MediaType.ALBUM:
242  if search_id:
243  album = await media_library.get_album_details(
244  album_id=int(search_id), properties=properties
245  )
246  thumbnail = media_library.thumbnail_url(
247  album["albumdetails"].get("thumbnail")
248  )
249  title = album["albumdetails"]["label"]
250  media = await media_library.get_songs(
251  album_id=int(search_id),
252  properties=[
253  "albumid",
254  "artist",
255  "duration",
256  "album",
257  "thumbnail",
258  "track",
259  ],
260  )
261  media = media.get("songs")
262  else:
263  media = await media_library.get_albums(properties=properties)
264  media = media.get("albums")
265  title = "Albums"
266 
267  elif search_type == MediaType.ARTIST:
268  if search_id:
269  media = await media_library.get_albums(
270  artist_id=int(search_id), properties=properties
271  )
272  media = media.get("albums")
273  artist = await media_library.get_artist_details(
274  artist_id=int(search_id), properties=properties
275  )
276  thumbnail = media_library.thumbnail_url(
277  artist["artistdetails"].get("thumbnail")
278  )
279  title = artist["artistdetails"]["label"]
280  else:
281  media = await media_library.get_artists(properties)
282  media = media.get("artists")
283  title = "Artists"
284 
285  elif search_type == "library_music":
286  library = {MediaType.ALBUM: "Albums", MediaType.ARTIST: "Artists"}
287  media = [{"label": name, "type": type_} for type_, name in library.items()]
288  title = "Music Library"
289 
290  elif search_type == MediaType.MOVIE:
291  if search_id:
292  movie = await media_library.get_movie_details(
293  movie_id=int(search_id), properties=properties
294  )
295  thumbnail = media_library.thumbnail_url(
296  movie["moviedetails"].get("thumbnail")
297  )
298  title = movie["moviedetails"]["label"]
299  else:
300  media = await media_library.get_movies(properties)
301  media = media.get("movies")
302  title = "Movies"
303 
304  elif search_type == MediaType.TVSHOW:
305  if search_id:
306  media = await media_library.get_seasons(
307  tv_show_id=int(search_id),
308  properties=["thumbnail", "season", "tvshowid"],
309  )
310  media = media.get("seasons")
311  tvshow = await media_library.get_tv_show_details(
312  tv_show_id=int(search_id), properties=properties
313  )
314  thumbnail = media_library.thumbnail_url(
315  tvshow["tvshowdetails"].get("thumbnail")
316  )
317  title = tvshow["tvshowdetails"]["label"]
318  else:
319  media = await media_library.get_tv_shows(properties)
320  media = media.get("tvshows")
321  title = "TV Shows"
322 
323  elif search_type == MediaType.SEASON:
324  tv_show_id, season_id = search_id.split("/", 1)
325  media = await media_library.get_episodes(
326  tv_show_id=int(tv_show_id),
327  season_id=int(season_id),
328  properties=["thumbnail", "tvshowid", "seasonid"],
329  )
330  media = media.get("episodes")
331  if media:
332  season = await media_library.get_season_details(
333  season_id=int(media[0]["seasonid"]), properties=properties
334  )
335  thumbnail = media_library.thumbnail_url(
336  season["seasondetails"].get("thumbnail")
337  )
338  title = season["seasondetails"]["label"]
339 
340  elif search_type == MediaType.CHANNEL:
341  media = await media_library.get_channels(
342  channel_group_id="alltv",
343  properties=["thumbnail", "channeltype", "channel", "broadcastnow"],
344  )
345  media = media.get("channels")
346  title = "Channels"
347 
348  return thumbnail, title, media
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
def build_item_response(media_library, payload, get_thumbnail_url=None)
Definition: browse_media.py:48
def get_media_info(media_library, search_id, search_type)
def item_payload(item, get_thumbnail_url=None)
Definition: browse_media.py:87
bool media_source_content_filter(BrowseMedia item)