1311 lines
49 KiB
Python
1311 lines
49 KiB
Python
import logging
|
|
import math
|
|
import os
|
|
from datetime import datetime
|
|
from time import sleep
|
|
from typing import Any, Dict, List, Optional, Union
|
|
from urllib.parse import urlencode
|
|
|
|
import requests
|
|
from deprecated import deprecated
|
|
|
|
from .api_objects import (
|
|
AlbumInfo,
|
|
AlbumList,
|
|
AlbumList2,
|
|
AlbumWithSongsID3,
|
|
ArtistInfo,
|
|
ArtistInfo2,
|
|
ArtistsID3,
|
|
ArtistWithAlbumsID3,
|
|
Bookmarks,
|
|
Child,
|
|
Directory,
|
|
Error,
|
|
Genres,
|
|
Indexes,
|
|
InternetRadioStations,
|
|
License,
|
|
Lyrics,
|
|
MusicFolders,
|
|
NowPlaying,
|
|
Playlists,
|
|
PlaylistWithSongs,
|
|
PlayQueue,
|
|
Response,
|
|
ScanStatus,
|
|
SearchResult,
|
|
SearchResult2,
|
|
SearchResult3,
|
|
Shares,
|
|
Songs,
|
|
Starred,
|
|
Starred2,
|
|
User,
|
|
Users,
|
|
VideoInfo,
|
|
)
|
|
|
|
|
|
class Server:
|
|
"""
|
|
Defines a \\*Sonic server.
|
|
|
|
Notes:
|
|
|
|
* The ``hls`` endpoint is not supported.
|
|
* The ``getCaptions`` endpoint is not supported
|
|
* None of the podcast endpoints are supported.
|
|
* The ``jukeboxControl`` endpoint is not supported.
|
|
* None of the chat message endpoints are supported.
|
|
* The ``server`` module is stateless. The only thing that it does is allow
|
|
the module's user to query the \\*sonic server via the API.
|
|
"""
|
|
|
|
class SubsonicServerError(Exception):
|
|
def __init__(self: "Server.SubsonicServerError", error: Error):
|
|
super().__init__(f"{error.code}: {error.message}")
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
hostname: str,
|
|
username: str,
|
|
password: str,
|
|
disable_cert_verify: bool,
|
|
):
|
|
self.name: str = name
|
|
self.hostname: str = hostname
|
|
self.username: str = username
|
|
self.password: str = password
|
|
self.disable_cert_verify: bool = disable_cert_verify
|
|
|
|
def _get_params(self) -> Dict[str, str]:
|
|
"""See Subsonic API Introduction for details."""
|
|
return {
|
|
"u": self.username,
|
|
"p": self.password,
|
|
"c": "Sublime Music",
|
|
"f": "json",
|
|
"v": "1.15.0",
|
|
}
|
|
|
|
def _make_url(self, endpoint: str) -> str:
|
|
return f"{self.hostname}/rest/{endpoint}.view"
|
|
|
|
# def _get(self, url, timeout=(3.05, 2), **params):
|
|
def _get(self, url: str, **params) -> Any:
|
|
params = {**self._get_params(), **params}
|
|
logging.info(f"[START] get: {url}")
|
|
|
|
if os.environ.get("SUBLIME_MUSIC_DEBUG_DELAY"):
|
|
logging.info(
|
|
"SUBLIME_MUSIC_DEBUG_DELAY enabled. Pausing for "
|
|
f"{os.environ['SUBLIME_MUSIC_DEBUG_DELAY']} seconds."
|
|
)
|
|
sleep(float(os.environ["SUBLIME_MUSIC_DEBUG_DELAY"]))
|
|
|
|
# Deal with datetime parameters (convert to milliseconds since 1970)
|
|
for k, v in params.items():
|
|
if type(v) == datetime:
|
|
params[k] = int(v.timestamp() * 1000)
|
|
|
|
result = requests.get(
|
|
url,
|
|
params=params,
|
|
verify=not self.disable_cert_verify,
|
|
# timeout=timeout,
|
|
)
|
|
# TODO (#122): make better
|
|
if result.status_code != 200:
|
|
raise Exception(f"[FAIL] get: {url} status={result.status_code}")
|
|
|
|
logging.info(f"[FINISH] get: {url}")
|
|
return result
|
|
|
|
def _get_json(
|
|
self, url: str, **params: Union[None, str, datetime, int, List[int]],
|
|
) -> Response:
|
|
"""
|
|
Make a get request to a *Sonic REST API. Handle all types of errors
|
|
including *Sonic ``<error>`` responses.
|
|
|
|
:returns: a Response containing all of the data of the
|
|
response, deserialized
|
|
:raises Exception: needs some work TODO
|
|
"""
|
|
result = self._get(url, **params)
|
|
subsonic_response = result.json().get("subsonic-response")
|
|
|
|
# TODO (#122): make better
|
|
if not subsonic_response:
|
|
raise Exception(f"[FAIL] get: invalid JSON from {url}")
|
|
|
|
if subsonic_response["status"] == "failed":
|
|
code, message = (
|
|
subsonic_response["error"].get("code"),
|
|
subsonic_response["error"].get("message"),
|
|
)
|
|
raise Exception(f"Subsonic API Error #{code}: {message}")
|
|
|
|
response = Response.from_json(subsonic_response)
|
|
|
|
# Check for an error and if it exists, raise it.
|
|
if response.error:
|
|
raise Server.SubsonicServerError(response.error)
|
|
|
|
return response
|
|
|
|
def do_download(self, url: str, **params) -> bytes:
|
|
download = self._get(url, **params)
|
|
if not download:
|
|
raise Exception("Download failed")
|
|
if "json" in download.headers.get("Content-Type"):
|
|
# TODO (#122): make better
|
|
raise Exception("Didn't expect JSON.")
|
|
return download.content
|
|
|
|
def ping(self) -> Response:
|
|
"""
|
|
Used to test connectivity with the server.
|
|
"""
|
|
return self._get_json(self._make_url("ping"))
|
|
|
|
def get_license(self) -> License:
|
|
"""Get details about the software license."""
|
|
return self._get_json(self._make_url("getLicense")).license
|
|
|
|
def get_music_folders(self) -> MusicFolders:
|
|
"""Returns all configured top-level music folders."""
|
|
return self._get_json(self._make_url("getMusicFolders")).musicFolders
|
|
|
|
def get_indexes(
|
|
self, music_folder_id: int = None, if_modified_since: int = None,
|
|
) -> Indexes:
|
|
"""
|
|
Returns an indexed structure of all artists.
|
|
|
|
:param music_folder_id: If specified, only return artists in the music
|
|
folder with the given ID. See ``getMusicFolders``.
|
|
:param if_modified_since: If specified, only return a result if the
|
|
artist collection has changed since the given time.
|
|
"""
|
|
result = self._get_json(
|
|
self._make_url("getIndexes"),
|
|
musicFolderId=music_folder_id,
|
|
ifModifiedSince=if_modified_since,
|
|
)
|
|
return result.indexes
|
|
|
|
def get_music_directory(self, dir_id: Union[int, str]) -> Directory:
|
|
"""
|
|
Returns a listing of all files in a music directory. Typically used
|
|
to get list of albums for an artist, or list of songs for an album.
|
|
|
|
:param dir_id: A string which uniquely identifies the music folder.
|
|
Obtained by calls to ``getIndexes`` or ``getMusicDirectory``.
|
|
"""
|
|
result = self._get_json(self._make_url("getMusicDirectory"), id=str(dir_id),)
|
|
return result.directory
|
|
|
|
def get_genres(self) -> Genres:
|
|
"""Returns all genres."""
|
|
return self._get_json(self._make_url("getGenres")).genres
|
|
|
|
def get_artists(self, music_folder_id: int = None) -> ArtistsID3:
|
|
"""
|
|
Similar to getIndexes, but organizes music according to ID3 tags.
|
|
|
|
:param music_folder_id: If specified, only return artists in the music
|
|
folder with the given ID. See ``getMusicFolders``.
|
|
"""
|
|
result = self._get_json(
|
|
self._make_url("getArtists"), musicFolderId=music_folder_id,
|
|
)
|
|
return result.artists
|
|
|
|
def get_artist(self, artist_id: int) -> ArtistWithAlbumsID3:
|
|
"""
|
|
Returns details for an artist, including a list of albums. This method
|
|
organizes music according to ID3 tags.
|
|
|
|
:param artist_id: The artist ID.
|
|
"""
|
|
return self._get_json(self._make_url("getArtist"), id=artist_id).artist
|
|
|
|
def get_album(self, album_id: int) -> AlbumWithSongsID3:
|
|
"""
|
|
Returns details for an album, including a list of songs. This method
|
|
organizes music according to ID3 tags.
|
|
|
|
:param album_id: The album ID.
|
|
"""
|
|
return self._get_json(self._make_url("getAlbum"), id=album_id).album
|
|
|
|
def get_song(self, song_id: int) -> Child:
|
|
"""
|
|
Returns details for a song.
|
|
|
|
:param song_id: The song ID.
|
|
"""
|
|
return self._get_json(self._make_url("getSong"), id=song_id).song
|
|
|
|
def get_videos(self) -> Optional[List[Child]]:
|
|
"""
|
|
Returns all video files.
|
|
"""
|
|
return self._get_json(self._make_url("getVideos")).videos.video
|
|
|
|
def get_video_info(self, video_id: int) -> Optional[VideoInfo]:
|
|
"""
|
|
Returns details for a video, including information about available
|
|
audio tracks, subtitles (captions) and conversions.
|
|
|
|
:param video_id: The video ID.
|
|
"""
|
|
result = self._get_json(self._make_url("getVideoInfo"), id=video_id)
|
|
return result.videoInfo
|
|
|
|
def get_artist_info(
|
|
self, id: int, count: int = None, include_not_present: bool = None,
|
|
) -> Optional[ArtistInfo]:
|
|
"""
|
|
Returns artist info with biography, image URLs and similar artists,
|
|
using data from last.fm.
|
|
|
|
:param id: The artist, album, or song ID.
|
|
:param count: Max number of similar artists to return. Defaults to 20,
|
|
according to API Spec.
|
|
:param include_not_present: Whether to return artists that are not
|
|
present in the media library. Defaults to false according to API
|
|
Spec.
|
|
"""
|
|
result = self._get_json(
|
|
self._make_url("getArtistInfo"),
|
|
id=id,
|
|
count=count,
|
|
includeNotPresent=include_not_present,
|
|
)
|
|
return result.artistInfo
|
|
|
|
def get_artist_info2(
|
|
self, id: int, count: int = None, include_not_present: bool = None,
|
|
) -> Optional[ArtistInfo2]:
|
|
"""
|
|
Similar to getArtistInfo, but organizes music according to ID3 tags.
|
|
|
|
:param id: The artist, album, or song ID.
|
|
:param count: Max number of similar artists to return. Defaults to 20,
|
|
according to API Spec.
|
|
:param include_not_present: Whether to return artists that are not
|
|
present in the media library. Defaults to false according to API
|
|
Spec.
|
|
"""
|
|
result = self._get_json(
|
|
self._make_url("getArtistInfo2"),
|
|
id=id,
|
|
count=count,
|
|
includeNotPresent=include_not_present,
|
|
)
|
|
return result.artistInfo2
|
|
|
|
def get_album_info(self, id: int) -> Optional[AlbumInfo]:
|
|
"""
|
|
Returns album notes, image URLs etc, using data from last.fm.
|
|
|
|
:param id: The album or song ID.
|
|
"""
|
|
result = self._get_json(self._make_url("getAlbumInfo"), id=id)
|
|
return result.albumInfo
|
|
|
|
def get_album_info2(self, id: int) -> Optional[AlbumInfo]:
|
|
"""
|
|
Similar to getAlbumInfo, but organizes music according to ID3 tags.
|
|
|
|
:param id: The album or song ID.
|
|
"""
|
|
result = self._get_json(self._make_url("getAlbumInfo2"), id=id)
|
|
return result.albumInfo
|
|
|
|
def get_similar_songs(self, id: int, count: int = None) -> List[Child]:
|
|
"""
|
|
Returns a random collection of songs from the given artist and similar
|
|
artists, using data from last.fm. Typically used for artist radio
|
|
features.
|
|
|
|
:param id: The artist, album or song ID.
|
|
:param count: Max number of songs to return. Defaults to 50 according
|
|
to API Spec.
|
|
"""
|
|
result = self._get_json(self._make_url("getSimilarSongs"), id=id, count=count,)
|
|
return result.similarSongs.song
|
|
|
|
def get_similar_songs2(self, id: int, count: int = None) -> List[Child]:
|
|
"""
|
|
Similar to getSimilarSongs, but organizes music according to ID3 tags.
|
|
|
|
:param id: The artist, album or song ID.
|
|
:param count: Max number of songs to return. Defaults to 50 according
|
|
to API Spec.
|
|
"""
|
|
result = self._get_json(self._make_url("getSimilarSongs2"), id=id, count=count,)
|
|
return result.similarSongs2.song
|
|
|
|
def get_top_songs(self, artist: str, count: int = None) -> List[Child]:
|
|
"""
|
|
Returns top songs for the given artist, using data from last.fm.
|
|
|
|
:param id: The artist name.
|
|
:param count: Max number of songs to return. Defaults to 50 according
|
|
to API Spec.
|
|
"""
|
|
result = self._get_json(
|
|
self._make_url("getTopSongs"), artist=artist, count=count,
|
|
)
|
|
return result.topSongs.song
|
|
|
|
def get_album_list(
|
|
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,
|
|
) -> AlbumList:
|
|
"""
|
|
Returns a list of random, newest, highest rated etc. albums. Similar to
|
|
the album lists on the home page of the Subsonic web interface.
|
|
|
|
:param type: The list type. Must be one of the following: ``random``,
|
|
``newest``, ``highest``, ``frequent``, ``recent``. Since 1.8.0 you
|
|
can also use ``alphabeticalByName`` or ``alphabeticalByArtist`` to
|
|
page through all albums alphabetically, and ``starred`` to retrieve
|
|
starred albums. 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._get_json(
|
|
self._make_url("getAlbumList"),
|
|
type=type,
|
|
size=size,
|
|
offset=offset,
|
|
fromYear=from_year,
|
|
toYear=to_year,
|
|
genre=genre,
|
|
musicFolderId=music_folder_id,
|
|
)
|
|
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._get_json(
|
|
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._get_json(
|
|
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._get_json(
|
|
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.
|
|
"""
|
|
return self._get_json(self._make_url("getNowPlaying")).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._get_json(
|
|
self._make_url("getStarred"), musicFolderId=music_folder_id,
|
|
)
|
|
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._get_json(
|
|
self._make_url("getStarred2"), musicFolderId=music_folder_id,
|
|
)
|
|
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._get_json(
|
|
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._get_json(
|
|
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._get_json(
|
|
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._get_json(self._make_url("getPlaylists"), username=username,)
|
|
return result.playlists
|
|
|
|
def get_playlist(self, id: int) -> PlaylistWithSongs:
|
|
"""
|
|
Returns a listing of files in a saved playlist.
|
|
|
|
:param username: ID of the playlist to return, as obtained by
|
|
``getPlaylists``.
|
|
"""
|
|
result = self._get_json(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._get_json(
|
|
self._make_url("createPlaylist"),
|
|
playlistId=playlist_id,
|
|
name=name,
|
|
songId=song_id,
|
|
)
|
|
|
|
return result.playlist or 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/these ID(s) to the
|
|
playlist. Can be a single ID or a list of IDs.
|
|
:param song_id_to_remove: Remove the song at this/these position(s) in
|
|
the playlist. Can be a single ID or a list of IDs.
|
|
"""
|
|
return self._get_json(
|
|
self._make_url("updatePlaylist"),
|
|
playlistId=playlist_id,
|
|
name=name,
|
|
comment=comment,
|
|
public=public,
|
|
songIdToAdd=song_id_to_add,
|
|
songIndexToRemove=song_index_to_remove,
|
|
)
|
|
|
|
def delete_playlist(self, id: int) -> Response:
|
|
"""Deletes a saved playlist."""
|
|
return self._get_json(self._make_url("deletePlaylist"), id=id)
|
|
|
|
def get_stream_url(
|
|
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,
|
|
) -> str:
|
|
"""
|
|
Gets the URL to stream 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.
|
|
"""
|
|
params = dict(
|
|
**self._get_params(),
|
|
id=id,
|
|
maxBitRate=max_bit_rate,
|
|
format=format,
|
|
timeOffset=time_offset,
|
|
size=size,
|
|
estimateContentLength=estimate_content_length,
|
|
converted=converted,
|
|
)
|
|
params = {k: v for k, v in params.items() if v}
|
|
return self._make_url("stream") + "?" + urlencode(params)
|
|
|
|
def download(self, id: str) -> bytes:
|
|
"""
|
|
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``.
|
|
"""
|
|
return self.do_download(self._make_url("download"), id=id)
|
|
|
|
def get_cover_art(self, id: str, size: int = 1000) -> bytes:
|
|
"""
|
|
Returns the cover art image in binary form.
|
|
|
|
:param id: The ID of a song, album or artist.
|
|
:param size: If specified, scale image to this size.
|
|
"""
|
|
return self.do_download(self._make_url("getCoverArt"), id=id, size=size,)
|
|
|
|
def get_cover_art_url(self, id: str, size: int = 1000) -> str:
|
|
"""
|
|
Returns the URL of the cover art image.
|
|
|
|
:param id: The ID of a song, album or artist.
|
|
:param size: If specified, scale image to this size.
|
|
"""
|
|
params = dict(**self._get_params(), id=id, size=size)
|
|
params = {k: v for k, v in params.items() if v}
|
|
return self._make_url("getCoverArt") + "?" + urlencode(params)
|
|
|
|
def get_lyrics(self, artist: str = None, title: str = None) -> Lyrics:
|
|
"""
|
|
Searches for and returns lyrics for a given song.
|
|
|
|
:param artist: The artist name.
|
|
:param title: The song title.
|
|
"""
|
|
result = self._get_json(
|
|
self._make_url("getLyrics"), artist=artist, title=title,
|
|
)
|
|
return result.lyrics
|
|
|
|
def get_avatar(self, username: str) -> bytes:
|
|
"""
|
|
Returns the avatar (personal image) for a user.
|
|
|
|
:param username: the user in question.
|
|
"""
|
|
return self.do_download(self._make_url("getAvatar"), username=username)
|
|
|
|
def star(
|
|
self,
|
|
id: Union[int, List[int]] = None,
|
|
album_id: Union[int, List[int]] = None,
|
|
artist_id: Union[int, List[int]] = None,
|
|
) -> Response:
|
|
"""
|
|
Attaches a star to a song, album or artist.
|
|
|
|
:param id: The ID(s) of the file(s) (song(s)) or folder(s)
|
|
(album(s)/artist(s)) to star. Can be a single ID or a list of IDs.
|
|
:param album_id: The ID(s) of an album/albums to star. Use this rather
|
|
than ``id`` if the client accesses the media collection according
|
|
to ID3 tags rather than file structure. Can be a single ID or a
|
|
list of IDs.
|
|
:param artist_id: The ID(s) of an artist/artists to star. Use this
|
|
rather than ``id`` if the client accesses the media collection
|
|
according to ID3 tags rather than file structure. Can be a single
|
|
ID or a list of IDs.
|
|
"""
|
|
return self._get_json(
|
|
self._make_url("star"), id=id, albumId=album_id, artistId=artist_id,
|
|
)
|
|
|
|
def unstar(
|
|
self,
|
|
id: Union[int, List[int]] = None,
|
|
album_id: Union[int, List[int]] = None,
|
|
artist_id: Union[int, List[int]] = None,
|
|
) -> Response:
|
|
"""
|
|
Removes the star from a song, album or artist.
|
|
|
|
:param id: The ID(s) of the file(s) (song(s)) or folder(s)
|
|
(album(s)/artist(s)) to star. Can be a single ID or a list of IDs.
|
|
:param album_id: The ID(s) of an album/albums to star. Use this rather
|
|
than ``id`` if the client accesses the media collection according
|
|
to ID3 tags rather than file structure. Can be a single ID or a
|
|
list of IDs.
|
|
:param artist_id: The ID(s) of an artist/artists to star. Use this
|
|
rather than ``id`` if the client accesses the media collection
|
|
according to ID3 tags rather than file structure. Can be a single
|
|
ID or a list of IDs.
|
|
"""
|
|
return self._get_json(
|
|
self._make_url("unstar"), id=id, albumId=album_id, artistId=artist_id,
|
|
)
|
|
|
|
def set_rating(self, id: int, rating: int) -> Response:
|
|
"""
|
|
Sets the rating for a music file.
|
|
|
|
:param id: A string which uniquely identifies the file (song) or folder
|
|
(album/artist) to rate.
|
|
:param rating: The rating between 1 and 5 (inclusive), or 0 to remove
|
|
the rating.
|
|
"""
|
|
return self._get_json(self._make_url("setRating"), id=id, rating=rating,)
|
|
|
|
def scrobble(
|
|
self, id: int, time: datetime = None, submission: bool = True,
|
|
) -> Response:
|
|
"""
|
|
Registers the local playback of one or more media files. Typically used
|
|
when playing media that is cached on the client. This operation
|
|
includes the following:
|
|
|
|
* "Scrobbles" the media files on last.fm if the user has configured
|
|
his/her last.fm credentials on the Subsonic server (Settings >
|
|
Personal).
|
|
* Updates the play count and last played timestamp for the media
|
|
files. (Since 1.11.0)
|
|
* Makes the media files appear in the "Now playing" page in the web
|
|
app, and appear in the list of songs returned by
|
|
``getNowPlaying`` (Since 1.11.0)
|
|
|
|
Since 1.8.0 you may specify multiple id (and optionally time)
|
|
parameters to scrobble multiple files.
|
|
|
|
:param id: The ID of the file to scrobble.
|
|
:param time: (Since 1.8.0) The time at which the song was listened to.
|
|
:param submission: Whether this is a "submission" or a "now playing"
|
|
notification.
|
|
"""
|
|
return self._get_json(
|
|
self._make_url("scrobble"), id=id, time=time, submission=submission,
|
|
)
|
|
|
|
def get_shares(self) -> Shares:
|
|
"""
|
|
Returns information about shared media this user is allowed to manage.
|
|
Takes no extra parameters.
|
|
"""
|
|
return self._get_json(self._make_url("getShares")).shares
|
|
|
|
def create_share(
|
|
self,
|
|
id: Union[int, List[int]],
|
|
description: str = None,
|
|
expires: datetime = None,
|
|
) -> Shares:
|
|
"""
|
|
Creates a public URL that can be used by anyone to stream music or
|
|
video from the Subsonic server. The URL is short and suitable for
|
|
posting on Facebook, Twitter etc. Note: The user must be authorized to
|
|
share (see Settings > Users > User is allowed to share files with
|
|
anyone).
|
|
|
|
:param id: ID(s) of (a) song(s), album(s) or video(s) to share. Can be
|
|
a single ID or a list of IDs.
|
|
:param description: A user-defined description that will be displayed
|
|
to people visiting the shared media.
|
|
:param expires: The time at which the share expires.
|
|
"""
|
|
result = self._get_json(
|
|
self._make_url("createShare"),
|
|
id=id,
|
|
description=description,
|
|
expires=expires,
|
|
)
|
|
return result.shares
|
|
|
|
def update_share(
|
|
self, id: int, description: str = None, expires: datetime = None,
|
|
) -> Response:
|
|
"""
|
|
Updates the description and/or expiration date for an existing share.
|
|
|
|
:param id: ID of the share to update.
|
|
:param description: A user-defined description that will be displayed
|
|
to people visiting the shared media.
|
|
:param expires: The time at which the share expires.
|
|
"""
|
|
return self._get_json(
|
|
self._make_url("updateShare"),
|
|
id=id,
|
|
description=description,
|
|
expires=expires,
|
|
)
|
|
|
|
def delete_share(self, id: int) -> Response:
|
|
"""
|
|
Deletes an existing share.
|
|
|
|
:param id: ID of the share to delete.
|
|
"""
|
|
return self._get_json(self._make_url("deleteShare"), id=id)
|
|
|
|
def get_internet_radio_stations(self) -> InternetRadioStations:
|
|
"""Returns all internet radio stations."""
|
|
result = self._get_json(self._make_url("getInternetRadioStations"))
|
|
return result.internetRadioStations
|
|
|
|
def create_internet_radio_station(
|
|
self, stream_url: str, name: str, homepage_url: str = None,
|
|
) -> Response:
|
|
"""
|
|
Adds a new internet radio station. Only users with admin privileges are
|
|
allowed to call this method.
|
|
|
|
:param stream_url: The stream URL for the station.
|
|
:param name: The user-defined name for the station.
|
|
:param homepage_url: The home page URL for the station.
|
|
"""
|
|
return self._get_json(
|
|
self._make_url("createInternetRadioStation"),
|
|
streamUrl=stream_url,
|
|
name=name,
|
|
homepageUrl=homepage_url,
|
|
)
|
|
|
|
def update_internet_radio_station(
|
|
self, id: int, stream_url: str, name: str, homepage_url: str = None,
|
|
) -> Response:
|
|
"""
|
|
Updates an existing internet radio station. Only users with admin
|
|
privileges are allowed to call this method.
|
|
|
|
:param id: The ID for the station.
|
|
:param stream_url: The stream URL for the station.
|
|
:param name: The user-defined name for the station.
|
|
:param homepage_url: The home page URL for the station.
|
|
"""
|
|
return self._get_json(
|
|
self._make_url("updateInternetRadioStation"),
|
|
id=id,
|
|
streamUrl=stream_url,
|
|
name=name,
|
|
homepageUrl=homepage_url,
|
|
)
|
|
|
|
def delete_internet_radio_station(self, id: int) -> Response:
|
|
"""
|
|
Deletes an existing internet radio station. Only users with admin
|
|
privileges are allowed to call this method.
|
|
|
|
:param id: The ID for the station.
|
|
"""
|
|
return self._get_json(self._make_url("deleteInternetRadioStation"), id=id,)
|
|
|
|
def get_user(self, username: str) -> User:
|
|
"""
|
|
Get details about a given user, including which authorization roles and
|
|
folder access it has. Can be used to enable/disable certain features in
|
|
the client, such as jukebox control.
|
|
|
|
:param username: The name of the user to retrieve. You can only
|
|
retrieve your own user unless you have admin privileges.
|
|
"""
|
|
result = self._get_json(self._make_url("getUser"), username=username)
|
|
return result.user
|
|
|
|
def get_users(self) -> Users:
|
|
"""
|
|
Get details about all users, including which authorization roles and
|
|
folder access they have. Only users with admin privileges are allowed
|
|
to call this method.
|
|
"""
|
|
return self._get_json(self._make_url("getUsers")).users
|
|
|
|
def create_user(
|
|
self,
|
|
username: str,
|
|
password: str,
|
|
email: str,
|
|
ldap_authenticated: bool = False,
|
|
admin_role: bool = False,
|
|
settings_role: bool = True,
|
|
stream_role: bool = True,
|
|
jukebox_role: bool = False,
|
|
download_role: bool = False,
|
|
upload_role: bool = False,
|
|
playlist_role: bool = False,
|
|
covert_art_role: bool = False,
|
|
comment_role: bool = False,
|
|
podcast_role: bool = False,
|
|
share_role: bool = False,
|
|
video_conversion_role: bool = False,
|
|
music_folder_id: Union[int, List[int]] = None,
|
|
) -> Response:
|
|
"""
|
|
Creates a new Subsonic user.
|
|
|
|
:param username: The name of the new user.
|
|
:param password: The password of the new user, either in clear text or
|
|
hex-encoded.
|
|
:param email: The email address of the new user.
|
|
:param ldap_authenticated: Whether the user is authenicated in LDAP.
|
|
:param admin_role: Whether the user is administrator.
|
|
:param settings_role: Whether the user is allowed to change personal
|
|
settings and password.
|
|
:param stream_role: Whether the user is allowed to play files.
|
|
:param jukebox_role: Whether the user is allowed to play files in
|
|
jukebox mode.
|
|
:param download_role: Whether the user is allowed to download files.
|
|
:param upload_role: Whether the user is allowed to upload files.
|
|
:param playlist_role: Whether the user is allowed to create and delete
|
|
playlists. Since 1.8.0, changing this role has no effect.
|
|
:param covert_art_role: Whether the user is allowed to change cover art
|
|
and tags.
|
|
:param comment_role: Whether the user is allowed to create and edit
|
|
comments and ratings.
|
|
:param podcast_role: Whether the user is allowed to administrate
|
|
Podcasts.
|
|
:param share_role: (Since 1.8.0) Whether the user is allowed to share
|
|
files with anyone.
|
|
:param video_conversion_role: (Since 1.15.0) Whether the user is
|
|
allowed to start video conversions.
|
|
:param music_folder_id: (Since 1.12.0) IDs of the music folders the
|
|
user is allowed access to. Can be a single ID or a list of IDs.
|
|
"""
|
|
return self._get_json(
|
|
self._make_url("createUser"),
|
|
username=username,
|
|
password=password,
|
|
email=email,
|
|
ldapAuthenticated=ldap_authenticated,
|
|
adminRole=admin_role,
|
|
settingsRole=settings_role,
|
|
streamRole=stream_role,
|
|
jukeboxRole=jukebox_role,
|
|
downloadRole=download_role,
|
|
uploadRole=upload_role,
|
|
playlistRole=playlist_role,
|
|
coverArtRole=covert_art_role,
|
|
commentRole=comment_role,
|
|
podcastRole=podcast_role,
|
|
shareRole=share_role,
|
|
videoConversionRole=video_conversion_role,
|
|
musicFolderId=music_folder_id,
|
|
)
|
|
|
|
def update_user(
|
|
self,
|
|
username: str,
|
|
password: str = None,
|
|
email: str = None,
|
|
ldap_authenticated: bool = False,
|
|
admin_role: bool = False,
|
|
settings_role: bool = True,
|
|
stream_role: bool = True,
|
|
jukebox_role: bool = False,
|
|
download_role: bool = False,
|
|
upload_role: bool = False,
|
|
playlist_role: bool = False,
|
|
covert_art_role: bool = False,
|
|
comment_role: bool = False,
|
|
podcast_role: bool = False,
|
|
share_role: bool = False,
|
|
video_conversion_role: bool = False,
|
|
music_folder_id: Union[int, List[int]] = None,
|
|
) -> Response:
|
|
"""
|
|
Modifies an existing Subsonic user.
|
|
|
|
:param username: The name of the user.
|
|
:param password: The password of the user, either in clear text or
|
|
hex-encoded.
|
|
:param email: The email address of the user.
|
|
:param ldap_authenticated: Whether the user is authenicated in LDAP.
|
|
:param admin_role: Whether the user is administrator.
|
|
:param settings_role: Whether the user is allowed to change personal
|
|
settings and password.
|
|
:param stream_role: Whether the user is allowed to play files.
|
|
:param jukebox_role: Whether the user is allowed to play files in
|
|
jukebox mode.
|
|
:param download_role: Whether the user is allowed to download files.
|
|
:param upload_role: Whether the user is allowed to upload files.
|
|
:param playlist_role: Whether the user is allowed to create and delete
|
|
playlists. Since 1.8.0, changing this role has no effect.
|
|
:param covert_art_role: Whether the user is allowed to change cover art
|
|
and tags.
|
|
:param comment_role: Whether the user is allowed to create and edit
|
|
comments and ratings.
|
|
:param podcast_role: Whether the user is allowed to administrate
|
|
Podcasts.
|
|
:param share_role: (Since 1.8.0) Whether the user is allowed to share
|
|
files with anyone.
|
|
:param video_conversion_role: (Since 1.15.0) Whether the user is
|
|
allowed to start video conversions.
|
|
:param music_folder_id: (Since 1.12.0) IDs of the music folders the
|
|
user is allowed access to. Can be a single ID or a list of IDs.
|
|
"""
|
|
return self._get_json(
|
|
self._make_url("updateUser"),
|
|
username=username,
|
|
password=password,
|
|
email=email,
|
|
ldapAuthenticated=ldap_authenticated,
|
|
adminRole=admin_role,
|
|
settingsRole=settings_role,
|
|
streamRole=stream_role,
|
|
jukeboxRole=jukebox_role,
|
|
downloadRole=download_role,
|
|
uploadRole=upload_role,
|
|
playlistRole=playlist_role,
|
|
coverArtRole=covert_art_role,
|
|
commentRole=comment_role,
|
|
podcastRole=podcast_role,
|
|
shareRole=share_role,
|
|
videoConversionRole=video_conversion_role,
|
|
musicFolderId=music_folder_id,
|
|
)
|
|
|
|
def delete_user(self, username: str) -> Response:
|
|
"""
|
|
Deletes an existing Subsonic user.
|
|
|
|
:param username: The name of the new user.
|
|
"""
|
|
return self._get_json(self._make_url("deleteUser"), username=username)
|
|
|
|
def change_password(self, username: str, password: str) -> Response:
|
|
"""
|
|
Changes the password of an existing Subsonic user. You can only change
|
|
your own password unless you have admin privileges.
|
|
|
|
:param username: The name of the user which should change its password.
|
|
:param password: The new password of the new user, either in clear text
|
|
of hex-encoded.
|
|
"""
|
|
return self._get_json(
|
|
self._make_url("changePassword"), username=username, password=password,
|
|
)
|
|
|
|
def get_bookmarks(self) -> Bookmarks:
|
|
"""
|
|
Returns all bookmarks for this user. A bookmark is a position within a
|
|
certain media file.
|
|
"""
|
|
return self._get_json(self._make_url("getBookmarks")).bookmarks
|
|
|
|
def create_bookmarks(
|
|
self, id: int, position: int, comment: str = None,
|
|
) -> Response:
|
|
"""
|
|
Creates or updates a bookmark (a position within a media file).
|
|
Bookmarks are personal and not visible to other users.
|
|
|
|
:param id: ID of the media file to bookmark. If a bookmark already
|
|
exists for this file it will be overwritten.
|
|
:param position: The position (in milliseconds) within the media file.
|
|
:param comment: A user-defined comment.
|
|
"""
|
|
return self._get_json(
|
|
self._make_url("createBookmark"), id=id, position=position, comment=comment,
|
|
)
|
|
|
|
def delete_bookmark(self, id: int) -> Response:
|
|
"""
|
|
Deletes the bookmark for a given file.
|
|
|
|
:param id: ID of the media file for which to delete the bookmark. Other
|
|
users' bookmarks are not affected.
|
|
"""
|
|
return self._get_json(self._make_url("deleteBookmark"), id=id)
|
|
|
|
def get_play_queue(self) -> Optional[PlayQueue]:
|
|
"""
|
|
Returns the state of the play queue for this user (as set by
|
|
``savePlayQueue``). This includes the tracks in the play queue, the
|
|
currently playing track, and the position within this track. Typically
|
|
used to allow a user to move between different clients/apps while
|
|
retaining the same play queue (for instance when listening to an audio
|
|
book).
|
|
"""
|
|
return self._get_json(self._make_url("getPlayQueue")).playQueue
|
|
|
|
def save_play_queue(
|
|
self, id: Union[int, List[int]], current: int = None, position: int = None,
|
|
) -> Response:
|
|
"""
|
|
Saves the state of the play queue for this user. This includes the
|
|
tracks in the play queue, the currently playing track, and the position
|
|
within this track. Typically used to allow a user to move between
|
|
different clients/apps while retaining the same play queue (for
|
|
instance when listening to an audio book).
|
|
|
|
:param id: ID(s) of a/the song(s) in the play queue. Can be either a
|
|
single ID or a list of IDs.
|
|
:param current: The ID of the current playing song.
|
|
:param position: The position in milliseconds within the currently
|
|
playing song.
|
|
"""
|
|
return self._get_json(
|
|
self._make_url("savePlayQueue"), id=id, current=current, position=position,
|
|
)
|
|
|
|
def get_scan_status(self) -> ScanStatus:
|
|
"""
|
|
Returns the current status for media library scanning. Takes no extra
|
|
parameters.
|
|
"""
|
|
return self._get_json(self._make_url("getScanStatus")).scanStatus
|
|
|
|
def start_scan(self) -> ScanStatus:
|
|
"""
|
|
Initiates a rescan of the media libraries. Takes no extra parameters.
|
|
"""
|
|
return self._get_json(self._make_url("startScan")).scanStatus
|