Jellyfin: Add support for movie collections (#73086)

This commit is contained in:
Jan Stienstra 2022-06-06 21:17:21 +02:00 committed by GitHub
parent a9e4673aff
commit ed54cea3f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 66 additions and 26 deletions

View File

@ -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}"

View File

@ -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"]

View File

@ -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."""

View File

@ -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

View File

@ -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