mirror of https://github.com/home-assistant/core
Jellyfin: Add support for movie collections (#73086)
This commit is contained in:
parent
a9e4673aff
commit
ed54cea3f2
|
@ -7,7 +7,6 @@ DOMAIN: Final = "jellyfin"
|
|||
CLIENT_VERSION: Final = "1.0"
|
||||
|
||||
COLLECTION_TYPE_MOVIES: Final = "movies"
|
||||
COLLECTION_TYPE_TVSHOWS: Final = "tvshows"
|
||||
COLLECTION_TYPE_MUSIC: Final = "music"
|
||||
|
||||
DATA_CLIENT: Final = "client"
|
||||
|
@ -24,6 +23,7 @@ ITEM_TYPE_ALBUM: Final = "MusicAlbum"
|
|||
ITEM_TYPE_ARTIST: Final = "MusicArtist"
|
||||
ITEM_TYPE_AUDIO: Final = "Audio"
|
||||
ITEM_TYPE_LIBRARY: Final = "CollectionFolder"
|
||||
ITEM_TYPE_MOVIE: Final = "Movie"
|
||||
|
||||
MAX_IMAGE_WIDTH: Final = 500
|
||||
MAX_STREAMING_BITRATE: Final = "140000000"
|
||||
|
@ -33,8 +33,9 @@ MEDIA_SOURCE_KEY_PATH: Final = "Path"
|
|||
|
||||
MEDIA_TYPE_AUDIO: Final = "Audio"
|
||||
MEDIA_TYPE_NONE: Final = ""
|
||||
MEDIA_TYPE_VIDEO: Final = "Video"
|
||||
|
||||
SUPPORTED_COLLECTION_TYPES: Final = [COLLECTION_TYPE_MUSIC]
|
||||
SUPPORTED_COLLECTION_TYPES: Final = [COLLECTION_TYPE_MUSIC, COLLECTION_TYPE_MOVIES]
|
||||
|
||||
USER_APP_NAME: Final = "Home Assistant"
|
||||
USER_AGENT: Final = f"Home-Assistant/{CLIENT_VERSION}"
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Jellyfin",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/jellyfin",
|
||||
"requirements": ["jellyfin-apiclient-python==1.7.2"],
|
||||
"requirements": ["jellyfin-apiclient-python==1.8.1"],
|
||||
"iot_class": "local_polling",
|
||||
"codeowners": ["@j-stienstra"],
|
||||
"loggers": ["jellyfin_apiclient_python"]
|
||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import annotations
|
|||
import logging
|
||||
import mimetypes
|
||||
from typing import Any
|
||||
import urllib.parse
|
||||
|
||||
from jellyfin_apiclient_python.api import jellyfin_url
|
||||
from jellyfin_apiclient_python.client import JellyfinClient
|
||||
|
@ -13,6 +12,7 @@ from homeassistant.components.media_player.const import (
|
|||
MEDIA_CLASS_ALBUM,
|
||||
MEDIA_CLASS_ARTIST,
|
||||
MEDIA_CLASS_DIRECTORY,
|
||||
MEDIA_CLASS_MOVIE,
|
||||
MEDIA_CLASS_TRACK,
|
||||
)
|
||||
from homeassistant.components.media_player.errors import BrowseError
|
||||
|
@ -25,6 +25,7 @@ from homeassistant.components.media_source.models import (
|
|||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import (
|
||||
COLLECTION_TYPE_MOVIES,
|
||||
COLLECTION_TYPE_MUSIC,
|
||||
DATA_CLIENT,
|
||||
DOMAIN,
|
||||
|
@ -39,11 +40,12 @@ from .const import (
|
|||
ITEM_TYPE_ARTIST,
|
||||
ITEM_TYPE_AUDIO,
|
||||
ITEM_TYPE_LIBRARY,
|
||||
ITEM_TYPE_MOVIE,
|
||||
MAX_IMAGE_WIDTH,
|
||||
MAX_STREAMING_BITRATE,
|
||||
MEDIA_SOURCE_KEY_PATH,
|
||||
MEDIA_TYPE_AUDIO,
|
||||
MEDIA_TYPE_NONE,
|
||||
MEDIA_TYPE_VIDEO,
|
||||
SUPPORTED_COLLECTION_TYPES,
|
||||
)
|
||||
|
||||
|
@ -147,6 +149,8 @@ class JellyfinSource(MediaSource):
|
|||
|
||||
if collection_type == COLLECTION_TYPE_MUSIC:
|
||||
return await self._build_music_library(library, include_children)
|
||||
if collection_type == COLLECTION_TYPE_MOVIES:
|
||||
return await self._build_movie_library(library, include_children)
|
||||
|
||||
raise BrowseError(f"Unsupported collection type {collection_type}")
|
||||
|
||||
|
@ -270,6 +274,55 @@ class JellyfinSource(MediaSource):
|
|||
|
||||
return result
|
||||
|
||||
async def _build_movie_library(
|
||||
self, library: dict[str, Any], include_children: bool
|
||||
) -> BrowseMediaSource:
|
||||
"""Return a single movie library as a browsable media source."""
|
||||
library_id = library[ITEM_KEY_ID]
|
||||
library_name = library[ITEM_KEY_NAME]
|
||||
|
||||
result = BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=library_id,
|
||||
media_class=MEDIA_CLASS_DIRECTORY,
|
||||
media_content_type=MEDIA_TYPE_NONE,
|
||||
title=library_name,
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
)
|
||||
|
||||
if include_children:
|
||||
result.children_media_class = MEDIA_CLASS_MOVIE
|
||||
result.children = await self._build_movies(library_id) # type: ignore[assignment]
|
||||
|
||||
return result
|
||||
|
||||
async def _build_movies(self, library_id: str) -> list[BrowseMediaSource]:
|
||||
"""Return all movies in the movie library."""
|
||||
movies = await self._get_children(library_id, ITEM_TYPE_MOVIE)
|
||||
movies = sorted(movies, key=lambda k: k[ITEM_KEY_NAME]) # type: ignore[no-any-return]
|
||||
return [self._build_movie(movie) for movie in movies]
|
||||
|
||||
def _build_movie(self, movie: dict[str, Any]) -> BrowseMediaSource:
|
||||
"""Return a single movie as a browsable media source."""
|
||||
movie_id = movie[ITEM_KEY_ID]
|
||||
movie_title = movie[ITEM_KEY_NAME]
|
||||
mime_type = _media_mime_type(movie)
|
||||
thumbnail_url = self._get_thumbnail_url(movie)
|
||||
|
||||
result = BrowseMediaSource(
|
||||
domain=DOMAIN,
|
||||
identifier=movie_id,
|
||||
media_class=MEDIA_CLASS_MOVIE,
|
||||
media_content_type=mime_type,
|
||||
title=movie_title,
|
||||
can_play=True,
|
||||
can_expand=False,
|
||||
thumbnail=thumbnail_url,
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
async def _get_children(
|
||||
self, parent_id: str, item_type: str
|
||||
) -> list[dict[str, Any]]:
|
||||
|
@ -279,7 +332,7 @@ class JellyfinSource(MediaSource):
|
|||
"ParentId": parent_id,
|
||||
"IncludeItemTypes": item_type,
|
||||
}
|
||||
if item_type == ITEM_TYPE_AUDIO:
|
||||
if item_type in {ITEM_TYPE_AUDIO, ITEM_TYPE_MOVIE}:
|
||||
params["Fields"] = ITEM_KEY_MEDIA_SOURCES
|
||||
|
||||
result = await self.hass.async_add_executor_job(self.api.user_items, "", params)
|
||||
|
@ -298,29 +351,15 @@ class JellyfinSource(MediaSource):
|
|||
def _get_stream_url(self, media_item: dict[str, Any]) -> str:
|
||||
"""Return the stream URL for a media item."""
|
||||
media_type = media_item[ITEM_KEY_MEDIA_TYPE]
|
||||
item_id = media_item[ITEM_KEY_ID]
|
||||
|
||||
if media_type == MEDIA_TYPE_AUDIO:
|
||||
return self._get_audio_stream_url(media_item)
|
||||
return self.api.audio_url(item_id) # type: ignore[no-any-return]
|
||||
if media_type == MEDIA_TYPE_VIDEO:
|
||||
return self.api.video_url(item_id) # type: ignore[no-any-return]
|
||||
|
||||
raise BrowseError(f"Unsupported media type {media_type}")
|
||||
|
||||
def _get_audio_stream_url(self, media_item: dict[str, Any]) -> str:
|
||||
"""Return the stream URL for a music media item."""
|
||||
item_id = media_item[ITEM_KEY_ID]
|
||||
user_id = self.client.config.data["auth.user_id"]
|
||||
device_id = self.client.config.data["app.device_id"]
|
||||
api_key = self.client.config.data["auth.token"]
|
||||
|
||||
params = urllib.parse.urlencode(
|
||||
{
|
||||
"UserId": user_id,
|
||||
"DeviceId": device_id,
|
||||
"api_key": api_key,
|
||||
"MaxStreamingBitrate": MAX_STREAMING_BITRATE,
|
||||
}
|
||||
)
|
||||
return f"{self.url}Audio/{item_id}/universal?{params}"
|
||||
|
||||
|
||||
def _media_mime_type(media_item: dict[str, Any]) -> str:
|
||||
"""Return the mime type of a media item."""
|
||||
|
|
|
@ -903,7 +903,7 @@ iperf3==0.1.11
|
|||
ismartgate==4.0.4
|
||||
|
||||
# homeassistant.components.jellyfin
|
||||
jellyfin-apiclient-python==1.7.2
|
||||
jellyfin-apiclient-python==1.8.1
|
||||
|
||||
# homeassistant.components.rest
|
||||
jsonpath==0.82
|
||||
|
|
|
@ -643,7 +643,7 @@ iotawattpy==0.1.0
|
|||
ismartgate==4.0.4
|
||||
|
||||
# homeassistant.components.jellyfin
|
||||
jellyfin-apiclient-python==1.7.2
|
||||
jellyfin-apiclient-python==1.8.1
|
||||
|
||||
# homeassistant.components.rest
|
||||
jsonpath==0.82
|
||||
|
|
Loading…
Reference in New Issue