Black/LINT for Jellyfin plugin.

This commit is contained in:
Fabio Manganiello 2023-11-07 00:13:22 +01:00
parent a94ddd3f05
commit e7bd61e0d4
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
2 changed files with 85 additions and 61 deletions

View File

@ -4,8 +4,13 @@ import requests
from marshmallow import Schema from marshmallow import Schema
from platypush.plugins import Plugin, action from platypush.plugins import Plugin, action
from platypush.schemas.media.jellyfin import JellyfinArtistSchema, \ from platypush.schemas.media.jellyfin import (
JellyfinCollectionSchema, JellyfinMovieSchema, JellyfinEpisodeSchema JellyfinArtistSchema,
JellyfinCollectionSchema,
JellyfinMovieSchema,
JellyfinVideoSchema,
JellyfinEpisodeSchema,
)
class MediaJellyfinPlugin(Plugin): class MediaJellyfinPlugin(Plugin):
@ -20,7 +25,9 @@ class MediaJellyfinPlugin(Plugin):
# Maximum number of results returned per query action # Maximum number of results returned per query action
_default_limit = 100 _default_limit = 100
def __init__(self, server: str, api_key: str, username: Optional[str] = None, **kwargs): def __init__(
self, server: str, api_key: str, username: Optional[str] = None, **kwargs
):
""" """
:param server: Jellyfin base server URL (including ``http://`` or ``https://``). :param server: Jellyfin base server URL (including ``http://`` or ``https://``).
:param api_key: Server API key. You can generate one from :param api_key: Server API key. You can generate one from
@ -35,16 +42,14 @@ class MediaJellyfinPlugin(Plugin):
self._api_key = api_key self._api_key = api_key
self.__user_id = None self.__user_id = None
def _execute( def _execute(self, method: str, url: str, *args, **kwargs) -> dict:
self, method: str, url: str, *args, **kwargs
) -> dict:
url = '/' + url.lstrip('/') url = '/' + url.lstrip('/')
url = self.server + url url = self.server + url
kwargs['headers'] = { kwargs['headers'] = {
**kwargs.get('headers', {}), **kwargs.get('headers', {}),
'X-Emby-Authorization': 'MediaBrowser Client="Platypush", Device="Platypush", ' 'X-Emby-Authorization': 'MediaBrowser Client="Platypush", Device="Platypush", '
f'Token="{self._api_key}"' f'Token="{self._api_key}"',
} }
rs = getattr(requests, method.lower())(url, *args, **kwargs) rs = getattr(requests, method.lower())(url, *args, **kwargs)
@ -58,19 +63,21 @@ class MediaJellyfinPlugin(Plugin):
try: try:
self.__user_id = self._execute('GET', '/Users/Me')['Id'] self.__user_id = self._execute('GET', '/Users/Me')['Id']
except requests.exceptions.HTTPError as e: except requests.exceptions.HTTPError as e:
assert e.response.status_code == 400, ( assert (
f'Could not get the current user: {e}' e.response.status_code == 400
) ), f'Could not get the current user: {e}'
self.__user_id = self._execute('GET', '/Users')[0]['Id'] self.__user_id = self._execute('GET', '/Users')[0]['Id']
return self.__user_id return self.__user_id
def _query( def _query(
self, url: str, self,
url: str,
schema_class: Optional[Type[Schema]] = None, schema_class: Optional[Type[Schema]] = None,
query: Optional[str] = None, query: Optional[str] = None,
limit: Optional[int] = _default_limit, offset: int = 0, limit: Optional[int] = _default_limit,
offset: int = 0,
parent_id: Optional[str] = None, parent_id: Optional[str] = None,
is_played: Optional[bool] = None, is_played: Optional[bool] = None,
is_favourite: Optional[bool] = None, is_favourite: Optional[bool] = None,
@ -78,7 +85,7 @@ class MediaJellyfinPlugin(Plugin):
genres: Optional[Iterable[str]] = None, genres: Optional[Iterable[str]] = None,
tags: Optional[Iterable[str]] = None, tags: Optional[Iterable[str]] = None,
years: Optional[Iterable[int]] = None, years: Optional[Iterable[int]] = None,
**kwargs **kwargs,
) -> Iterable[dict]: ) -> Iterable[dict]:
filters = [] filters = []
if is_played is not None: if is_played is not None:
@ -107,46 +114,51 @@ class MediaJellyfinPlugin(Plugin):
return results return results
def _flatten_series_result( def _flatten_series_result(self, search_result: dict) -> Iterable[dict]:
self, search_result: dict
) -> Iterable[dict]:
episodes = [] episodes = []
show_id = search_result['Id'] show_id = search_result['Id']
seasons = self._execute( seasons = self._execute(
'get', f'/Shows/{show_id}/Seasons', 'get',
f'/Shows/{show_id}/Seasons',
params={ params={
'userId': self._user_id, 'userId': self._user_id,
} },
).get('Items', []) ).get('Items', [])
for i, season in enumerate(seasons): for i, season in enumerate(seasons):
episodes.extend( episodes.extend(
JellyfinEpisodeSchema().dump([ JellyfinEpisodeSchema().dump(
{**episode, 'SeasonIndex': i+1} [
for episode in self._execute( {**episode, 'SeasonIndex': i + 1}
'get', f'/Shows/{show_id}/Episodes', for episode in self._execute(
params={ 'get',
'userId': self._user_id, f'/Shows/{show_id}/Episodes',
'seasonId': season['Id'], params={
} 'userId': self._user_id,
).get('Items', []) 'seasonId': season['Id'],
], many=True) },
).get('Items', [])
],
many=True,
)
) )
return episodes return episodes
def _serialize_search_results(self, search_results: Iterable[dict]) -> Iterable[dict]: def _serialize_search_results(
self, search_results: Iterable[dict]
) -> Iterable[dict]:
serialized_results = [] serialized_results = []
for result in search_results: for result in search_results:
if result['Type'] == 'CollectionFolder': if result['Type'] == 'CollectionFolder':
result = JellyfinCollectionSchema().dump(result) result = JellyfinCollectionSchema().dump(result)
result['type'] = 'collection' # type: ignore result['type'] = 'collection' # type: ignore
elif result['Type'] == 'Movie': elif result['Type'] == 'Movie':
result = JellyfinMovieSchema().dump(result) result = JellyfinMovieSchema().dump(result)
result['type'] = 'movie' # type: ignore result['type'] = 'movie' # type: ignore
elif result['Type'] == 'Movie': elif result['Type'] == 'Video':
result = JellyfinMovieSchema().dump(result) result = JellyfinVideoSchema().dump(result)
result['type'] = 'movie' # type: ignore result['type'] = 'video' # type: ignore
elif result['Type'] == 'Series': elif result['Type'] == 'Series':
serialized_results += self._flatten_series_result(result) serialized_results += self._flatten_series_result(result)
for r in serialized_results: for r in serialized_results:
@ -185,10 +197,17 @@ class MediaJellyfinPlugin(Plugin):
:return: .. schema:: media.jellyfin.JellyfinArtistSchema(many=True) :return: .. schema:: media.jellyfin.JellyfinArtistSchema(many=True)
""" """
return self._query( return self._query(
'/Artists', schema_class=JellyfinArtistSchema, '/Artists',
limit=limit, offset=offset, is_favourite=is_favourite, schema_class=JellyfinArtistSchema,
is_played=is_played, is_liked=is_liked, genres=genres, limit=limit,
query=query, tags=tags, years=years offset=offset,
is_favourite=is_favourite,
is_played=is_played,
is_liked=is_liked,
genres=genres,
query=query,
tags=tags,
years=years,
) )
@action @action
@ -202,7 +221,7 @@ class MediaJellyfinPlugin(Plugin):
f'/Users/{self._user_id}/Items', f'/Users/{self._user_id}/Items',
parent_id=None, parent_id=None,
schema_class=JellyfinCollectionSchema, schema_class=JellyfinCollectionSchema,
params=dict(recursive=False), params={'recursive': False},
) )
@action @action
@ -258,9 +277,10 @@ class MediaJellyfinPlugin(Plugin):
""" """
if collection: if collection:
collections = self.get_collections().output # type: ignore collections = self.get_collections().output # type: ignore
matching_collections = [ matching_collections = [
c for c in collections c
for c in collections
if c['id'] == collection or c['name'].lower() == collection.lower() if c['id'] == collection or c['name'].lower() == collection.lower()
] ]
@ -272,28 +292,36 @@ class MediaJellyfinPlugin(Plugin):
results = self._query( results = self._query(
f'/Users/{self._user_id}/Items', f'/Users/{self._user_id}/Items',
limit=limit, offset=offset, is_favourite=is_favourite, limit=limit,
is_played=is_played, is_liked=is_liked, genres=genres, offset=offset,
query=query, tags=tags, years=years, parent_id=parent_id, is_favourite=is_favourite,
is_played=is_played,
is_liked=is_liked,
genres=genres,
query=query,
tags=tags,
years=years,
parent_id=parent_id,
params={ params={
**( **(
{'sortOrder': 'Descending' if sort_desc else 'Ascending'} {'sortOrder': 'Descending' if sort_desc else 'Ascending'}
if sort_desc is not None else {} if sort_desc is not None
else {}
), ),
**( **(
{'hasSubtitles': has_subtitles} {'hasSubtitles': has_subtitles} if has_subtitles is not None else {}
if has_subtitles is not None else {}
), ),
**( **(
{'minCriticRating': minimum_critic_rating} {'minCriticRating': minimum_critic_rating}
if minimum_critic_rating is not None else {} if minimum_critic_rating is not None
else {}
), ),
**( **(
{'minCommunityRating': minimum_community_rating} {'minCommunityRating': minimum_community_rating}
if minimum_community_rating is not None else {} if minimum_community_rating is not None
else {}
), ),
} },
) )
return self._serialize_search_results(results) return self._serialize_search_results(results)

View File

@ -21,8 +21,8 @@ class JellyfinSchema(Schema):
def gen_img_url(self, data: dict, **_) -> dict: def gen_img_url(self, data: dict, **_) -> dict:
if 'image' in self.fields: if 'image' in self.fields:
data['image'] = ( data['image'] = (
get_plugin('media.jellyfin').server + # type: ignore get_plugin('media.jellyfin').server
f'/Items/{data["id"]}' + f'/Items/{data["id"]}' # type: ignore
'/Images/Primary?fillHeight=333&fillWidth=222&quality=96' '/Images/Primary?fillHeight=333&fillWidth=222&quality=96'
) )
@ -44,7 +44,8 @@ class JellyfinSchema(Schema):
if not video_format: if not video_format:
if not available_containers: if not available_containers:
warnings.warn( warnings.warn(
f'The media ID {data["Id"]} has no available video containers' f'The media ID {data["Id"]} has no available video containers',
stacklevel=2,
) )
return data return data
@ -53,17 +54,15 @@ class JellyfinSchema(Schema):
plugin = get_plugin('media.jellyfin') plugin = get_plugin('media.jellyfin')
assert plugin, 'The media.jellyfin plugin is not configured' assert plugin, 'The media.jellyfin plugin is not configured'
url = ( data['url'] = (
f'{plugin.server}/Videos/{data["Id"]}' f'{plugin.server}/Videos/{data["Id"]}'
f'/stream.{video_format}' f'/stream.{video_format}'
f'?Static=true&api_key={plugin._api_key}' f'?Static=true&api_key={plugin._api_key}'
) )
data['url'] = data['file'] = url
return data return data
class JellyfinArtistSchema(JellyfinSchema, MediaArtistSchema): class JellyfinArtistSchema(JellyfinSchema, MediaArtistSchema):
pass pass
@ -99,13 +98,10 @@ class JellyfinEpisodeSchema(JellyfinVideoSchema):
episode_index = data.get('IndexNumber') episode_index = data.get('IndexNumber')
if episode_index: if episode_index:
season_index = data.get('SeasonIndex', 1) season_index = data.get('SeasonIndex', 1)
episode_index = 's{:02d}e{:02d}'.format( episode_index = 's{:02d}e{:02d}'.format(season_index, episode_index)
season_index, episode_index
)
if episode_index: if episode_index:
prefix += f'{" " if prefix else ""}[{episode_index}] ' prefix += f'{" " if prefix else ""}[{episode_index}] '
data['Name'] = prefix + data.get('Name', '') data['Name'] = prefix + data.get('Name', '')
return data return data