Home Assistant Unofficial Reference 2024.12.1
browse_media.py
Go to the documentation of this file.
1 """Support for media browsing."""
2 
3 from __future__ import annotations
4 
5 from typing import NamedTuple
6 
7 from xbox.webapi.api.client import XboxLiveClient
8 from xbox.webapi.api.provider.catalog.const import HOME_APP_IDS, SYSTEM_PFN_ID_MAP
9 from xbox.webapi.api.provider.catalog.models import (
10  AlternateIdType,
11  CatalogResponse,
12  FieldsTemplate,
13  Image,
14 )
15 from xbox.webapi.api.provider.smartglass.models import (
16  InstalledPackage,
17  InstalledPackagesList,
18 )
19 
20 from homeassistant.components.media_player import BrowseMedia, MediaClass, MediaType
21 
22 
23 class MediaTypeDetails(NamedTuple):
24  """Details for media type."""
25 
26  type: str
27  cls: str
28 
29 
30 TYPE_MAP = {
31  "App": MediaTypeDetails(
32  type=MediaType.APP,
33  cls=MediaClass.APP,
34  ),
35  "Game": MediaTypeDetails(
36  type=MediaType.GAME,
37  cls=MediaClass.GAME,
38  ),
39 }
40 
41 
43  client: XboxLiveClient,
44  device_id: str,
45  tv_configured: bool,
46  media_content_type: str,
47  media_content_id: str,
48 ) -> BrowseMedia | None:
49  """Create response payload for the provided media query."""
50  apps: InstalledPackagesList = await client.smartglass.get_installed_apps(device_id)
51 
52  if media_content_type in (None, "library"):
53  children: list[BrowseMedia] = []
54  library_info = BrowseMedia(
55  media_class=MediaClass.DIRECTORY,
56  media_content_id="library",
57  media_content_type="library",
58  title="Installed Applications",
59  can_play=False,
60  can_expand=True,
61  children=children,
62  )
63 
64  # Add Home
65  id_type = AlternateIdType.LEGACY_XBOX_PRODUCT_ID
66  home_catalog: CatalogResponse = (
67  await client.catalog.get_product_from_alternate_id(
68  HOME_APP_IDS[id_type], id_type
69  )
70  )
71  home_thumb = _find_media_image(
72  home_catalog.products[0].localized_properties[0].images
73  )
74  children.append(
76  media_class=MediaClass.APP,
77  media_content_id="Home",
78  media_content_type=MediaType.APP,
79  title="Home",
80  can_play=True,
81  can_expand=False,
82  thumbnail=None if home_thumb is None else home_thumb.uri,
83  )
84  )
85 
86  # Add TV if configured
87  if tv_configured:
88  tv_catalog: CatalogResponse = (
89  await client.catalog.get_product_from_alternate_id(
90  SYSTEM_PFN_ID_MAP["Microsoft.Xbox.LiveTV_8wekyb3d8bbwe"][id_type],
91  id_type,
92  )
93  )
94  tv_thumb = _find_media_image(
95  tv_catalog.products[0].localized_properties[0].images
96  )
97  children.append(
99  media_class=MediaClass.APP,
100  media_content_id="TV",
101  media_content_type=MediaType.APP,
102  title="Live TV",
103  can_play=True,
104  can_expand=False,
105  thumbnail=None if tv_thumb is None else tv_thumb.uri,
106  )
107  )
108 
109  content_types = sorted(
110  {app.content_type for app in apps.result if app.content_type in TYPE_MAP}
111  )
112  children.extend(
113  BrowseMedia(
114  media_class=MediaClass.DIRECTORY,
115  media_content_id=c_type,
116  media_content_type=TYPE_MAP[c_type].type,
117  title=f"{c_type}s",
118  can_play=False,
119  can_expand=True,
120  children_media_class=TYPE_MAP[c_type].cls,
121  )
122  for c_type in content_types
123  )
124 
125  return library_info
126 
127  app_details = await client.catalog.get_products(
128  [
129  app.one_store_product_id
130  for app in apps.result
131  if app.content_type == media_content_id and app.one_store_product_id
132  ],
133  FieldsTemplate.BROWSE,
134  )
135 
136  images = {
137  prod.product_id: prod.localized_properties[0].images
138  for prod in app_details.products
139  }
140 
141  return BrowseMedia(
142  media_class=MediaClass.DIRECTORY,
143  media_content_id=media_content_id,
144  media_content_type=media_content_type,
145  title=f"{media_content_id}s",
146  can_play=False,
147  can_expand=True,
148  children=[
149  item_payload(app, images)
150  for app in apps.result
151  if app.content_type == media_content_id and app.one_store_product_id
152  ],
153  children_media_class=TYPE_MAP[media_content_id].cls,
154  )
155 
156 
157 def item_payload(item: InstalledPackage, images: dict[str, list[Image]]):
158  """Create response payload for a single media item."""
159  thumbnail = None
160  image = _find_media_image(images.get(item.one_store_product_id, []))
161  if image is not None:
162  thumbnail = image.uri
163  if thumbnail[0] == "/":
164  thumbnail = f"https:{thumbnail}"
165 
166  return BrowseMedia(
167  media_class=TYPE_MAP[item.content_type].cls,
168  media_content_id=item.one_store_product_id,
169  media_content_type=TYPE_MAP[item.content_type].type,
170  title=item.name,
171  can_play=True,
172  can_expand=False,
173  thumbnail=thumbnail,
174  )
175 
176 
177 def _find_media_image(images: list[Image]) -> Image | None:
178  purpose_order = ["Poster", "Tile", "Logo", "BoxArt"]
179  for purpose in purpose_order:
180  for image in images:
181  if image.image_purpose == purpose and image.width >= 300:
182  return image
183  return None
Image|None _find_media_image(list[Image] images)
def item_payload(InstalledPackage item, dict[str, list[Image]] images)
BrowseMedia|None build_item_response(XboxLiveClient client, str device_id, bool tv_configured, str media_content_type, str media_content_id)
Definition: browse_media.py:48