Major improvements to server code
This commit is contained in:
@@ -5,15 +5,18 @@ import gi
|
|||||||
gi.require_version('Gtk', '3.0')
|
gi.require_version('Gtk', '3.0')
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
import mpv
|
||||||
|
|
||||||
from .app import LibremsonicApp
|
from .app import LibremsonicApp
|
||||||
from .server import Server
|
from .server import Server
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
server = Server('ohea', 'https://airsonic.the-evans.family', 'sumner',
|
server = Server('ohea', 'https://airsonic.the-evans.family', 'sumner',
|
||||||
'O}/UieSb[nzZ~l[X1S&zzX1Hi')
|
'O}/UieSb[nzZ~l[X1S&zzX1Hi')
|
||||||
|
|
||||||
print(server.ping())
|
print(server.ping())
|
||||||
print(server.get_license())
|
# print(server.search2('The Band Perry'))
|
||||||
|
stream = server.stream(740)
|
||||||
# app = LibremsonicApp()
|
# app = LibremsonicApp()
|
||||||
# app.run(sys.argv)
|
# app.run(sys.argv)
|
||||||
|
@@ -28,17 +28,6 @@ def from_json(cls, data):
|
|||||||
# Handle primitive of objects
|
# Handle primitive of objects
|
||||||
if data is None:
|
if data is None:
|
||||||
instance = None
|
instance = None
|
||||||
elif cls == str or issubclass(cls, str):
|
|
||||||
instance = data
|
|
||||||
elif cls == int or issubclass(cls, int):
|
|
||||||
instance = int(data)
|
|
||||||
elif cls == bool or issubclass(cls, bool):
|
|
||||||
instance = bool(data)
|
|
||||||
elif type(cls) == EnumMeta:
|
|
||||||
instance = cls(data)
|
|
||||||
elif cls == datetime:
|
|
||||||
instance = parser.parse(data)
|
|
||||||
|
|
||||||
# Handle generics. List[*], Dict[*, *] in particular.
|
# Handle generics. List[*], Dict[*, *] in particular.
|
||||||
elif type(cls) == typing._GenericAlias:
|
elif type(cls) == typing._GenericAlias:
|
||||||
# Having to use this because things changed in Python 3.7.
|
# Having to use this because things changed in Python 3.7.
|
||||||
@@ -63,6 +52,17 @@ def from_json(cls, data):
|
|||||||
raise Exception(
|
raise Exception(
|
||||||
f'Trying to deserialize an unsupported type: {cls._name}')
|
f'Trying to deserialize an unsupported type: {cls._name}')
|
||||||
|
|
||||||
|
elif cls == str or issubclass(cls, str):
|
||||||
|
instance = data
|
||||||
|
elif cls == int or issubclass(cls, int):
|
||||||
|
instance = int(data)
|
||||||
|
elif cls == bool or issubclass(cls, bool):
|
||||||
|
instance = bool(data)
|
||||||
|
elif type(cls) == EnumMeta:
|
||||||
|
instance = cls(data)
|
||||||
|
elif cls == datetime:
|
||||||
|
instance = parser.parse(data)
|
||||||
|
|
||||||
# Handle everything else by first instantiating the class, then adding
|
# Handle everything else by first instantiating the class, then adding
|
||||||
# all of the sub-elements, recursively calling from_json on them.
|
# all of the sub-elements, recursively calling from_json on them.
|
||||||
else:
|
else:
|
||||||
|
@@ -1,9 +1,36 @@
|
|||||||
import requests
|
import math
|
||||||
from typing import List, Optional, Dict
|
from deprecated import deprecated
|
||||||
|
from typing import Any, Optional, Dict, List, Union, Iterator
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from .api_objects import (Response, License, MusicFolder, Indexes, AlbumInfo,
|
import requests
|
||||||
ArtistInfo, VideoInfo, Child, AlbumID3, Artist,
|
|
||||||
ArtistsID3, Directory, Genre)
|
from .api_objects import (
|
||||||
|
AlbumInfo,
|
||||||
|
AlbumList,
|
||||||
|
AlbumList2,
|
||||||
|
AlbumWithSongsID3,
|
||||||
|
ArtistInfo,
|
||||||
|
ArtistsID3,
|
||||||
|
ArtistWithAlbumsID3,
|
||||||
|
Child,
|
||||||
|
Directory,
|
||||||
|
Genres,
|
||||||
|
Indexes,
|
||||||
|
License,
|
||||||
|
MusicFolders,
|
||||||
|
NowPlaying,
|
||||||
|
Playlists,
|
||||||
|
PlaylistWithSongs,
|
||||||
|
Response,
|
||||||
|
SearchResult,
|
||||||
|
SearchResult2,
|
||||||
|
SearchResult3,
|
||||||
|
Starred,
|
||||||
|
Starred2,
|
||||||
|
Songs,
|
||||||
|
VideoInfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Server:
|
class Server:
|
||||||
@@ -29,6 +56,9 @@ class Server:
|
|||||||
def _make_url(self, endpoint: str) -> str:
|
def _make_url(self, endpoint: str) -> str:
|
||||||
return f'{self.hostname}/rest/{endpoint}.view'
|
return f'{self.hostname}/rest/{endpoint}.view'
|
||||||
|
|
||||||
|
def _subsonic_error_to_exception(self, error):
|
||||||
|
return Exception(f'{error.code}: {error.message}')
|
||||||
|
|
||||||
def _post(self, url, **params) -> Response:
|
def _post(self, url, **params) -> Response:
|
||||||
"""
|
"""
|
||||||
Make a post to a *Sonic REST API. Handle all types of errors including
|
Make a post to a *Sonic REST API. Handle all types of errors including
|
||||||
@@ -58,10 +88,35 @@ class Server:
|
|||||||
|
|
||||||
# Check for an error and if it exists, raise it.
|
# Check for an error and if it exists, raise it.
|
||||||
if response.get('error'):
|
if response.get('error'):
|
||||||
raise response.error.as_exception()
|
raise self._subsonic_error_to_exception(response.error)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def _stream(self, url, **params) -> Iterator[Any]:
|
||||||
|
"""
|
||||||
|
Stream a file.
|
||||||
|
"""
|
||||||
|
params = {**self._get_params(), **params}
|
||||||
|
result = requests.post(url, data=params, stream=True)
|
||||||
|
# TODO make better
|
||||||
|
if result.status_code != 200:
|
||||||
|
raise Exception(f'Fail! {result.status_code}')
|
||||||
|
|
||||||
|
content_type = result.headers.get('Content-Type', '')
|
||||||
|
|
||||||
|
if 'application/json' in content_type:
|
||||||
|
# Error occurred
|
||||||
|
subsonic_response = result.json()['subsonic-response']
|
||||||
|
|
||||||
|
# TODO make better
|
||||||
|
if not subsonic_response:
|
||||||
|
raise Exception('Fail!')
|
||||||
|
|
||||||
|
response = Response.from_json(subsonic_response)
|
||||||
|
raise self._subsonic_error_to_exception(response.error)
|
||||||
|
else:
|
||||||
|
return result.iter_content(chunk_size=1024)
|
||||||
|
|
||||||
def ping(self) -> Response:
|
def ping(self) -> Response:
|
||||||
"""
|
"""
|
||||||
Used to test connectivity with the server.
|
Used to test connectivity with the server.
|
||||||
@@ -75,16 +130,18 @@ class Server:
|
|||||||
result = self._post(self._make_url('getLicense'))
|
result = self._post(self._make_url('getLicense'))
|
||||||
return result.license
|
return result.license
|
||||||
|
|
||||||
def get_music_folders(self) -> List[MusicFolder]:
|
def get_music_folders(self) -> MusicFolders:
|
||||||
"""
|
"""
|
||||||
Returns all configured top-level music folders.
|
Returns all configured top-level music folders.
|
||||||
"""
|
"""
|
||||||
result = self._post(self._make_url('getMusicFolders'))
|
result = self._post(self._make_url('getMusicFolders'))
|
||||||
return result.musicFolders.musicFolder
|
return result.musicFolders
|
||||||
|
|
||||||
def get_indexes(self,
|
def get_indexes(
|
||||||
music_folder_id: int = None,
|
self,
|
||||||
if_modified_since: int = None) -> Indexes:
|
music_folder_id: int = None,
|
||||||
|
if_modified_since: int = None,
|
||||||
|
) -> Indexes:
|
||||||
"""
|
"""
|
||||||
Returns an indexed structure of all artists.
|
Returns an indexed structure of all artists.
|
||||||
|
|
||||||
@@ -111,12 +168,12 @@ class Server:
|
|||||||
id=str(dir_id))
|
id=str(dir_id))
|
||||||
return result.directory
|
return result.directory
|
||||||
|
|
||||||
def get_genres(self) -> List[Genre]:
|
def get_genres(self) -> Genres:
|
||||||
"""
|
"""
|
||||||
Returns all genres.
|
Returns all genres.
|
||||||
"""
|
"""
|
||||||
result = self._post(self._make_url('getGenres'))
|
result = self._post(self._make_url('getGenres'))
|
||||||
return result.genres.genre
|
return result.genres
|
||||||
|
|
||||||
def get_artists(self, music_folder_id: int = None) -> ArtistsID3:
|
def get_artists(self, music_folder_id: int = None) -> ArtistsID3:
|
||||||
"""
|
"""
|
||||||
@@ -129,7 +186,7 @@ class Server:
|
|||||||
musicFolderId=music_folder_id)
|
musicFolderId=music_folder_id)
|
||||||
return result.artists
|
return result.artists
|
||||||
|
|
||||||
def get_artist(self, artist_id: int) -> Artist:
|
def get_artist(self, artist_id: int) -> ArtistWithAlbumsID3:
|
||||||
"""
|
"""
|
||||||
Returns details for an artist, including a list of albums. This method
|
Returns details for an artist, including a list of albums. This method
|
||||||
organizes music according to ID3 tags.
|
organizes music according to ID3 tags.
|
||||||
@@ -139,7 +196,7 @@ class Server:
|
|||||||
result = self._post(self._make_url('getArtist'), id=artist_id)
|
result = self._post(self._make_url('getArtist'), id=artist_id)
|
||||||
return result.artist
|
return result.artist
|
||||||
|
|
||||||
def get_album(self, album_id: int) -> AlbumID3:
|
def get_album(self, album_id: int) -> AlbumWithSongsID3:
|
||||||
"""
|
"""
|
||||||
Returns details for an album, including a list of songs. This method
|
Returns details for an album, including a list of songs. This method
|
||||||
organizes music according to ID3 tags.
|
organizes music according to ID3 tags.
|
||||||
@@ -175,11 +232,12 @@ class Server:
|
|||||||
result = self._post(self._make_url('getVideoInfo'), id=video_id)
|
result = self._post(self._make_url('getVideoInfo'), id=video_id)
|
||||||
return result.videoInfo
|
return result.videoInfo
|
||||||
|
|
||||||
def get_artist_info(self,
|
def get_artist_info(
|
||||||
id: int,
|
self,
|
||||||
count: int = None,
|
id: int,
|
||||||
include_not_present: bool = None
|
count: int = None,
|
||||||
) -> Optional[ArtistInfo]:
|
include_not_present: bool = None,
|
||||||
|
) -> Optional[ArtistInfo]:
|
||||||
"""
|
"""
|
||||||
Returns artist info with biography, image URLs and similar artists,
|
Returns artist info with biography, image URLs and similar artists,
|
||||||
using data from last.fm.
|
using data from last.fm.
|
||||||
@@ -191,17 +249,20 @@ class Server:
|
|||||||
present in the media library. Defaults to false according to API
|
present in the media library. Defaults to false according to API
|
||||||
Spec.
|
Spec.
|
||||||
"""
|
"""
|
||||||
result = self._post(self._make_url('getArtistInfo'),
|
result = self._post(
|
||||||
id=id,
|
self._make_url('getArtistInfo'),
|
||||||
count=count,
|
id=id,
|
||||||
includeNotPresent=include_not_present)
|
count=count,
|
||||||
|
includeNotPresent=include_not_present,
|
||||||
|
)
|
||||||
return result.artistInfo
|
return result.artistInfo
|
||||||
|
|
||||||
def get_artist_info2(self,
|
def get_artist_info2(
|
||||||
id: int,
|
self,
|
||||||
count: int = None,
|
id: int,
|
||||||
include_not_present: bool = None
|
count: int = None,
|
||||||
) -> Optional[ArtistInfo]:
|
include_not_present: bool = None,
|
||||||
|
) -> Optional[ArtistInfo]:
|
||||||
"""
|
"""
|
||||||
Similar to getArtistInfo, but organizes music according to ID3 tags.
|
Similar to getArtistInfo, but organizes music according to ID3 tags.
|
||||||
|
|
||||||
@@ -212,10 +273,12 @@ class Server:
|
|||||||
present in the media library. Defaults to false according to API
|
present in the media library. Defaults to false according to API
|
||||||
Spec.
|
Spec.
|
||||||
"""
|
"""
|
||||||
result = self._post(self._make_url('getArtistInfo2'),
|
result = self._post(
|
||||||
id=id,
|
self._make_url('getArtistInfo2'),
|
||||||
count=count,
|
id=id,
|
||||||
includeNotPresent=include_not_present)
|
count=count,
|
||||||
|
includeNotPresent=include_not_present,
|
||||||
|
)
|
||||||
return result.artistInfo
|
return result.artistInfo
|
||||||
|
|
||||||
def get_album_info(self, id: int) -> Optional[AlbumInfo]:
|
def get_album_info(self, id: int) -> Optional[AlbumInfo]:
|
||||||
@@ -234,7 +297,7 @@ class Server:
|
|||||||
:param id: The album or song ID.
|
:param id: The album or song ID.
|
||||||
"""
|
"""
|
||||||
result = self._post(self._make_url('getAlbumInfo2'), id=id)
|
result = self._post(self._make_url('getAlbumInfo2'), id=id)
|
||||||
return result.albumInfo2
|
return result.albumInfo
|
||||||
|
|
||||||
def get_similar_songs(self, id: int, count: int = None) -> List[Child]:
|
def get_similar_songs(self, id: int, count: int = None) -> List[Child]:
|
||||||
"""
|
"""
|
||||||
@@ -246,9 +309,11 @@ class Server:
|
|||||||
:param count: Max number of songs to return. Defaults to 50 according
|
:param count: Max number of songs to return. Defaults to 50 according
|
||||||
to API Spec.
|
to API Spec.
|
||||||
"""
|
"""
|
||||||
result = self._post(self._make_url('getSimilarSongs'),
|
result = self._post(
|
||||||
id=id,
|
self._make_url('getSimilarSongs'),
|
||||||
count=count)
|
id=id,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
return result.similarSongs.song
|
return result.similarSongs.song
|
||||||
|
|
||||||
def get_similar_songs2(self, id: int, count: int = None) -> List[Child]:
|
def get_similar_songs2(self, id: int, count: int = None) -> List[Child]:
|
||||||
@@ -259,9 +324,11 @@ class Server:
|
|||||||
:param count: Max number of songs to return. Defaults to 50 according
|
:param count: Max number of songs to return. Defaults to 50 according
|
||||||
to API Spec.
|
to API Spec.
|
||||||
"""
|
"""
|
||||||
result = self._post(self._make_url('getSimilarSongs2'),
|
result = self._post(
|
||||||
id=id,
|
self._make_url('getSimilarSongs2'),
|
||||||
count=count)
|
id=id,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
return result.similarSongs2.song
|
return result.similarSongs2.song
|
||||||
|
|
||||||
def get_top_songs(self, artist: str, count: int = None) -> List[Child]:
|
def get_top_songs(self, artist: str, count: int = None) -> List[Child]:
|
||||||
@@ -272,19 +339,23 @@ class Server:
|
|||||||
:param count: Max number of songs to return. Defaults to 50 according
|
:param count: Max number of songs to return. Defaults to 50 according
|
||||||
to API Spec.
|
to API Spec.
|
||||||
"""
|
"""
|
||||||
result = self._post(self._make_url('getTopSongs'),
|
result = self._post(
|
||||||
artist=artist,
|
self._make_url('getTopSongs'),
|
||||||
count=count)
|
artist=artist,
|
||||||
|
count=count,
|
||||||
|
)
|
||||||
return result.topSongs.song
|
return result.topSongs.song
|
||||||
|
|
||||||
def get_album_list(self,
|
def get_album_list(
|
||||||
type: str,
|
self,
|
||||||
size: int = None,
|
type: str,
|
||||||
offset: int = None,
|
size: int = None,
|
||||||
from_year: int = None,
|
offset: int = None,
|
||||||
to_year: int = None,
|
from_year: int = None,
|
||||||
genre: str = None,
|
to_year: int = None,
|
||||||
music_folder_id: int = None) -> List[Child]:
|
genre: str = None,
|
||||||
|
music_folder_id: int = None,
|
||||||
|
) -> AlbumList:
|
||||||
"""
|
"""
|
||||||
Returns a list of random, newest, highest rated etc. albums. Similar to
|
Returns a list of random, newest, highest rated etc. albums. Similar to
|
||||||
the album lists on the home page of the Subsonic web interface.
|
the album lists on the home page of the Subsonic web interface.
|
||||||
@@ -321,3 +392,403 @@ class Server:
|
|||||||
musicFolderId=music_folder_id,
|
musicFolderId=music_folder_id,
|
||||||
)
|
)
|
||||||
return result.albumList
|
return result.albumList
|
||||||
|
|
||||||
|
def get_album_list2(
|
||||||
|
self,
|
||||||
|
type: str,
|
||||||
|
size: int = None,
|
||||||
|
offset: int = None,
|
||||||
|
from_year: int = None,
|
||||||
|
to_year: int = None,
|
||||||
|
genre: str = None,
|
||||||
|
music_folder_id: int = None,
|
||||||
|
) -> AlbumList2:
|
||||||
|
"""
|
||||||
|
Similar to getAlbumList, but organizes music according to ID3 tags.
|
||||||
|
|
||||||
|
:param type: The list type. Must be one of the following: ``random``,
|
||||||
|
``newest``, ``frequent``, ``recent``, ``starred``,
|
||||||
|
``alphabeticalByName`` or ``alphabeticalByArtist``. Since 1.10.1
|
||||||
|
you can use ``byYear`` and ``byGenre`` to list albums in a given
|
||||||
|
year range or genre.
|
||||||
|
:param size: The number of albums to return. Max 500. Deafult is 10
|
||||||
|
according to API Spec.
|
||||||
|
:param offset: The list offset. Useful if you for example want to page
|
||||||
|
through the list of newest albums. Default is 0 according to API
|
||||||
|
Spec.
|
||||||
|
:param from_year: Required if ``type`` is ``byYear``. The first year in
|
||||||
|
the range. If ``fromYear > toYear`` a reverse chronological list is
|
||||||
|
returned.
|
||||||
|
:param to_year: Required if ``type`` is ``byYear``. The last year in
|
||||||
|
the range.
|
||||||
|
:param genre: Required if ``type`` is ``byGenre``. The name of the
|
||||||
|
genre, e.g., "Rock".
|
||||||
|
:param music_folder_id: (Since 1.11.0) Only return albums in the music
|
||||||
|
folder with the given ID. See ``getMusicFolders``.
|
||||||
|
"""
|
||||||
|
result = self._post(
|
||||||
|
self._make_url('getAlbumList2'),
|
||||||
|
type=type,
|
||||||
|
size=size,
|
||||||
|
offset=offset,
|
||||||
|
fromYear=from_year,
|
||||||
|
toYear=to_year,
|
||||||
|
genre=genre,
|
||||||
|
musicFolderId=music_folder_id,
|
||||||
|
)
|
||||||
|
return result.albumList2
|
||||||
|
|
||||||
|
def get_random_songs(
|
||||||
|
self,
|
||||||
|
size: int = None,
|
||||||
|
genre: str = None,
|
||||||
|
from_year: str = None,
|
||||||
|
to_year: str = None,
|
||||||
|
music_folder_id: int = None,
|
||||||
|
) -> Songs:
|
||||||
|
"""
|
||||||
|
Returns random songs matching the given criteria.
|
||||||
|
|
||||||
|
:param size: The maximum number of songs to return. Max 500. Defaults
|
||||||
|
to 10 according to API Spec.
|
||||||
|
:param genre: Only returns songs belonging to this genre.
|
||||||
|
:param from_year: Only return songs published after or in this year.
|
||||||
|
:param to_year: Only return songs published before or in this year.
|
||||||
|
:param music_folder_id: Only return albums in the music folder with the
|
||||||
|
given ID. See ``getMusicFolders``.
|
||||||
|
"""
|
||||||
|
result = self._post(
|
||||||
|
self._make_url('getRandomSongs'),
|
||||||
|
size=size,
|
||||||
|
genre=genre,
|
||||||
|
fromYear=from_year,
|
||||||
|
toYear=to_year,
|
||||||
|
musicFolderId=music_folder_id,
|
||||||
|
)
|
||||||
|
return result.randomSongs
|
||||||
|
|
||||||
|
def get_songs_by_genre(
|
||||||
|
self,
|
||||||
|
genre: str,
|
||||||
|
count: int = None,
|
||||||
|
offset: int = None,
|
||||||
|
music_folder_id: int = None,
|
||||||
|
) -> Songs:
|
||||||
|
"""
|
||||||
|
Returns songs in a given genre.
|
||||||
|
|
||||||
|
:param genre: Only returns songs belonging to this genre.
|
||||||
|
:param count: The maximum number of songs to return. Max 500. Defaults
|
||||||
|
to 10 according to API Spec.
|
||||||
|
:param offset: The offset. Useful if you want to page through the songs
|
||||||
|
in a genre.
|
||||||
|
:param music_folder_id: (Since 1.12.0) Only return albums in the music
|
||||||
|
folder with the given ID. See ``getMusicFolders``.
|
||||||
|
"""
|
||||||
|
result = self._post(
|
||||||
|
self._make_url('getSongsByGenre'),
|
||||||
|
genre=genre,
|
||||||
|
count=count,
|
||||||
|
offset=offset,
|
||||||
|
musicFolderId=music_folder_id,
|
||||||
|
)
|
||||||
|
return result.songsByGenre
|
||||||
|
|
||||||
|
def get_now_playing(self) -> NowPlaying:
|
||||||
|
"""
|
||||||
|
Returns what is currently being played by all users. Takes no extra
|
||||||
|
parameters.
|
||||||
|
"""
|
||||||
|
result = self._post(self._make_url('getNowPlaying'))
|
||||||
|
return result.nowPlaying
|
||||||
|
|
||||||
|
def get_starred(self, music_folder_id: int = None) -> Starred:
|
||||||
|
"""
|
||||||
|
Returns starred songs, albums and artists.
|
||||||
|
|
||||||
|
:param music_folder_id: (Since 1.12.0) Only return results from the
|
||||||
|
music folder with the given ID. See ``getMusicFolders``.
|
||||||
|
"""
|
||||||
|
result = self._post(self._make_url('getStarred'))
|
||||||
|
return result.starred
|
||||||
|
|
||||||
|
def get_starred2(self, music_folder_id: int = None) -> Starred2:
|
||||||
|
"""
|
||||||
|
Similar to getStarred, but organizes music according to ID3 tags.
|
||||||
|
|
||||||
|
:param music_folder_id: (Since 1.12.0) Only return results from the
|
||||||
|
music folder with the given ID. See ``getMusicFolders``.
|
||||||
|
"""
|
||||||
|
result = self._post(self._make_url('getStarred2'))
|
||||||
|
return result.starred2
|
||||||
|
|
||||||
|
@deprecated(version='1.4.0', reason='You should use search2 instead.')
|
||||||
|
def search(
|
||||||
|
self,
|
||||||
|
artist: str = None,
|
||||||
|
album: str = None,
|
||||||
|
title: str = None,
|
||||||
|
any: str = None,
|
||||||
|
count: int = None,
|
||||||
|
offset: int = None,
|
||||||
|
newer_than: datetime = None,
|
||||||
|
) -> SearchResult:
|
||||||
|
"""
|
||||||
|
Returns a listing of files matching the given search criteria. Supports
|
||||||
|
paging through the result.
|
||||||
|
|
||||||
|
:param artist: Artist to search for.
|
||||||
|
:param album: Album to searh for.
|
||||||
|
:param title: Song title to search for.
|
||||||
|
:param any: Searches all fields.
|
||||||
|
:param count: Maximum number of results to return.
|
||||||
|
:param offset: Search result offset. Used for paging.
|
||||||
|
:param newer_than: Only return matches that are newer than this.
|
||||||
|
"""
|
||||||
|
result = self._post(
|
||||||
|
self._make_url('search'),
|
||||||
|
artist=artist,
|
||||||
|
album=album,
|
||||||
|
title=title,
|
||||||
|
any=any,
|
||||||
|
count=count,
|
||||||
|
offset=offset,
|
||||||
|
newerThan=math.floor(newer_than.timestamp() *
|
||||||
|
1000) if newer_than else None,
|
||||||
|
)
|
||||||
|
return result.searchResult
|
||||||
|
|
||||||
|
def search2(
|
||||||
|
self,
|
||||||
|
query: str,
|
||||||
|
artist_count: int = None,
|
||||||
|
artist_offset: int = None,
|
||||||
|
album_count: int = None,
|
||||||
|
album_offset: int = None,
|
||||||
|
song_count: int = None,
|
||||||
|
song_offset: int = None,
|
||||||
|
music_folder_id: int = None,
|
||||||
|
) -> SearchResult2:
|
||||||
|
"""
|
||||||
|
Returns albums, artists and songs matching the given search criteria.
|
||||||
|
Supports paging through the result.
|
||||||
|
|
||||||
|
:param query: Search query.
|
||||||
|
:param artist_count: Maximum number of artists to return. Defaults to
|
||||||
|
20 according to API Spec.
|
||||||
|
:param artist_offset: Search result offset for artists. Used for
|
||||||
|
paging. Defualts to 0 according to API Spec.
|
||||||
|
:param album_count: Maximum number of albums to return. Defaults to 20
|
||||||
|
according to API Spec.
|
||||||
|
:param album_offset: Search result offset for albums. Used for paging.
|
||||||
|
Defualts to 0 according to API Spec.
|
||||||
|
:param song_count: Maximum number of songs to return. Defaults to 20
|
||||||
|
according to API Spec.
|
||||||
|
:param song_offset: Search result offset for songs. Used for paging.
|
||||||
|
Defualts to 0 according to API Spec.
|
||||||
|
:param music_folder_id: (Since 1.12.0) Only return results from the
|
||||||
|
music folder with the given ID. See ``getMusicFolders``.
|
||||||
|
"""
|
||||||
|
result = self._post(
|
||||||
|
self._make_url('search2'),
|
||||||
|
query=query,
|
||||||
|
artistCount=artist_count,
|
||||||
|
artistOffset=artist_offset,
|
||||||
|
albumCount=album_count,
|
||||||
|
albumOffset=album_offset,
|
||||||
|
songCount=song_count,
|
||||||
|
songOffset=song_offset,
|
||||||
|
musicFolderId=music_folder_id,
|
||||||
|
)
|
||||||
|
return result.searchResult2
|
||||||
|
|
||||||
|
def search3(
|
||||||
|
self,
|
||||||
|
query: str,
|
||||||
|
artist_count: int = None,
|
||||||
|
artist_offset: int = None,
|
||||||
|
album_count: int = None,
|
||||||
|
album_offset: int = None,
|
||||||
|
song_count: int = None,
|
||||||
|
song_offset: int = None,
|
||||||
|
music_folder_id: int = None,
|
||||||
|
) -> SearchResult3:
|
||||||
|
"""
|
||||||
|
Similar to search2, but organizes music according to ID3 tags.
|
||||||
|
|
||||||
|
:param query: Search query.
|
||||||
|
:param artist_count: Maximum number of artists to return. Defaults to
|
||||||
|
20 according to API Spec.
|
||||||
|
:param artist_offset: Search result offset for artists. Used for
|
||||||
|
paging. Defualts to 0 according to API Spec.
|
||||||
|
:param album_count: Maximum number of albums to return. Defaults to 20
|
||||||
|
according to API Spec.
|
||||||
|
:param album_offset: Search result offset for albums. Used for paging.
|
||||||
|
Defualts to 0 according to API Spec.
|
||||||
|
:param song_count: Maximum number of songs to return. Defaults to 20
|
||||||
|
according to API Spec.
|
||||||
|
:param song_offset: Search result offset for songs. Used for paging.
|
||||||
|
Defualts to 0 according to API Spec.
|
||||||
|
:param music_folder_id: (Since 1.12.0) Only return results from the
|
||||||
|
music folder with the given ID. See ``getMusicFolders``.
|
||||||
|
"""
|
||||||
|
result = self._post(
|
||||||
|
self._make_url('search3'),
|
||||||
|
query=query,
|
||||||
|
artistCount=artist_count,
|
||||||
|
artistOffset=artist_offset,
|
||||||
|
albumCount=album_count,
|
||||||
|
albumOffset=album_offset,
|
||||||
|
songCount=song_count,
|
||||||
|
songOffset=song_offset,
|
||||||
|
musicFolderId=music_folder_id,
|
||||||
|
)
|
||||||
|
return result.searchResult3
|
||||||
|
|
||||||
|
def get_playlists(self, username: str = None) -> Playlists:
|
||||||
|
"""
|
||||||
|
Returns all playlists a user is allowed to play.
|
||||||
|
|
||||||
|
:param username: (Since 1.8.0) If specified, return playlists for this
|
||||||
|
user rather than for the authenticated user. The authenticated user
|
||||||
|
must have admin role if this parameter is used.
|
||||||
|
"""
|
||||||
|
result = self._post(self._make_url('getPlaylists'), username=username)
|
||||||
|
return result.playlists
|
||||||
|
|
||||||
|
def get_playlist(self, id: int = None) -> PlaylistWithSongs:
|
||||||
|
"""
|
||||||
|
Returns a listing of files in a saved playlist.
|
||||||
|
|
||||||
|
:param username: ID of the playlist to return, as obtained by
|
||||||
|
``getPlaylists``.
|
||||||
|
"""
|
||||||
|
result = self._post(self._make_url('getPlaylist'), id=id)
|
||||||
|
return result.playlist
|
||||||
|
|
||||||
|
def create_playlist(
|
||||||
|
self,
|
||||||
|
playlist_id: int = None,
|
||||||
|
name: str = None,
|
||||||
|
song_id: Union[int, List[int]] = None,
|
||||||
|
) -> Union[PlaylistWithSongs, Response]:
|
||||||
|
"""
|
||||||
|
Creates (or updates) a playlist.
|
||||||
|
|
||||||
|
:param playlist_id: The playlist ID. Required if updating.
|
||||||
|
:param name: The human-readable name of the playlist. Required if
|
||||||
|
creating.
|
||||||
|
:param song_id: ID(s) of a song in the playlist. Can be a single ID or
|
||||||
|
a list of IDs.
|
||||||
|
"""
|
||||||
|
result = self._post(
|
||||||
|
self._make_url('createPlaylist'),
|
||||||
|
playlistId=playlist_id,
|
||||||
|
name=name,
|
||||||
|
songId=song_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.playlist:
|
||||||
|
return result.playlist
|
||||||
|
else:
|
||||||
|
return result
|
||||||
|
|
||||||
|
def update_playlist(
|
||||||
|
self,
|
||||||
|
playlist_id: int,
|
||||||
|
name: str = None,
|
||||||
|
comment: str = None,
|
||||||
|
public: bool = None,
|
||||||
|
song_id_to_add: Union[int, List[int]] = None,
|
||||||
|
song_index_to_remove: Union[int, List[int]] = None,
|
||||||
|
) -> Response:
|
||||||
|
"""
|
||||||
|
Updates a playlist. Only the owner of a playlist is allowed to update
|
||||||
|
it.
|
||||||
|
|
||||||
|
:param playlist_id: The playlist ID. Required if updating.
|
||||||
|
:param name: The human-readable name of the playlist.
|
||||||
|
:param comment: The playlist comment.
|
||||||
|
:param public: ``true`` if the playlist should be visible to all users,
|
||||||
|
``false`` otherwise.
|
||||||
|
:param song_id_to_add: Add this song with this ID to the playlist.
|
||||||
|
Multiple parameters allowed.
|
||||||
|
:param song_id_to_remove: Remove the song at this position in the
|
||||||
|
playlist. Multiple parameters allowed.
|
||||||
|
"""
|
||||||
|
return self._post(
|
||||||
|
self._make_url('updatePlaylist'),
|
||||||
|
playlistId=playlist_id,
|
||||||
|
name=name,
|
||||||
|
comment=comment,
|
||||||
|
public=public,
|
||||||
|
songIdToAdd=song_id_to_add,
|
||||||
|
songIdToRemove=song_index_to_remove,
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete_playlist(self, id: int) -> Response:
|
||||||
|
"""
|
||||||
|
Deletes a saved playlist
|
||||||
|
"""
|
||||||
|
return self._post(self._make_url('deletePlaylist'), id=id)
|
||||||
|
|
||||||
|
def stream(
|
||||||
|
self,
|
||||||
|
id: str,
|
||||||
|
max_bit_rate: int = None,
|
||||||
|
format: str = None,
|
||||||
|
time_offset: int = None,
|
||||||
|
size: int = None,
|
||||||
|
estimate_content_length: bool = False,
|
||||||
|
converted: bool = False,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Streams a given file.
|
||||||
|
|
||||||
|
:param id: A string which uniquely identifies the file to stream.
|
||||||
|
Obtained by calls to ``getMusicDirectory``.
|
||||||
|
:param maxBitRate: (Since 1.2.0) If specified, the server will attempt
|
||||||
|
to limit the bitrate to this value, in kilobits per second. If set
|
||||||
|
to zero, no limit is imposed.
|
||||||
|
:param format: (Since 1.6.0) Specifies the preferred target format
|
||||||
|
(e.g., "mp3" or "flv") in case there are multiple applicable
|
||||||
|
transcodings. Starting with 1.9.0 you can use the special value
|
||||||
|
"raw" to disable transcoding.
|
||||||
|
:param timeOffset: Only applicable to video streaming. If specified,
|
||||||
|
start streaming at the given offset (in seconds) into the video.
|
||||||
|
Typically used to implement video skipping.
|
||||||
|
:param size: (Since 1.6.0) Only applicable to video streaming.
|
||||||
|
Requested video size specified as WxH, for instance "640x480".
|
||||||
|
:param estimateContentLength: (Since 1.8.0). If set to ``True``, the
|
||||||
|
*Content-Length* HTTP header will be set to an estimated value for
|
||||||
|
transcoded or downsampled media. Defaults to False according to the
|
||||||
|
API Spec.
|
||||||
|
:param converted: (Since 1.14.0) Only applicable to video streaming.
|
||||||
|
Subsonic can optimize videos for streaming by converting them to
|
||||||
|
MP4. If a conversion exists for the video in question, then setting
|
||||||
|
this parameter to ``True`` will cause the converted video to be
|
||||||
|
returned instead of the original. Defaults to False according to
|
||||||
|
the API Spec.
|
||||||
|
"""
|
||||||
|
# TODO make this a decent object
|
||||||
|
return self._stream(
|
||||||
|
self._make_url('stream'),
|
||||||
|
id=id,
|
||||||
|
maxBitRate=max_bit_rate,
|
||||||
|
format=format,
|
||||||
|
timeOffset=time_offset,
|
||||||
|
size=size,
|
||||||
|
estimateContentLength=estimate_content_length,
|
||||||
|
converted=converted,
|
||||||
|
)
|
||||||
|
|
||||||
|
def download(self, id: str):
|
||||||
|
"""
|
||||||
|
Downloads a given media file. Similar to stream, but this method
|
||||||
|
returns the original media data without transcoding or downsampling.
|
||||||
|
|
||||||
|
:param id: A string which uniquely identifies the file to stream.
|
||||||
|
Obtained by calls to ``getMusicDirectory``.
|
||||||
|
"""
|
||||||
|
# TODO make this a decent object
|
||||||
|
return self._post(self._make_url('stream'), id=id)
|
||||||
|
Reference in New Issue
Block a user