From adb48cdc23fed77f2158c479bb047cb841257b1a Mon Sep 17 00:00:00 2001 From: Chris Browet Date: Sat, 8 May 2021 10:20:43 +0200 Subject: [PATCH] [contd] media source --- __init__.py | 3 ++ media_source.py | 123 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 93 insertions(+), 33 deletions(-) diff --git a/__init__.py b/__init__.py index e5979ab..376e4c5 100644 --- a/__init__.py +++ b/__init__.py @@ -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) diff --git a/media_source.py b/media_source.py index ff2f46e..20a8039 100644 --- a/media_source.py +++ b/media_source.py @@ -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