Skip to content

Commit

Permalink
[media.jellyfin] Playlists support (backend implementation).
Browse files Browse the repository at this point in the history
  • Loading branch information
blacklight committed Nov 7, 2024
1 parent 7c7b80c commit b967cb1
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 3 deletions.
66 changes: 63 additions & 3 deletions platypush/plugins/media/jellyfin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Iterable, List, Optional, Type
from typing import Collection, Iterable, List, Optional, Type

import requests
from marshmallow import Schema
Expand All @@ -12,6 +12,7 @@
JellyfinEpisodeSchema,
JellyfinMovieSchema,
JellyfinPhotoSchema,
JellyfinPlaylistSchema,
JellyfinTrackSchema,
JellyfinVideoSchema,
)
Expand Down Expand Up @@ -46,7 +47,9 @@ def __init__(
self._api_key = api_key
self.__user_id = None

def _execute(self, method: str, url: str, *args, **kwargs) -> dict:
def _execute(
self, method: str, url: str, *args, return_json: bool = True, **kwargs
) -> dict:
url = '/' + url.lstrip('/')
url = self.server + url

Expand All @@ -59,7 +62,7 @@ def _execute(self, method: str, url: str, *args, **kwargs) -> dict:
rs = getattr(requests, method.lower())(url, *args, **kwargs)
rs.raise_for_status()

return rs.json()
return rs.json() if return_json else {}

@property
def _user_id(self) -> str:
Expand Down Expand Up @@ -165,6 +168,8 @@ def _serialize_search_results(self, search_results: Iterable[dict]) -> List[dict
result = JellyfinEpisodeSchema().dump(result)
elif result['Type'] == 'Audio':
result = JellyfinTrackSchema().dump(result)
elif result['Type'] == 'Playlist':
result = JellyfinPlaylistSchema().dump(result)
elif result['Type'] == 'MusicArtist':
result = JellyfinArtistSchema().dump(result)
elif result['Type'] == 'MusicAlbum':
Expand Down Expand Up @@ -379,3 +384,58 @@ def search(
)

return self._serialize_search_results(results)

@action
def create_playlist(
self, name: str, public: bool = True, item_ids: Optional[Collection[str]] = None
) -> dict:
"""
Create a new playlist.
:param name: Name of the playlist.
:param public: Whether the playlist should be visible to any logged-in user.
:param item_ids: List of item IDs to add to the playlist.
"""
playlist = self._execute(
'POST',
'/Playlists',
json={
'Name': name,
'UserId': self._user_id,
'IsPublic': public,
'Items': item_ids or [],
},
)

return dict(
JellyfinPlaylistSchema().dump(
{
'Name': name,
'IsPublic': public,
**playlist,
}
)
)

@action
def delete_item(self, item_id: str) -> dict:
"""
Delete an item from the server.
:param item_id: ID of the item to delete.
"""
return self._execute('DELETE', f'/Items/{item_id}', return_json=False)

@action
def add_to_playlist(self, playlist_id: str, item_ids: Collection[str]) -> dict:
"""
Add items to a playlist.
:param playlist_id: ID of the playlist.
:param item_ids: List of item IDs to add to the playlist.
"""
return self._execute(
'POST',
f'/Playlists/{playlist_id}/Items',
params={'ids': ','.join(item_ids)},
)
15 changes: 15 additions & 0 deletions platypush/schemas/media/jellyfin.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,18 @@ def _gen_book_url(self, data, **_):
] = f'{self._server}/Items/{data["Id"]}/Download?api_key={self._api_key}'
data['embed_url'] = f'{self._server}/web/#/details?id={data["Id"]}'
return data


class JellyfinPlaylistSchema(JellyfinSchema, MediaCollectionSchema):
id = fields.String(attribute='Id')
type = fields.Constant('playlist')
item_type = fields.Constant('playlist')
collection_type = fields.Constant('music')
name = fields.String(attribute='Name')
image = fields.String()
public = fields.Boolean(attribute='IsPublic')
duration = fields.Number(attribute='RunTimeTicks')
n_items = fields.Number(attribute='ChildCount')
genres = fields.List(fields.String, attribute='Genres')
tags = fields.List(fields.String, attribute='Tags')
created_at = DateTime(attribute='DateCreated')

0 comments on commit b967cb1

Please sign in to comment.