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 json
4 
6  BrowseError,
7  BrowseMedia,
8  MediaClass,
9  MediaType,
10 )
11 
12 PLAYABLE_ITEM_TYPES = [
13  "folder",
14  "song",
15  "mywebradio",
16  "webradio",
17  "playlist",
18  "cuesong",
19  "remdisk",
20  "cuefile",
21  "folder-with-favourites",
22  "internal-folder",
23 ]
24 
25 NON_EXPANDABLE_ITEM_TYPES = [
26  "song",
27  "webradio",
28  "mywebradio",
29  "cuesong",
30  "album",
31  "artist",
32  "cd",
33  "play-playlist",
34 ]
35 
36 PLAYLISTS_URI_PREFIX = "playlists"
37 ARTISTS_URI_PREFIX = "artists://"
38 ALBUMS_URI_PREFIX = "albums://"
39 GENRES_URI_PREFIX = "genres://"
40 RADIO_URI_PREFIX = "radio"
41 LAST_100_URI_PREFIX = "Last_100"
42 FAVOURITES_URI = "favourites"
43 
44 
45 def _item_to_children_media_class(item, info=None):
46  if info and "album" in info and "artist" in info:
47  return MediaClass.TRACK
48  if item["uri"].startswith(PLAYLISTS_URI_PREFIX):
49  return MediaClass.PLAYLIST
50  if item["uri"].startswith(ARTISTS_URI_PREFIX):
51  if len(item["uri"]) > len(ARTISTS_URI_PREFIX):
52  return MediaClass.ALBUM
53  return MediaClass.ARTIST
54  if item["uri"].startswith(ALBUMS_URI_PREFIX):
55  if len(item["uri"]) > len(ALBUMS_URI_PREFIX):
56  return MediaClass.TRACK
57  return MediaClass.ALBUM
58  if item["uri"].startswith(GENRES_URI_PREFIX):
59  if len(item["uri"]) > len(GENRES_URI_PREFIX):
60  return MediaClass.ALBUM
61  return MediaClass.GENRE
62  if item["uri"].startswith(LAST_100_URI_PREFIX) or item["uri"] == FAVOURITES_URI:
63  return MediaClass.TRACK
64  if item["uri"].startswith(RADIO_URI_PREFIX):
65  return MediaClass.CHANNEL
66  return MediaClass.DIRECTORY
67 
68 
69 def _item_to_media_class(item, parent_item=None):
70  if "type" not in item:
71  return MediaClass.DIRECTORY
72  if item["type"] in ("webradio", "mywebradio"):
73  return MediaClass.CHANNEL
74  if item["type"] in ("song", "cuesong"):
75  return MediaClass.TRACK
76  if item.get("artist"):
77  return MediaClass.ALBUM
78  if item["uri"].startswith(ARTISTS_URI_PREFIX) and len(item["uri"]) > len(
79  ARTISTS_URI_PREFIX
80  ):
81  return MediaClass.ARTIST
82  if parent_item:
83  return _item_to_children_media_class(parent_item)
84  return MediaClass.DIRECTORY
85 
86 
87 def _list_payload(item, children=None):
88  return BrowseMedia(
89  title=item["name"],
90  media_class=MediaClass.DIRECTORY,
91  children_media_class=_item_to_children_media_class(item),
92  media_content_type=MediaType.MUSIC,
93  media_content_id=json.dumps(item),
94  can_play=False,
95  can_expand=True,
96  )
97 
98 
99 def _raw_item_payload(entity, item, parent_item=None, title=None, info=None):
100  if "type" in item:
101  if thumbnail := item.get("albumart"):
102  item_hash = str(hash(thumbnail))
103  entity.thumbnail_cache.setdefault(item_hash, thumbnail)
104  thumbnail = entity.get_browse_image_url(MediaType.MUSIC, item_hash)
105  else:
106  # don't use the built-in volumio white-on-white icons
107  thumbnail = None
108 
109  return {
110  "title": title or item.get("title"),
111  "media_class": _item_to_media_class(item, parent_item),
112  "children_media_class": _item_to_children_media_class(item, info),
113  "media_content_type": MediaType.MUSIC,
114  "media_content_id": json.dumps(item),
115  "can_play": item.get("type") in PLAYABLE_ITEM_TYPES,
116  "can_expand": item.get("type") not in NON_EXPANDABLE_ITEM_TYPES,
117  "thumbnail": thumbnail,
118  }
119 
120 
121 def _item_payload(entity, item, parent_item):
122  return BrowseMedia(**_raw_item_payload(entity, item, parent_item=parent_item))
123 
124 
125 async def browse_top_level(media_library):
126  """Browse the top-level of a Volumio media hierarchy."""
127  navigation = await media_library.browse()
128  children = [_list_payload(item) for item in navigation["lists"]]
129  return BrowseMedia(
130  media_class=MediaClass.DIRECTORY,
131  media_content_id="library",
132  media_content_type="library",
133  title="Media Library",
134  can_play=False,
135  can_expand=True,
136  children=children,
137  )
138 
139 
140 async def browse_node(entity, media_library, media_content_type, media_content_id):
141  """Browse a node of a Volumio media hierarchy."""
142  json_item = json.loads(media_content_id)
143  navigation = await media_library.browse(json_item["uri"])
144  if "lists" not in navigation:
145  raise BrowseError(f"Media not found: {media_content_type} / {media_content_id}")
146 
147  # we only use the first list since the second one could include all tracks
148  first_list = navigation["lists"][0]
149  children = [
150  _item_payload(entity, item, parent_item=json_item)
151  for item in first_list["items"]
152  ]
153  info = navigation.get("info")
154  if not (title := first_list.get("title")):
155  if info:
156  title = f"{info.get('album')} ({info.get('artist')})"
157  else:
158  title = "Media Library"
159 
160  payload = _raw_item_payload(entity, json_item, title=title, info=info)
161  return BrowseMedia(**payload, children=children)
def _item_payload(entity, item, parent_item)
def _item_to_children_media_class(item, info=None)
Definition: browse_media.py:45
def browse_node(entity, media_library, media_content_type, media_content_id)
def _raw_item_payload(entity, item, parent_item=None, title=None, info=None)
Definition: browse_media.py:99
def _item_to_media_class(item, parent_item=None)
Definition: browse_media.py:69