From 944ce291bda3ca495ee6b13108bf20a906f3ca3a Mon Sep 17 00:00:00 2001 From: sudoxnym <76703581+sudoxnym@users.noreply.github.com> Date: Sun, 14 Sep 2025 23:12:12 -0600 Subject: [PATCH 1/5] Use updated Home Assistant APIs --- custom_components/jellyfin/__init__.py | 14 +--- custom_components/jellyfin/media_player.py | 15 ++--- custom_components/jellyfin/media_source.py | 74 ++++++++++------------ 3 files changed, 42 insertions(+), 61 deletions(-) diff --git a/custom_components/jellyfin/__init__.py b/custom_components/jellyfin/__init__.py index 7bb0205..1113156 100644 --- a/custom_components/jellyfin/__init__.py +++ b/custom_components/jellyfin/__init__.py @@ -194,9 +194,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): for platform in PLATFORMS: hass.data[DOMAIN][config.get(CONF_URL)][platform] = {} hass.data[DOMAIN][config.get(CONF_URL)][platform]["entities"] = [] - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, platform) - ) + + await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) async_dispatcher_send(hass, SIGNAL_STATE_UPDATED) @@ -211,14 +210,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): _LOGGER.info("Unloading jellyfin") - unload_ok = all( - await asyncio.gather( - *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS - ] - ) - ) + unload_ok = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) _jelly: JellyfinClientManager = hass.data[DOMAIN][config_entry.data.get(CONF_URL)]["manager"] await _jelly.stop() diff --git a/custom_components/jellyfin/media_player.py b/custom_components/jellyfin/media_player.py index aa2e828..2bca00c 100644 --- a/custom_components/jellyfin/media_player.py +++ b/custom_components/jellyfin/media_player.py @@ -4,10 +4,7 @@ from typing import Mapping, MutableMapping, Optional, Sequence, Iterable, List, from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( - MEDIA_TYPE_CHANNEL, - MEDIA_TYPE_MOVIE, - MEDIA_TYPE_MUSIC, - MEDIA_TYPE_TVSHOW, + MediaType, SUPPORT_PLAY_MEDIA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -230,19 +227,19 @@ class JellyfinMediaPlayer(MediaPlayerEntity): """Content type of current playing media.""" media_type = self.device.media_type if media_type == "Episode": - return MEDIA_TYPE_TVSHOW + return MediaType.TVSHOW if media_type == "Movie": - return MEDIA_TYPE_MOVIE + return MediaType.MOVIE if media_type == "Trailer": return MEDIA_TYPE_TRAILER if media_type == "Music": - return MEDIA_TYPE_MUSIC + return MediaType.MUSIC if media_type == "Video": return MEDIA_TYPE_GENERIC_VIDEO if media_type == "Audio": - return MEDIA_TYPE_MUSIC + return MediaType.MUSIC if media_type == "TvChannel": - return MEDIA_TYPE_CHANNEL + return MediaType.CHANNEL return None @property diff --git a/custom_components/jellyfin/media_source.py b/custom_components/jellyfin/media_source.py index 7d72005..57e8c3f 100644 --- a/custom_components/jellyfin/media_source.py +++ b/custom_components/jellyfin/media_source.py @@ -19,6 +19,7 @@ from homeassistant.const import ( # pylint: disable=import-error CONF_URL, ) from homeassistant.components.media_player.const import ( + MediaType, MEDIA_CLASS_ALBUM, MEDIA_CLASS_ARTIST, MEDIA_CLASS_CHANNEL, @@ -30,15 +31,6 @@ from homeassistant.components.media_player.const import ( MEDIA_CLASS_SEASON, MEDIA_CLASS_TRACK, MEDIA_CLASS_TV_SHOW, - MEDIA_TYPE_ALBUM, - MEDIA_TYPE_ARTIST, - MEDIA_TYPE_CHANNEL, - MEDIA_TYPE_EPISODE, - MEDIA_TYPE_MOVIE, - MEDIA_TYPE_PLAYLIST, - MEDIA_TYPE_SEASON, - MEDIA_TYPE_TRACK, - MEDIA_TYPE_TVSHOW, ) from . import JellyfinClientManager, JellyfinDevice, autolog @@ -49,29 +41,29 @@ from .const import ( ) PLAYABLE_MEDIA_TYPES = [ - MEDIA_TYPE_ALBUM, - MEDIA_TYPE_ARTIST, - MEDIA_TYPE_TRACK, + MediaType.ALBUM, + MediaType.ARTIST, + MediaType.TRACK, ] CONTAINER_TYPES_SPECIFIC_MEDIA_CLASS = { - MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM, - MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST, - MEDIA_TYPE_PLAYLIST: MEDIA_CLASS_PLAYLIST, - MEDIA_TYPE_SEASON: MEDIA_CLASS_SEASON, - MEDIA_TYPE_TVSHOW: MEDIA_CLASS_TV_SHOW, + MediaType.ALBUM: MEDIA_CLASS_ALBUM, + MediaType.ARTIST: MEDIA_CLASS_ARTIST, + MediaType.PLAYLIST: MEDIA_CLASS_PLAYLIST, + MediaType.SEASON: MEDIA_CLASS_SEASON, + MediaType.TVSHOW: MEDIA_CLASS_TV_SHOW, } CHILD_TYPE_MEDIA_CLASS = { - MEDIA_TYPE_SEASON: MEDIA_CLASS_SEASON, - MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM, - MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST, - MEDIA_TYPE_MOVIE: MEDIA_CLASS_MOVIE, - MEDIA_TYPE_PLAYLIST: MEDIA_CLASS_PLAYLIST, - MEDIA_TYPE_TRACK: MEDIA_CLASS_TRACK, - MEDIA_TYPE_TVSHOW: MEDIA_CLASS_TV_SHOW, - MEDIA_TYPE_CHANNEL: MEDIA_CLASS_CHANNEL, - MEDIA_TYPE_EPISODE: MEDIA_CLASS_EPISODE, + MediaType.SEASON: MEDIA_CLASS_SEASON, + MediaType.ALBUM: MEDIA_CLASS_ALBUM, + MediaType.ARTIST: MEDIA_CLASS_ARTIST, + MediaType.MOVIE: MEDIA_CLASS_MOVIE, + MediaType.PLAYLIST: MEDIA_CLASS_PLAYLIST, + MediaType.TRACK: MEDIA_CLASS_TRACK, + MediaType.TVSHOW: MEDIA_CLASS_TV_SHOW, + MediaType.CHANNEL: MEDIA_CLASS_CHANNEL, + MediaType.EPISODE: MEDIA_CLASS_EPISODE, } IDENTIFIER_SPLIT = "~~" @@ -140,30 +132,30 @@ def async_parse_identifier( def Type2Mediatype(type): switcher = { - "Movie": MEDIA_TYPE_MOVIE, - "Series": MEDIA_TYPE_TVSHOW, - "Season": MEDIA_TYPE_SEASON, - "Episode": MEDIA_TYPE_EPISODE, - "Music": MEDIA_TYPE_ALBUM, - "Audio": MEDIA_TYPE_TRACK, + "Movie": MediaType.MOVIE, + "Series": MediaType.TVSHOW, + "Season": MediaType.SEASON, + "Episode": MediaType.EPISODE, + "Music": MediaType.ALBUM, + "Audio": MediaType.TRACK, "BoxSet": MEDIA_CLASS_DIRECTORY, "Folder": MEDIA_CLASS_DIRECTORY, "CollectionFolder": MEDIA_CLASS_DIRECTORY, "Playlist": MEDIA_CLASS_DIRECTORY, "PlaylistsFolder": MEDIA_CLASS_DIRECTORY, "ManualPlaylistsFolder": MEDIA_CLASS_DIRECTORY, - "MusicArtist": MEDIA_TYPE_ARTIST, - "MusicAlbum": MEDIA_TYPE_ALBUM, + "MusicArtist": MediaType.ARTIST, + "MusicAlbum": MediaType.ALBUM, } return switcher[type] def Type2Mimetype(type): switcher = { "Movie": "video/mp4", - "Series": MEDIA_TYPE_TVSHOW, - "Season": MEDIA_TYPE_SEASON, + "Series": MediaType.TVSHOW, + "Season": MediaType.SEASON, "Episode": "video/mp4", - "Music": MEDIA_TYPE_ALBUM, + "Music": MediaType.ALBUM, "Audio": "audio/mp3", "BoxSet": MEDIA_CLASS_DIRECTORY, "Folder": MEDIA_CLASS_DIRECTORY, @@ -171,8 +163,8 @@ def Type2Mimetype(type): "Playlist": MEDIA_CLASS_DIRECTORY, "PlaylistsFolder": MEDIA_CLASS_DIRECTORY, "ManualPlaylistsFolder": MEDIA_CLASS_DIRECTORY, - "MusicArtist": MEDIA_TYPE_ARTIST, - "MusicAlbum": MEDIA_TYPE_ALBUM, + "MusicArtist": MediaType.ARTIST, + "MusicAlbum": MediaType.ALBUM, } return switcher[type] @@ -247,7 +239,7 @@ async def async_library_items(jelly_cm: JellyfinClientManager, can_expand=True, children=[], ) - elif media_content_type in [MEDIA_CLASS_DIRECTORY, MEDIA_TYPE_ARTIST, MEDIA_TYPE_ALBUM, MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_SEASON]: + elif media_content_type in [MEDIA_CLASS_DIRECTORY, MediaType.ARTIST, MediaType.ALBUM, MediaType.PLAYLIST, MediaType.TVSHOW, MediaType.SEASON]: query = { "ParentId": media_content_id, "sortBy": "SortName", @@ -285,7 +277,7 @@ async def async_library_items(jelly_cm: JellyfinClientManager, items = await jelly_cm.get_items(query) for item in items: - if media_content_type in [None, "library", MEDIA_CLASS_DIRECTORY, MEDIA_TYPE_ARTIST, MEDIA_TYPE_ALBUM, MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_SEASON]: + if media_content_type in [None, "library", MEDIA_CLASS_DIRECTORY, MediaType.ARTIST, MediaType.ALBUM, MediaType.PLAYLIST, MediaType.TVSHOW, MediaType.SEASON]: if item["IsFolder"]: library_info.children_media_class = MEDIA_CLASS_DIRECTORY library_info.children.append(BrowseMediaSource( From 11ca93169ffd56cec6af912724d0b8cefc793256 Mon Sep 17 00:00:00 2001 From: sudoxnym <76703581+sudoxnym@users.noreply.github.com> Date: Sun, 14 Sep 2025 23:16:22 -0600 Subject: [PATCH 2/5] Update manifest.json --- custom_components/jellyfin/manifest.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/custom_components/jellyfin/manifest.json b/custom_components/jellyfin/manifest.json index b5113d1..29c00be 100644 --- a/custom_components/jellyfin/manifest.json +++ b/custom_components/jellyfin/manifest.json @@ -1,11 +1,11 @@ { - "domain": "jellyfin", - "name": "Jellyfin", - "version": "1.1.2", + "domain": "Jellyfin", + "name": "fin-assistant", + "version": "0.0.1", "config_flow": true, - "documentation": "https://github.com/koying/jellyfin_ha", - "issue_tracker": "https://github.com/koying/jellyfin_ha/issues", + "documentation": "https://github.com/sudoxnym/fin-assistant", + "issue_tracker": "https://github.com/sudoxnym/fin-assistant/issues", "requirements": ["jellyfin-apiclient-python==1.7.2"], - "codeowners": ["@koying"], + "codeowners": ["sudoxnym"], "iot_class": "local_push" } From 47ab93efa105842bb2904f09a13b1e7318de697d Mon Sep 17 00:00:00 2001 From: sudoxnym <76703581+sudoxnym@users.noreply.github.com> Date: Sun, 14 Sep 2025 23:25:34 -0600 Subject: [PATCH 3/5] fix config flow registration --- custom_components/jellyfin/__init__.py | 14 +--- custom_components/jellyfin/config_flow.py | 3 +- custom_components/jellyfin/media_player.py | 15 ++--- custom_components/jellyfin/media_source.py | 74 ++++++++++------------ 4 files changed, 43 insertions(+), 63 deletions(-) diff --git a/custom_components/jellyfin/__init__.py b/custom_components/jellyfin/__init__.py index 7bb0205..1113156 100644 --- a/custom_components/jellyfin/__init__.py +++ b/custom_components/jellyfin/__init__.py @@ -194,9 +194,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): for platform in PLATFORMS: hass.data[DOMAIN][config.get(CONF_URL)][platform] = {} hass.data[DOMAIN][config.get(CONF_URL)][platform]["entities"] = [] - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, platform) - ) + + await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) async_dispatcher_send(hass, SIGNAL_STATE_UPDATED) @@ -211,14 +210,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): _LOGGER.info("Unloading jellyfin") - unload_ok = all( - await asyncio.gather( - *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS - ] - ) - ) + unload_ok = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) _jelly: JellyfinClientManager = hass.data[DOMAIN][config_entry.data.get(CONF_URL)]["manager"] await _jelly.stop() diff --git a/custom_components/jellyfin/config_flow.py b/custom_components/jellyfin/config_flow.py index 027d0b0..0516ff9 100644 --- a/custom_components/jellyfin/config_flow.py +++ b/custom_components/jellyfin/config_flow.py @@ -28,8 +28,7 @@ RESULT_CONN_ERROR = "cannot_connect" RESULT_LOG_MESSAGE = {RESULT_CONN_ERROR: "Connection error"} -@config_entries.HANDLERS.register(DOMAIN) -class JellyfinFlowHandler(config_entries.ConfigFlow): +class JellyfinFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Config flow for Jellyfin component.""" VERSION = 1 diff --git a/custom_components/jellyfin/media_player.py b/custom_components/jellyfin/media_player.py index aa2e828..2bca00c 100644 --- a/custom_components/jellyfin/media_player.py +++ b/custom_components/jellyfin/media_player.py @@ -4,10 +4,7 @@ from typing import Mapping, MutableMapping, Optional, Sequence, Iterable, List, from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( - MEDIA_TYPE_CHANNEL, - MEDIA_TYPE_MOVIE, - MEDIA_TYPE_MUSIC, - MEDIA_TYPE_TVSHOW, + MediaType, SUPPORT_PLAY_MEDIA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -230,19 +227,19 @@ class JellyfinMediaPlayer(MediaPlayerEntity): """Content type of current playing media.""" media_type = self.device.media_type if media_type == "Episode": - return MEDIA_TYPE_TVSHOW + return MediaType.TVSHOW if media_type == "Movie": - return MEDIA_TYPE_MOVIE + return MediaType.MOVIE if media_type == "Trailer": return MEDIA_TYPE_TRAILER if media_type == "Music": - return MEDIA_TYPE_MUSIC + return MediaType.MUSIC if media_type == "Video": return MEDIA_TYPE_GENERIC_VIDEO if media_type == "Audio": - return MEDIA_TYPE_MUSIC + return MediaType.MUSIC if media_type == "TvChannel": - return MEDIA_TYPE_CHANNEL + return MediaType.CHANNEL return None @property diff --git a/custom_components/jellyfin/media_source.py b/custom_components/jellyfin/media_source.py index 7d72005..57e8c3f 100644 --- a/custom_components/jellyfin/media_source.py +++ b/custom_components/jellyfin/media_source.py @@ -19,6 +19,7 @@ from homeassistant.const import ( # pylint: disable=import-error CONF_URL, ) from homeassistant.components.media_player.const import ( + MediaType, MEDIA_CLASS_ALBUM, MEDIA_CLASS_ARTIST, MEDIA_CLASS_CHANNEL, @@ -30,15 +31,6 @@ from homeassistant.components.media_player.const import ( MEDIA_CLASS_SEASON, MEDIA_CLASS_TRACK, MEDIA_CLASS_TV_SHOW, - MEDIA_TYPE_ALBUM, - MEDIA_TYPE_ARTIST, - MEDIA_TYPE_CHANNEL, - MEDIA_TYPE_EPISODE, - MEDIA_TYPE_MOVIE, - MEDIA_TYPE_PLAYLIST, - MEDIA_TYPE_SEASON, - MEDIA_TYPE_TRACK, - MEDIA_TYPE_TVSHOW, ) from . import JellyfinClientManager, JellyfinDevice, autolog @@ -49,29 +41,29 @@ from .const import ( ) PLAYABLE_MEDIA_TYPES = [ - MEDIA_TYPE_ALBUM, - MEDIA_TYPE_ARTIST, - MEDIA_TYPE_TRACK, + MediaType.ALBUM, + MediaType.ARTIST, + MediaType.TRACK, ] CONTAINER_TYPES_SPECIFIC_MEDIA_CLASS = { - MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM, - MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST, - MEDIA_TYPE_PLAYLIST: MEDIA_CLASS_PLAYLIST, - MEDIA_TYPE_SEASON: MEDIA_CLASS_SEASON, - MEDIA_TYPE_TVSHOW: MEDIA_CLASS_TV_SHOW, + MediaType.ALBUM: MEDIA_CLASS_ALBUM, + MediaType.ARTIST: MEDIA_CLASS_ARTIST, + MediaType.PLAYLIST: MEDIA_CLASS_PLAYLIST, + MediaType.SEASON: MEDIA_CLASS_SEASON, + MediaType.TVSHOW: MEDIA_CLASS_TV_SHOW, } CHILD_TYPE_MEDIA_CLASS = { - MEDIA_TYPE_SEASON: MEDIA_CLASS_SEASON, - MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM, - MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST, - MEDIA_TYPE_MOVIE: MEDIA_CLASS_MOVIE, - MEDIA_TYPE_PLAYLIST: MEDIA_CLASS_PLAYLIST, - MEDIA_TYPE_TRACK: MEDIA_CLASS_TRACK, - MEDIA_TYPE_TVSHOW: MEDIA_CLASS_TV_SHOW, - MEDIA_TYPE_CHANNEL: MEDIA_CLASS_CHANNEL, - MEDIA_TYPE_EPISODE: MEDIA_CLASS_EPISODE, + MediaType.SEASON: MEDIA_CLASS_SEASON, + MediaType.ALBUM: MEDIA_CLASS_ALBUM, + MediaType.ARTIST: MEDIA_CLASS_ARTIST, + MediaType.MOVIE: MEDIA_CLASS_MOVIE, + MediaType.PLAYLIST: MEDIA_CLASS_PLAYLIST, + MediaType.TRACK: MEDIA_CLASS_TRACK, + MediaType.TVSHOW: MEDIA_CLASS_TV_SHOW, + MediaType.CHANNEL: MEDIA_CLASS_CHANNEL, + MediaType.EPISODE: MEDIA_CLASS_EPISODE, } IDENTIFIER_SPLIT = "~~" @@ -140,30 +132,30 @@ def async_parse_identifier( def Type2Mediatype(type): switcher = { - "Movie": MEDIA_TYPE_MOVIE, - "Series": MEDIA_TYPE_TVSHOW, - "Season": MEDIA_TYPE_SEASON, - "Episode": MEDIA_TYPE_EPISODE, - "Music": MEDIA_TYPE_ALBUM, - "Audio": MEDIA_TYPE_TRACK, + "Movie": MediaType.MOVIE, + "Series": MediaType.TVSHOW, + "Season": MediaType.SEASON, + "Episode": MediaType.EPISODE, + "Music": MediaType.ALBUM, + "Audio": MediaType.TRACK, "BoxSet": MEDIA_CLASS_DIRECTORY, "Folder": MEDIA_CLASS_DIRECTORY, "CollectionFolder": MEDIA_CLASS_DIRECTORY, "Playlist": MEDIA_CLASS_DIRECTORY, "PlaylistsFolder": MEDIA_CLASS_DIRECTORY, "ManualPlaylistsFolder": MEDIA_CLASS_DIRECTORY, - "MusicArtist": MEDIA_TYPE_ARTIST, - "MusicAlbum": MEDIA_TYPE_ALBUM, + "MusicArtist": MediaType.ARTIST, + "MusicAlbum": MediaType.ALBUM, } return switcher[type] def Type2Mimetype(type): switcher = { "Movie": "video/mp4", - "Series": MEDIA_TYPE_TVSHOW, - "Season": MEDIA_TYPE_SEASON, + "Series": MediaType.TVSHOW, + "Season": MediaType.SEASON, "Episode": "video/mp4", - "Music": MEDIA_TYPE_ALBUM, + "Music": MediaType.ALBUM, "Audio": "audio/mp3", "BoxSet": MEDIA_CLASS_DIRECTORY, "Folder": MEDIA_CLASS_DIRECTORY, @@ -171,8 +163,8 @@ def Type2Mimetype(type): "Playlist": MEDIA_CLASS_DIRECTORY, "PlaylistsFolder": MEDIA_CLASS_DIRECTORY, "ManualPlaylistsFolder": MEDIA_CLASS_DIRECTORY, - "MusicArtist": MEDIA_TYPE_ARTIST, - "MusicAlbum": MEDIA_TYPE_ALBUM, + "MusicArtist": MediaType.ARTIST, + "MusicAlbum": MediaType.ALBUM, } return switcher[type] @@ -247,7 +239,7 @@ async def async_library_items(jelly_cm: JellyfinClientManager, can_expand=True, children=[], ) - elif media_content_type in [MEDIA_CLASS_DIRECTORY, MEDIA_TYPE_ARTIST, MEDIA_TYPE_ALBUM, MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_SEASON]: + elif media_content_type in [MEDIA_CLASS_DIRECTORY, MediaType.ARTIST, MediaType.ALBUM, MediaType.PLAYLIST, MediaType.TVSHOW, MediaType.SEASON]: query = { "ParentId": media_content_id, "sortBy": "SortName", @@ -285,7 +277,7 @@ async def async_library_items(jelly_cm: JellyfinClientManager, items = await jelly_cm.get_items(query) for item in items: - if media_content_type in [None, "library", MEDIA_CLASS_DIRECTORY, MEDIA_TYPE_ARTIST, MEDIA_TYPE_ALBUM, MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_SEASON]: + if media_content_type in [None, "library", MEDIA_CLASS_DIRECTORY, MediaType.ARTIST, MediaType.ALBUM, MediaType.PLAYLIST, MediaType.TVSHOW, MediaType.SEASON]: if item["IsFolder"]: library_info.children_media_class = MEDIA_CLASS_DIRECTORY library_info.children.append(BrowseMediaSource( From e37ce92e2122dae3af1e639c3b867ff814a72ced Mon Sep 17 00:00:00 2001 From: sudoxnym <76703581+sudoxnym@users.noreply.github.com> Date: Sun, 14 Sep 2025 23:26:23 -0600 Subject: [PATCH 4/5] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 9877076..9745abe 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +# fin-assistant +--- +fin-assistant is the revival of jellyfin-ha to bring the ability for voice assist to jellyfin. + + + + # jellyfin_ha Jellyfin integration for Home Assistant From 46d55afbcc1357d2e973370ac817cdf84b450dde Mon Sep 17 00:00:00 2001 From: sudoxnym <76703581+sudoxnym@users.noreply.github.com> Date: Sun, 14 Sep 2025 23:52:53 -0600 Subject: [PATCH 5/5] Fix config flow handler --- custom_components/jellyfin/__init__.py | 14 +--- custom_components/jellyfin/config_flow.py | 12 ++-- custom_components/jellyfin/media_player.py | 15 ++--- custom_components/jellyfin/media_source.py | 74 ++++++++++------------ 4 files changed, 48 insertions(+), 67 deletions(-) diff --git a/custom_components/jellyfin/__init__.py b/custom_components/jellyfin/__init__.py index 7bb0205..1113156 100644 --- a/custom_components/jellyfin/__init__.py +++ b/custom_components/jellyfin/__init__.py @@ -194,9 +194,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): for platform in PLATFORMS: hass.data[DOMAIN][config.get(CONF_URL)][platform] = {} hass.data[DOMAIN][config.get(CONF_URL)][platform]["entities"] = [] - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, platform) - ) + + await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) async_dispatcher_send(hass, SIGNAL_STATE_UPDATED) @@ -211,14 +210,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): _LOGGER.info("Unloading jellyfin") - unload_ok = all( - await asyncio.gather( - *[ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in PLATFORMS - ] - ) - ) + unload_ok = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) _jelly: JellyfinClientManager = hass.data[DOMAIN][config_entry.data.get(CONF_URL)]["manager"] await _jelly.stop() diff --git a/custom_components/jellyfin/config_flow.py b/custom_components/jellyfin/config_flow.py index 027d0b0..bacfd32 100644 --- a/custom_components/jellyfin/config_flow.py +++ b/custom_components/jellyfin/config_flow.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant import config_entries, exceptions from homeassistant.core import callback -from homeassistant.const import ( # pylint: disable=import-error +from homeassistant.const import ( # pylint: disable=import-error CONF_URL, CONF_VERIFY_SSL, CONF_USERNAME, @@ -28,9 +28,8 @@ RESULT_CONN_ERROR = "cannot_connect" RESULT_LOG_MESSAGE = {RESULT_CONN_ERROR: "Connection error"} -@config_entries.HANDLERS.register(DOMAIN) -class JellyfinFlowHandler(config_entries.ConfigFlow): - """Config flow for Jellyfin component.""" +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Jellyfin.""" VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH @@ -38,15 +37,16 @@ class JellyfinFlowHandler(config_entries.ConfigFlow): @staticmethod @callback def async_get_options_flow(config_entry): - """Jellyfin options callback.""" + """Return the options flow handler.""" return JellyfinOptionsFlowHandler(config_entry) def __init__(self): - """Init JellyfinFlowHandler.""" + """Initialize the config flow.""" self._errors = {} self._url = None self._ssl = DEFAULT_SSL self._verify_ssl = DEFAULT_VERIFY_SSL + self._is_import = False async def async_step_import(self, user_input=None): """Handle configuration by yaml file.""" diff --git a/custom_components/jellyfin/media_player.py b/custom_components/jellyfin/media_player.py index aa2e828..2bca00c 100644 --- a/custom_components/jellyfin/media_player.py +++ b/custom_components/jellyfin/media_player.py @@ -4,10 +4,7 @@ from typing import Mapping, MutableMapping, Optional, Sequence, Iterable, List, from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( - MEDIA_TYPE_CHANNEL, - MEDIA_TYPE_MOVIE, - MEDIA_TYPE_MUSIC, - MEDIA_TYPE_TVSHOW, + MediaType, SUPPORT_PLAY_MEDIA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -230,19 +227,19 @@ class JellyfinMediaPlayer(MediaPlayerEntity): """Content type of current playing media.""" media_type = self.device.media_type if media_type == "Episode": - return MEDIA_TYPE_TVSHOW + return MediaType.TVSHOW if media_type == "Movie": - return MEDIA_TYPE_MOVIE + return MediaType.MOVIE if media_type == "Trailer": return MEDIA_TYPE_TRAILER if media_type == "Music": - return MEDIA_TYPE_MUSIC + return MediaType.MUSIC if media_type == "Video": return MEDIA_TYPE_GENERIC_VIDEO if media_type == "Audio": - return MEDIA_TYPE_MUSIC + return MediaType.MUSIC if media_type == "TvChannel": - return MEDIA_TYPE_CHANNEL + return MediaType.CHANNEL return None @property diff --git a/custom_components/jellyfin/media_source.py b/custom_components/jellyfin/media_source.py index 7d72005..57e8c3f 100644 --- a/custom_components/jellyfin/media_source.py +++ b/custom_components/jellyfin/media_source.py @@ -19,6 +19,7 @@ from homeassistant.const import ( # pylint: disable=import-error CONF_URL, ) from homeassistant.components.media_player.const import ( + MediaType, MEDIA_CLASS_ALBUM, MEDIA_CLASS_ARTIST, MEDIA_CLASS_CHANNEL, @@ -30,15 +31,6 @@ from homeassistant.components.media_player.const import ( MEDIA_CLASS_SEASON, MEDIA_CLASS_TRACK, MEDIA_CLASS_TV_SHOW, - MEDIA_TYPE_ALBUM, - MEDIA_TYPE_ARTIST, - MEDIA_TYPE_CHANNEL, - MEDIA_TYPE_EPISODE, - MEDIA_TYPE_MOVIE, - MEDIA_TYPE_PLAYLIST, - MEDIA_TYPE_SEASON, - MEDIA_TYPE_TRACK, - MEDIA_TYPE_TVSHOW, ) from . import JellyfinClientManager, JellyfinDevice, autolog @@ -49,29 +41,29 @@ from .const import ( ) PLAYABLE_MEDIA_TYPES = [ - MEDIA_TYPE_ALBUM, - MEDIA_TYPE_ARTIST, - MEDIA_TYPE_TRACK, + MediaType.ALBUM, + MediaType.ARTIST, + MediaType.TRACK, ] CONTAINER_TYPES_SPECIFIC_MEDIA_CLASS = { - MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM, - MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST, - MEDIA_TYPE_PLAYLIST: MEDIA_CLASS_PLAYLIST, - MEDIA_TYPE_SEASON: MEDIA_CLASS_SEASON, - MEDIA_TYPE_TVSHOW: MEDIA_CLASS_TV_SHOW, + MediaType.ALBUM: MEDIA_CLASS_ALBUM, + MediaType.ARTIST: MEDIA_CLASS_ARTIST, + MediaType.PLAYLIST: MEDIA_CLASS_PLAYLIST, + MediaType.SEASON: MEDIA_CLASS_SEASON, + MediaType.TVSHOW: MEDIA_CLASS_TV_SHOW, } CHILD_TYPE_MEDIA_CLASS = { - MEDIA_TYPE_SEASON: MEDIA_CLASS_SEASON, - MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM, - MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST, - MEDIA_TYPE_MOVIE: MEDIA_CLASS_MOVIE, - MEDIA_TYPE_PLAYLIST: MEDIA_CLASS_PLAYLIST, - MEDIA_TYPE_TRACK: MEDIA_CLASS_TRACK, - MEDIA_TYPE_TVSHOW: MEDIA_CLASS_TV_SHOW, - MEDIA_TYPE_CHANNEL: MEDIA_CLASS_CHANNEL, - MEDIA_TYPE_EPISODE: MEDIA_CLASS_EPISODE, + MediaType.SEASON: MEDIA_CLASS_SEASON, + MediaType.ALBUM: MEDIA_CLASS_ALBUM, + MediaType.ARTIST: MEDIA_CLASS_ARTIST, + MediaType.MOVIE: MEDIA_CLASS_MOVIE, + MediaType.PLAYLIST: MEDIA_CLASS_PLAYLIST, + MediaType.TRACK: MEDIA_CLASS_TRACK, + MediaType.TVSHOW: MEDIA_CLASS_TV_SHOW, + MediaType.CHANNEL: MEDIA_CLASS_CHANNEL, + MediaType.EPISODE: MEDIA_CLASS_EPISODE, } IDENTIFIER_SPLIT = "~~" @@ -140,30 +132,30 @@ def async_parse_identifier( def Type2Mediatype(type): switcher = { - "Movie": MEDIA_TYPE_MOVIE, - "Series": MEDIA_TYPE_TVSHOW, - "Season": MEDIA_TYPE_SEASON, - "Episode": MEDIA_TYPE_EPISODE, - "Music": MEDIA_TYPE_ALBUM, - "Audio": MEDIA_TYPE_TRACK, + "Movie": MediaType.MOVIE, + "Series": MediaType.TVSHOW, + "Season": MediaType.SEASON, + "Episode": MediaType.EPISODE, + "Music": MediaType.ALBUM, + "Audio": MediaType.TRACK, "BoxSet": MEDIA_CLASS_DIRECTORY, "Folder": MEDIA_CLASS_DIRECTORY, "CollectionFolder": MEDIA_CLASS_DIRECTORY, "Playlist": MEDIA_CLASS_DIRECTORY, "PlaylistsFolder": MEDIA_CLASS_DIRECTORY, "ManualPlaylistsFolder": MEDIA_CLASS_DIRECTORY, - "MusicArtist": MEDIA_TYPE_ARTIST, - "MusicAlbum": MEDIA_TYPE_ALBUM, + "MusicArtist": MediaType.ARTIST, + "MusicAlbum": MediaType.ALBUM, } return switcher[type] def Type2Mimetype(type): switcher = { "Movie": "video/mp4", - "Series": MEDIA_TYPE_TVSHOW, - "Season": MEDIA_TYPE_SEASON, + "Series": MediaType.TVSHOW, + "Season": MediaType.SEASON, "Episode": "video/mp4", - "Music": MEDIA_TYPE_ALBUM, + "Music": MediaType.ALBUM, "Audio": "audio/mp3", "BoxSet": MEDIA_CLASS_DIRECTORY, "Folder": MEDIA_CLASS_DIRECTORY, @@ -171,8 +163,8 @@ def Type2Mimetype(type): "Playlist": MEDIA_CLASS_DIRECTORY, "PlaylistsFolder": MEDIA_CLASS_DIRECTORY, "ManualPlaylistsFolder": MEDIA_CLASS_DIRECTORY, - "MusicArtist": MEDIA_TYPE_ARTIST, - "MusicAlbum": MEDIA_TYPE_ALBUM, + "MusicArtist": MediaType.ARTIST, + "MusicAlbum": MediaType.ALBUM, } return switcher[type] @@ -247,7 +239,7 @@ async def async_library_items(jelly_cm: JellyfinClientManager, can_expand=True, children=[], ) - elif media_content_type in [MEDIA_CLASS_DIRECTORY, MEDIA_TYPE_ARTIST, MEDIA_TYPE_ALBUM, MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_SEASON]: + elif media_content_type in [MEDIA_CLASS_DIRECTORY, MediaType.ARTIST, MediaType.ALBUM, MediaType.PLAYLIST, MediaType.TVSHOW, MediaType.SEASON]: query = { "ParentId": media_content_id, "sortBy": "SortName", @@ -285,7 +277,7 @@ async def async_library_items(jelly_cm: JellyfinClientManager, items = await jelly_cm.get_items(query) for item in items: - if media_content_type in [None, "library", MEDIA_CLASS_DIRECTORY, MEDIA_TYPE_ARTIST, MEDIA_TYPE_ALBUM, MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_SEASON]: + if media_content_type in [None, "library", MEDIA_CLASS_DIRECTORY, MediaType.ARTIST, MediaType.ALBUM, MediaType.PLAYLIST, MediaType.TVSHOW, MediaType.SEASON]: if item["IsFolder"]: library_info.children_media_class = MEDIA_CLASS_DIRECTORY library_info.children.append(BrowseMediaSource(