[contd] media source

This commit is contained in:
Chris Browet 2021-05-08 10:20:43 +02:00
parent a9e7a24af2
commit adb48cdc23
2 changed files with 93 additions and 33 deletions

View file

@ -689,6 +689,9 @@ class JellyfinClientManager(object):
def get_server_url(self) -> str:
return self.jf_client.config.data["auth.server"]
def get_auth_token(self) -> str:
return self.jf_client.config.data["auth.token"]
async def get_item(self, id):
return await self.hass.async_add_executor_job(self.jf_client.jellyfin.get_item, id)

View file

@ -90,6 +90,15 @@ async def async_get_media_source(hass: HomeAssistant):
class JellyfinSource(MediaSource):
"""Media source for Jellyfin"""
@staticmethod
def parse_mediasource_identifier(identifier: str):
prefix = f"{URI_SCHEME}{DOMAIN}/"
text = identifier
if identifier.startswith(prefix):
text = identifier[len(prefix):]
return text.split(IDENTIFIER_SPLIT, 2)
def __init__(self, hass: HomeAssistant, manager: JellyfinClientManager):
"""Initialize Netatmo source."""
super().__init__(DOMAIN)
@ -103,18 +112,24 @@ class JellyfinSource(MediaSource):
if not item or not item.identifier:
return None
_, media_content_id = parse_mediasource_identifier(item.identifier)
media_content_type, media_content_id = self.parse_mediasource_identifier(item.identifier)
profile = {
"Name": USER_APP_NAME,
"MaxStreamingBitrate": 25000 * 1000,
"MusicStreamingTranscodingBitrate": 1280000,
"MusicStreamingTranscodingBitrate": 1920000,
"TimelineOffsetSeconds": 5,
"TranscodingProfiles": [
{"Type": "Audio"},
{
"Container": "mp4",
"Type": "Audio",
"Container": "mp3",
"Protocol": "http",
"AudioCodec": "mp3",
"MaxAudioChannels": "2",
},
{
"Type": "Video",
"Container": "mp4",
"Protocol": "http",
"AudioCodec": "aac,mp3,opus,flac,vorbis",
"VideoCodec": "h264,mpeg4,mpeg2video",
@ -122,7 +137,25 @@ class JellyfinSource(MediaSource):
},
{"Container": "jpeg", "Type": "Photo"},
],
"DirectPlayProfiles": [],
"DirectPlayProfiles": [
{
"Type": "Audio",
"Container": "mp3",
"AudioCodec": "mp3"
},
{
"Type": "Audio",
"Container": "m4a,m4b",
"AudioCodec": "aac"
},
{
"Type": "Video",
"Container": "mp4,m4v",
"AudioCodec": "aac,mp3,opus,flac,vorbis",
"VideoCodec": "h264,mpeg4,mpeg2video",
"MaxAudioChannels": "6",
},
],
"ResponseProfiles": [],
"ContainerProfiles": [],
"CodecProfiles": [],
@ -158,21 +191,48 @@ class JellyfinSource(MediaSource):
playback_info = await self.jelly_cm.get_play_info(media_content_id, profile)
_LOGGER.debug("playbackinfo: %s", str(playback_info))
if playback_info is None or "MediaSources" not in playback_info:
_LOGGER.error(f"No playback info for item id {media_content_id}")
return None
selected = None
weight_selected = 0
for media_source in playback_info["MediaSources"]:
weight = (media_source.get("SupportsDirectPlay") or 0) * 50000 + (
weight = (media_source.get("SupportsDirectStream") or 0) * 50000 + (
media_source.get("Bitrate") or 0
) / 1000
if weight > weight_selected:
weight_selected = weight
selected = media_source
if selected is None:
return None
if selected["SupportsTranscoding"]:
if selected["SupportsDirectStream"]:
if media_content_type == MEDIA_TYPE_TRACK:
mimetype = "audio/" + selected["Container"]
url = self.jelly_cm.get_server_url() + "/Audio/%s/stream?static=true&MediaSourceId=%s&api_key=%s" % (
media_content_id,
selected["Id"],
self.jelly_cm.get_auth_token()
)
else:
mimetype = "video/" + selected["Container"]
url = self.jelly_cm.get_server_url() + "/Videos/%s/stream?static=true&MediaSourceId=%s&api_key=%s" % (
media_content_id,
selected["Id"],
self.jelly_cm.get_auth_token()
)
elif selected["SupportsTranscoding"]:
url = self.jelly_cm.get_server_url() + selected.get("TranscodingUrl")
_LOGGER.debug("cast url: %s", url)
return PlayMedia(url, "video/mp4")
container = selected["TranscodingContainer"] if "TranscodingContainer" in selected else selected["Container"]
if media_content_type == MEDIA_TYPE_TRACK:
mimetype = "audio/" + container
else:
mimetype = "video/" + container
_LOGGER.debug("cast url: %s", url)
return PlayMedia(url, mimetype)
return None
@ -183,7 +243,7 @@ class JellyfinSource(MediaSource):
autolog("<<<")
media_contant_type, media_content_id = async_parse_identifier(item)
return await async_library_items(self.jelly_cm, media_contant_type, media_content_id)
return await async_library_items(self.jelly_cm, media_contant_type, media_content_id, canPlayList=False)
@callback
def async_parse_identifier(
@ -210,7 +270,6 @@ def Type2Mediatype(type):
"Playlist": MEDIA_CLASS_DIRECTORY,
"MusicArtist": MEDIA_TYPE_ARTIST,
"MusicAlbum": MEDIA_TYPE_ALBUM,
"Audio": MEDIA_TYPE_TRACK,
}
return switcher[type]
@ -231,32 +290,28 @@ def Type2Mediaclass(type):
}
return switcher[type]
def IsPlayable(type):
def IsPlayable(type, canPlayList):
switcher = {
"Movie": True,
"Series": True,
"Season": True,
"Series": canPlayList,
"Season": canPlayList,
"Episode": True,
"Music": False,
"BoxSet": True,
"BoxSet": canPlayList,
"Folder": False,
"CollectionFolder": False,
"Playlist": True,
"MusicArtist": True,
"MusicAlbum": True,
"Playlist": canPlayList,
"MusicArtist": canPlayList,
"MusicAlbum": canPlayList,
"Audio": True,
}
return switcher[type]
def parse_mediasource_identifier(identifier: str):
prefix = f"{URI_SCHEME}{DOMAIN}/"
text = identifier
if identifier.startswith(prefix):
text = identifier[len(prefix):]
return text.split(IDENTIFIER_SPLIT, 2)
async def async_library_items(jelly_cm: JellyfinClientManager, media_content_type_in=None, media_content_id_in=None) -> BrowseMediaSource:
async def async_library_items(jelly_cm: JellyfinClientManager,
media_content_type_in=None,
media_content_id_in=None,
canPlayList=True
) -> BrowseMediaSource:
"""
Create response payload to describe contents of a specific library.
@ -271,7 +326,7 @@ async def async_library_items(jelly_cm: JellyfinClientManager, media_content_typ
media_content_type = None
media_content_id = None
else:
media_content_type, media_content_id = parse_mediasource_identifier(media_content_id_in)
media_content_type, media_content_id = JellyfinSource.parse_mediasource_identifier(media_content_id_in)
_LOGGER.debug(f'>> {media_content_type} / {media_content_id}')
if media_content_type in [None, "library"]:
@ -287,7 +342,9 @@ async def async_library_items(jelly_cm: JellyfinClientManager, media_content_typ
)
elif media_content_type in [MEDIA_CLASS_DIRECTORY, MEDIA_TYPE_ARTIST, MEDIA_TYPE_ALBUM, MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_SEASON]:
query = {
"ParentId": media_content_id
"ParentId": media_content_id,
"sortBy": "SortName",
"sortOrder": "Ascending"
}
parent_item = await jelly_cm.get_item(media_content_id)
@ -297,7 +354,7 @@ async def async_library_items(jelly_cm: JellyfinClientManager, media_content_typ
media_class=media_content_type,
media_content_type=media_content_type,
title=parent_item["Name"],
can_play=IsPlayable(parent_item["Type"]),
can_play=IsPlayable(parent_item["Type"], canPlayList),
can_expand=True,
thumbnail=await jelly_cm.get_artwork_url(media_content_id),
children=[],
@ -328,7 +385,7 @@ async def async_library_items(jelly_cm: JellyfinClientManager, media_content_typ
media_class=Type2Mediaclass(item["Type"]),
media_content_type=Type2Mediatype(item["Type"]),
title=item["Name"],
can_play=IsPlayable(item["Type"]),
can_play=IsPlayable(item["Type"], canPlayList),
can_expand=True,
children=[],
thumbnail=await jelly_cm.get_artwork_url(item["Id"])
@ -340,7 +397,7 @@ async def async_library_items(jelly_cm: JellyfinClientManager, media_content_typ
media_class=Type2Mediaclass(item["Type"]),
media_content_type=Type2Mediatype(item["Type"]),
title=item["Name"],
can_play=IsPlayable(item["Type"]),
can_play=IsPlayable(item["Type"], canPlayList),
can_expand=False,
children=[],
thumbnail=await jelly_cm.get_artwork_url(item["Id"])
@ -352,7 +409,7 @@ async def async_library_items(jelly_cm: JellyfinClientManager, media_content_typ
library_info.media_content_type = Type2Mediatype(item["Type"])
library_info.media_class = Type2Mediaclass(item["Type"])
library_info.can_expand = False
library_info.can_play=IsPlayable(item["Type"]),
library_info.can_play=IsPlayable(item["Type"], canPlayList),
break
return library_info