url type check, podcast methods, player device passing
This commit is contained in:
parent
cceba0bd78
commit
2e6585325b
@ -28,9 +28,10 @@ class SimplifiedEpisode:
|
|||||||
name: str
|
name: str
|
||||||
release_date: datetime
|
release_date: datetime
|
||||||
release_date_precision: str
|
release_date_precision: str
|
||||||
resume_point: ResumePoint
|
|
||||||
type: str
|
type: str
|
||||||
uri: Union[str, Uri]
|
uri: Union[str, Uri]
|
||||||
|
language: str = None # soon to be deprecated
|
||||||
|
resume_point: ResumePoint = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
|
|
||||||
@ -71,6 +72,7 @@ class SimplifiedShow:
|
|||||||
name: str
|
name: str
|
||||||
publisher: str
|
publisher: str
|
||||||
type: str
|
type: str
|
||||||
|
total_episodes: int
|
||||||
uri: Union[str, Uri]
|
uri: Union[str, Uri]
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
@ -79,15 +81,15 @@ class SimplifiedShow:
|
|||||||
self.uri = Uri(self.uri)
|
self.uri = Uri(self.uri)
|
||||||
|
|
||||||
if self.uri:
|
if self.uri:
|
||||||
if self.uri.object_type != Uri.ObjectType.episode:
|
if self.uri.object_type != Uri.ObjectType.show:
|
||||||
raise TypeError('provided uri not for an episode')
|
raise TypeError('provided uri not for an show')
|
||||||
|
|
||||||
if all((isinstance(i, dict) for i in self.images)):
|
if all((isinstance(i, dict) for i in self.images)):
|
||||||
self.images = [init_with_key_filter(Image, i) for i in self.images]
|
self.images = [init_with_key_filter(Image, i) for i in self.images]
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class EpisodeFull(SimplifiedEpisode):
|
class EpisodeFull(SimplifiedEpisode):
|
||||||
show: SimplifiedShow
|
show: SimplifiedShow = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__post_init__()
|
super().__post_init__()
|
||||||
|
@ -18,9 +18,9 @@ from spotframework.model.artist import ArtistFull
|
|||||||
from spotframework.model.album import AlbumFull, LibraryAlbum, SimplifiedAlbum
|
from spotframework.model.album import AlbumFull, LibraryAlbum, SimplifiedAlbum
|
||||||
from spotframework.model.track import SimplifiedTrack, TrackFull, PlaylistTrack, PlayedTrack, LibraryTrack, \
|
from spotframework.model.track import SimplifiedTrack, TrackFull, PlaylistTrack, PlayedTrack, LibraryTrack, \
|
||||||
AudioFeatures, Device, CurrentlyPlaying, Recommendations
|
AudioFeatures, Device, CurrentlyPlaying, Recommendations
|
||||||
from spotframework.model.podcast import SimplifiedEpisode, EpisodeFull
|
from spotframework.model.podcast import SimplifiedEpisode, EpisodeFull, SimplifiedShow, ShowFull
|
||||||
from spotframework.model.uri import Uri
|
from spotframework.model.uri import Uri
|
||||||
from spotframework.util.decorators import inject_uri
|
from spotframework.util.decorators import inject_uri, uri_type_check
|
||||||
|
|
||||||
limit = 50
|
limit = 50
|
||||||
|
|
||||||
@ -251,13 +251,13 @@ class Network:
|
|||||||
self.user.user = self.get_current_user()
|
self.user.user = self.get_current_user()
|
||||||
|
|
||||||
@inject_uri(uris=False)
|
@inject_uri(uris=False)
|
||||||
|
@uri_type_check(uri_type=Uri.ObjectType.playlist)
|
||||||
def get_playlist(self,
|
def get_playlist(self,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
tracks: bool = True) -> FullPlaylist:
|
tracks: bool = True) -> FullPlaylist:
|
||||||
"""get playlist object with tracks for uri
|
"""get playlist object with tracks for uri
|
||||||
|
|
||||||
:param uri: target request uri
|
:param uri: target request uri
|
||||||
:param uri_string: target request uri as string
|
|
||||||
:param tracks: populate tracks of playlist during generation
|
:param tracks: populate tracks of playlist during generation
|
||||||
:return: playlist object
|
:return: playlist object
|
||||||
"""
|
"""
|
||||||
@ -394,13 +394,13 @@ class Network:
|
|||||||
logger.error('no playlists returned to filter')
|
logger.error('no playlists returned to filter')
|
||||||
|
|
||||||
@inject_uri(uris=False)
|
@inject_uri(uris=False)
|
||||||
|
@uri_type_check(uri_type=Uri.ObjectType.playlist)
|
||||||
def get_playlist_tracks(self,
|
def get_playlist_tracks(self,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
response_limit: int = None) -> List[PlaylistTrack]:
|
response_limit: int = None) -> List[PlaylistTrack]:
|
||||||
"""get list of playlists tracks for uri
|
"""get list of playlists tracks for uri
|
||||||
|
|
||||||
:param uri: target playlist uri
|
:param uri: target playlist uri
|
||||||
:param uri_string: target playlist uri as string
|
|
||||||
:param response_limit: max tracks to return
|
:param response_limit: max tracks to return
|
||||||
:return: list of playlist tracks if available
|
:return: list of playlist tracks if available
|
||||||
"""
|
"""
|
||||||
@ -419,6 +419,32 @@ class Network:
|
|||||||
|
|
||||||
return return_items
|
return return_items
|
||||||
|
|
||||||
|
@inject_uri(uris=False)
|
||||||
|
@uri_type_check(uri_type=Uri.ObjectType.show)
|
||||||
|
def get_show_episodes(self,
|
||||||
|
uri: Uri,
|
||||||
|
response_limit: int = None) -> List[SimplifiedEpisode]:
|
||||||
|
"""get list of shows episodes for uri
|
||||||
|
|
||||||
|
:param uri: target show uri
|
||||||
|
:param response_limit: max episodes to return
|
||||||
|
:return: list of show episodes if available
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.info(f"paging episodes for {uri}")
|
||||||
|
|
||||||
|
pager = PageCollection(net=self, url=f'shows/{uri.object_id}/episodes', name='getShowEpisodes')
|
||||||
|
if response_limit:
|
||||||
|
pager.total_limit = response_limit
|
||||||
|
pager.iterate()
|
||||||
|
|
||||||
|
return_items = [init_with_key_filter(SimplifiedEpisode, i) for i in pager.items]
|
||||||
|
|
||||||
|
if len(return_items) == 0:
|
||||||
|
logger.error('no episodes returned')
|
||||||
|
|
||||||
|
return return_items
|
||||||
|
|
||||||
def get_available_devices(self) -> List[Device]:
|
def get_available_devices(self) -> List[Device]:
|
||||||
"""get users available devices"""
|
"""get users available devices"""
|
||||||
|
|
||||||
@ -587,6 +613,7 @@ class Network:
|
|||||||
logger.error(f"{volume} not accepted value")
|
logger.error(f"{volume} not accepted value")
|
||||||
|
|
||||||
@inject_uri
|
@inject_uri
|
||||||
|
@uri_type_check(uri_type=Uri.ObjectType.playlist, uris_type=(Uri.ObjectType.track, Uri.ObjectType.episode))
|
||||||
def replace_playlist_tracks(self,
|
def replace_playlist_tracks(self,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
uris: List[Uri]) -> Optional[List[str]]:
|
uris: List[Uri]) -> Optional[List[str]]:
|
||||||
@ -599,6 +626,7 @@ class Network:
|
|||||||
return self.add_playlist_tracks(uri=uri, uris=uris[100:])
|
return self.add_playlist_tracks(uri=uri, uris=uris[100:])
|
||||||
|
|
||||||
@inject_uri(uris=False)
|
@inject_uri(uris=False)
|
||||||
|
@uri_type_check(uri_type=Uri.ObjectType.playlist)
|
||||||
def change_playlist_details(self,
|
def change_playlist_details(self,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
name: str = None,
|
name: str = None,
|
||||||
@ -619,6 +647,7 @@ class Network:
|
|||||||
description=description)
|
description=description)
|
||||||
|
|
||||||
@inject_uri
|
@inject_uri
|
||||||
|
@uri_type_check(uri_type=Uri.ObjectType.playlist, uris_type=(Uri.ObjectType.track, Uri.ObjectType.episode))
|
||||||
def add_playlist_tracks(self, uri: Uri, uris: List[Uri]) -> List[str]:
|
def add_playlist_tracks(self, uri: Uri, uris: List[Uri]) -> List[str]:
|
||||||
|
|
||||||
logger.info(f"adding {len(uris)} tracks to {uri}")
|
logger.info(f"adding {len(uris)} tracks to {uri}")
|
||||||
@ -684,6 +713,7 @@ class Network:
|
|||||||
logger.error('playlist has no id')
|
logger.error('playlist has no id')
|
||||||
|
|
||||||
@inject_uri(uris=False)
|
@inject_uri(uris=False)
|
||||||
|
@uri_type_check(uri_type=Uri.ObjectType.playlist)
|
||||||
def reorder_playlist_tracks(self,
|
def reorder_playlist_tracks(self,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
range_start: int,
|
range_start: int,
|
||||||
@ -708,6 +738,7 @@ class Network:
|
|||||||
insert_before=insert_before)
|
insert_before=insert_before)
|
||||||
|
|
||||||
@inject_uri(uri=False)
|
@inject_uri(uri=False)
|
||||||
|
@uri_type_check(uris_type=Uri.ObjectType.track)
|
||||||
def get_track_audio_features(self, uris: List[Uri]) -> Optional[List[AudioFeatures]]:
|
def get_track_audio_features(self, uris: List[Uri]) -> Optional[List[AudioFeatures]]:
|
||||||
logger.info(f'getting {len(uris)} features')
|
logger.info(f'getting {len(uris)} features')
|
||||||
|
|
||||||
@ -759,13 +790,11 @@ class Network:
|
|||||||
raise TypeError('must provide either single or list of spotify tracks')
|
raise TypeError('must provide either single or list of spotify tracks')
|
||||||
|
|
||||||
@inject_uri(uri=False)
|
@inject_uri(uri=False)
|
||||||
|
@uri_type_check(uris_type=Uri.ObjectType.track)
|
||||||
def get_tracks(self, uris: List[Uri]) -> List[TrackFull]:
|
def get_tracks(self, uris: List[Uri]) -> List[TrackFull]:
|
||||||
|
|
||||||
logger.info(f'getting {len(uris)} tracks')
|
logger.info(f'getting {len(uris)} tracks')
|
||||||
|
|
||||||
if not all(i.object_type == Uri.ObjectType.track for i in uris):
|
|
||||||
raise TypeError('uris must be of type track')
|
|
||||||
|
|
||||||
tracks = []
|
tracks = []
|
||||||
chunked_uris = list(self.chunk(uris, 50))
|
chunked_uris = list(self.chunk(uris, 50))
|
||||||
for chunk in chunked_uris:
|
for chunk in chunked_uris:
|
||||||
@ -776,6 +805,7 @@ class Network:
|
|||||||
return tracks
|
return tracks
|
||||||
|
|
||||||
@inject_uri(uris=False)
|
@inject_uri(uris=False)
|
||||||
|
@uri_type_check(uri_type=Uri.ObjectType.track)
|
||||||
def get_track(self, uri) -> Optional[TrackFull]:
|
def get_track(self, uri) -> Optional[TrackFull]:
|
||||||
|
|
||||||
track = self.get_tracks(uris=[uri])
|
track = self.get_tracks(uris=[uri])
|
||||||
@ -785,13 +815,11 @@ class Network:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@inject_uri(uri=False)
|
@inject_uri(uri=False)
|
||||||
|
@uri_type_check(uris_type=Uri.ObjectType.album)
|
||||||
def get_albums(self, uris: List[Uri]) -> List[AlbumFull]:
|
def get_albums(self, uris: List[Uri]) -> List[AlbumFull]:
|
||||||
|
|
||||||
logger.info(f'getting {len(uris)} albums')
|
logger.info(f'getting {len(uris)} albums')
|
||||||
|
|
||||||
if not all(i.object_type == Uri.ObjectType.album for i in uris):
|
|
||||||
raise TypeError('uris must be of type album')
|
|
||||||
|
|
||||||
albums = []
|
albums = []
|
||||||
chunked_uris = list(self.chunk(uris, 50))
|
chunked_uris = list(self.chunk(uris, 50))
|
||||||
for chunk in chunked_uris:
|
for chunk in chunked_uris:
|
||||||
@ -802,6 +830,7 @@ class Network:
|
|||||||
return albums
|
return albums
|
||||||
|
|
||||||
@inject_uri(uris=False)
|
@inject_uri(uris=False)
|
||||||
|
@uri_type_check(uri_type=Uri.ObjectType.album)
|
||||||
def get_album(self, uri: Uri) -> Optional[AlbumFull]:
|
def get_album(self, uri: Uri) -> Optional[AlbumFull]:
|
||||||
|
|
||||||
album = self.get_albums(uris=[uri])
|
album = self.get_albums(uris=[uri])
|
||||||
@ -811,13 +840,11 @@ class Network:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@inject_uri(uri=False)
|
@inject_uri(uri=False)
|
||||||
|
@uri_type_check(uris_type=Uri.ObjectType.artist)
|
||||||
def get_artists(self, uris) -> List[ArtistFull]:
|
def get_artists(self, uris) -> List[ArtistFull]:
|
||||||
|
|
||||||
logger.info(f'getting {len(uris)} artists')
|
logger.info(f'getting {len(uris)} artists')
|
||||||
|
|
||||||
if not all(i.object_type == Uri.ObjectType.artist for i in uris):
|
|
||||||
raise TypeError('uris must be of type artist')
|
|
||||||
|
|
||||||
artists = []
|
artists = []
|
||||||
chunked_uris = list(self.chunk(uris, 50))
|
chunked_uris = list(self.chunk(uris, 50))
|
||||||
for chunk in chunked_uris:
|
for chunk in chunked_uris:
|
||||||
@ -828,6 +855,7 @@ class Network:
|
|||||||
return artists
|
return artists
|
||||||
|
|
||||||
@inject_uri(uris=False)
|
@inject_uri(uris=False)
|
||||||
|
@uri_type_check(uri_type=Uri.ObjectType.artist)
|
||||||
def get_artist(self, uri) -> Optional[ArtistFull]:
|
def get_artist(self, uri) -> Optional[ArtistFull]:
|
||||||
|
|
||||||
artist = self.get_artists(uris=[uri])
|
artist = self.get_artists(uris=[uri])
|
||||||
@ -836,6 +864,67 @@ class Network:
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@inject_uri(uri=False)
|
||||||
|
@uri_type_check(uris_type=Uri.ObjectType.show)
|
||||||
|
def get_shows(self, uris) -> List[SimplifiedShow]:
|
||||||
|
|
||||||
|
logger.info(f'getting {len(uris)} shows')
|
||||||
|
|
||||||
|
shows = []
|
||||||
|
chunked_uris = list(self.chunk(uris, 50))
|
||||||
|
for chunk in chunked_uris:
|
||||||
|
resp = self.get_request(url='shows', ids=','.join([i.object_id for i in chunk]))
|
||||||
|
if resp:
|
||||||
|
shows += [init_with_key_filter(SimplifiedShow, i) for i in resp.get('shows', [])]
|
||||||
|
|
||||||
|
return shows
|
||||||
|
|
||||||
|
@inject_uri(uris=False)
|
||||||
|
@uri_type_check(uri_type=Uri.ObjectType.show)
|
||||||
|
def get_show(self, uri, episodes: bool = True) -> Optional[ShowFull]:
|
||||||
|
|
||||||
|
logger.info(f"retrieving {uri}")
|
||||||
|
|
||||||
|
resp = self.get_request(f'shows/{uri.object_id}')
|
||||||
|
show = init_with_key_filter(ShowFull, resp)
|
||||||
|
|
||||||
|
if resp.get('episodes') and episodes:
|
||||||
|
if 'next' in resp['episodes']:
|
||||||
|
logger.debug(f'paging episodes for {uri}')
|
||||||
|
|
||||||
|
track_pager = PageCollection(net=self, page=resp['episodes'])
|
||||||
|
track_pager.continue_iteration()
|
||||||
|
|
||||||
|
show.episodes = [init_with_key_filter(SimplifiedEpisode, i) for i in track_pager.items]
|
||||||
|
else:
|
||||||
|
logger.debug(f'parsing {len(resp.get("episodes"))} tracks for {uri}')
|
||||||
|
show.episodes = [init_with_key_filter(SimplifiedEpisode, i) for i in resp.get('episodes', [])]
|
||||||
|
return show
|
||||||
|
|
||||||
|
@inject_uri(uri=False)
|
||||||
|
@uri_type_check(uris_type=Uri.ObjectType.episode)
|
||||||
|
def get_episodes(self, uris) -> List[EpisodeFull]:
|
||||||
|
|
||||||
|
logger.info(f'getting {len(uris)} episodes')
|
||||||
|
|
||||||
|
episodes = []
|
||||||
|
chunked_uris = list(self.chunk(uris, 50))
|
||||||
|
for chunk in chunked_uris:
|
||||||
|
resp = self.get_request(url='episodes', ids=','.join([i.object_id for i in chunk]))
|
||||||
|
if resp:
|
||||||
|
episodes += [init_with_key_filter(EpisodeFull, i) for i in resp.get('episodes', [])]
|
||||||
|
|
||||||
|
return episodes
|
||||||
|
|
||||||
|
@inject_uri(uris=False)
|
||||||
|
@uri_type_check(uri_type=Uri.ObjectType.episode)
|
||||||
|
def get_episode(self, uri) -> EpisodeFull:
|
||||||
|
|
||||||
|
logger.info(f"retrieving {uri}")
|
||||||
|
|
||||||
|
resp = self.get_request(f'episodes/{uri.object_id}')
|
||||||
|
return init_with_key_filter(EpisodeFull, resp)
|
||||||
|
|
||||||
def search(self,
|
def search(self,
|
||||||
query_types: List[Uri.ObjectType],
|
query_types: List[Uri.ObjectType],
|
||||||
query: str = None,
|
query: str = None,
|
||||||
|
@ -1,13 +1,46 @@
|
|||||||
|
import functools
|
||||||
|
from typing import List, Union
|
||||||
|
import logging
|
||||||
|
|
||||||
from spotframework.net.network import Network, SpotifyNetworkException
|
from spotframework.net.network import Network, SpotifyNetworkException
|
||||||
from spotframework.model.track import SimplifiedTrack, Context, Device
|
from spotframework.model.track import SimplifiedTrack, Context, Device
|
||||||
from spotframework.model.album import AlbumFull
|
from spotframework.model.album import AlbumFull
|
||||||
from spotframework.model.playlist import FullPlaylist
|
from spotframework.model.playlist import FullPlaylist
|
||||||
from typing import List, Union
|
|
||||||
import logging
|
from spotframework.util.decorators import inject_uri
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Player:
|
class Player:
|
||||||
|
class Decorators:
|
||||||
|
@staticmethod
|
||||||
|
def inject_device(_func=None):
|
||||||
|
def decorator_inject_device(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
def inject_device_wrapper(self, *args, **kwargs):
|
||||||
|
|
||||||
|
if kwargs.get('device_name'):
|
||||||
|
kwargs['device'] = self.get_device(kwargs['device_name'], 'name')
|
||||||
|
|
||||||
|
del kwargs['device_name']
|
||||||
|
|
||||||
|
elif kwargs.get('device_id'):
|
||||||
|
kwargs['device'] = self.get_device(kwargs['device_id'], 'id')
|
||||||
|
|
||||||
|
del kwargs['device_id']
|
||||||
|
|
||||||
|
elif kwargs.get('device'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
|
||||||
|
return inject_device_wrapper
|
||||||
|
|
||||||
|
if _func is None:
|
||||||
|
return decorator_inject_device
|
||||||
|
else:
|
||||||
|
return decorator_inject_device(_func)
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
net: Network):
|
net: Network):
|
||||||
@ -24,8 +57,9 @@ class Player:
|
|||||||
def available_devices(self):
|
def available_devices(self):
|
||||||
try:
|
try:
|
||||||
return self.net.get_available_devices()
|
return self.net.get_available_devices()
|
||||||
except SpotifyNetworkException:
|
except SpotifyNetworkException as e:
|
||||||
logger.exception(f'error retrieving current devices')
|
logger.exception(f'error retrieving current devices')
|
||||||
|
raise e
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status(self):
|
def status(self):
|
||||||
@ -37,17 +71,13 @@ class Player:
|
|||||||
except SpotifyNetworkException:
|
except SpotifyNetworkException:
|
||||||
logger.exception(f'error retrieving current devices')
|
logger.exception(f'error retrieving current devices')
|
||||||
|
|
||||||
|
@inject_uri(flatten_to_uris=True, uri_optional=True, uris_optional=True)
|
||||||
|
@Decorators.inject_device
|
||||||
def play(self,
|
def play(self,
|
||||||
context: Union[Context, AlbumFull, FullPlaylist] = None,
|
context: Union[Context, AlbumFull, FullPlaylist] = None,
|
||||||
tracks: List[SimplifiedTrack] = None,
|
tracks: List[SimplifiedTrack] = None,
|
||||||
uris: List = None,
|
uris: List = None,
|
||||||
device: Device = None,
|
device: Device = None):
|
||||||
device_name: str = None):
|
|
||||||
if device_name:
|
|
||||||
searched_device = next((i for i in self.available_devices if i.name == device_name), None)
|
|
||||||
if searched_device:
|
|
||||||
device = searched_device
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if context and (tracks or uris):
|
if context and (tracks or uris):
|
||||||
raise Exception('cant execute context and track list')
|
raise Exception('cant execute context and track list')
|
||||||
@ -73,65 +103,86 @@ class Player:
|
|||||||
except SpotifyNetworkException:
|
except SpotifyNetworkException:
|
||||||
logger.exception(f'error playing')
|
logger.exception(f'error playing')
|
||||||
|
|
||||||
|
@Decorators.inject_device
|
||||||
def change_device(self, device: Device):
|
def change_device(self, device: Device):
|
||||||
try:
|
try:
|
||||||
self.net.change_playback_device(device.id)
|
self.net.change_playback_device(device.id)
|
||||||
except SpotifyNetworkException:
|
except SpotifyNetworkException:
|
||||||
logger.exception(f'error changing device to {device.name}')
|
logger.exception(f'error changing device to {device.name}')
|
||||||
|
|
||||||
def pause(self):
|
@Decorators.inject_device
|
||||||
|
def pause(self, device = None):
|
||||||
try:
|
try:
|
||||||
self.net.pause()
|
if device is not None:
|
||||||
|
self.net.pause(deviceid=device.id)
|
||||||
|
else:
|
||||||
|
self.net.pause()
|
||||||
|
|
||||||
except SpotifyNetworkException:
|
except SpotifyNetworkException:
|
||||||
logger.exception(f'error pausing')
|
logger.exception(f'error pausing')
|
||||||
|
|
||||||
def toggle_playback(self):
|
@Decorators.inject_device
|
||||||
|
def toggle_playback(self, device: Device = None):
|
||||||
status = self.status
|
status = self.status
|
||||||
try:
|
try:
|
||||||
if status:
|
if status:
|
||||||
if status.is_playing:
|
if status.is_playing:
|
||||||
self.pause()
|
self.pause(device=device)
|
||||||
else:
|
else:
|
||||||
self.play()
|
self.play(device=device)
|
||||||
else:
|
else:
|
||||||
logger.warning('no current playback, playing')
|
logger.warning('no current playback, playing')
|
||||||
self.play()
|
self.play(device=device)
|
||||||
except SpotifyNetworkException:
|
except SpotifyNetworkException:
|
||||||
logger.exception(f'error toggling playback')
|
logger.exception(f'error toggling playback')
|
||||||
|
|
||||||
def next(self):
|
@Decorators.inject_device
|
||||||
|
def next(self, device: Device = None):
|
||||||
try:
|
try:
|
||||||
self.net.next()
|
if device is not None:
|
||||||
|
self.net.next(deviceid=device.id)
|
||||||
|
else:
|
||||||
|
self.net.next()
|
||||||
|
|
||||||
except SpotifyNetworkException:
|
except SpotifyNetworkException:
|
||||||
logger.exception(f'error skipping track')
|
logger.exception(f'error skipping track')
|
||||||
|
|
||||||
def previous(self):
|
@Decorators.inject_device
|
||||||
|
def previous(self, device: Device = None):
|
||||||
try:
|
try:
|
||||||
self.net.previous()
|
if device is not None:
|
||||||
|
self.net.previous(deviceid=device.id)
|
||||||
|
else:
|
||||||
|
self.net.previous()
|
||||||
|
|
||||||
except SpotifyNetworkException:
|
except SpotifyNetworkException:
|
||||||
logger.exception(f'error reversing track')
|
logger.exception(f'error reversing track')
|
||||||
|
|
||||||
def shuffle(self, state=None):
|
@Decorators.inject_device
|
||||||
|
def shuffle(self, device: Device = None, state=None):
|
||||||
if state is not None:
|
if state is not None:
|
||||||
if isinstance(state, bool):
|
if isinstance(state, bool):
|
||||||
try:
|
try:
|
||||||
self.net.set_shuffle(state)
|
if device is not None:
|
||||||
|
self.net.set_shuffle(deviceid=device.id, state=state)
|
||||||
|
else:
|
||||||
|
self.net.set_shuffle(state=state)
|
||||||
|
|
||||||
except SpotifyNetworkException:
|
except SpotifyNetworkException:
|
||||||
logger.exception(f'error setting shuffle')
|
logger.exception(f'error setting shuffle')
|
||||||
else:
|
else:
|
||||||
raise TypeError(f'{state} is not bool')
|
raise TypeError(f'{state} is not bool')
|
||||||
else:
|
|
||||||
status = self.status
|
|
||||||
if status.shuffle_state:
|
|
||||||
self.shuffle(state=False)
|
|
||||||
else:
|
|
||||||
self.shuffle(state=True)
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.shuffle(device=device, state=not self.status.shuffle_state)
|
||||||
|
|
||||||
|
|
||||||
|
@Decorators.inject_device
|
||||||
def volume(self, value: int, device: Device = None):
|
def volume(self, value: int, device: Device = None):
|
||||||
|
|
||||||
if 0 <= int(value) <= 100:
|
if 0 <= int(value) <= 100:
|
||||||
try:
|
try:
|
||||||
if device:
|
if device is not None:
|
||||||
self.net.set_volume(value, deviceid=device.id)
|
self.net.set_volume(value, deviceid=device.id)
|
||||||
else:
|
else:
|
||||||
self.net.set_volume(value)
|
self.net.set_volume(value)
|
||||||
@ -139,3 +190,20 @@ class Player:
|
|||||||
logger.exception(f'error setting volume to {value}')
|
logger.exception(f'error setting volume to {value}')
|
||||||
else:
|
else:
|
||||||
logger.error(f'{value} not between 0 and 100')
|
logger.error(f'{value} not between 0 and 100')
|
||||||
|
|
||||||
|
def get_device(self, device_in, attr):
|
||||||
|
|
||||||
|
if isinstance(device_in, str):
|
||||||
|
try:
|
||||||
|
searched_device = next((i for i in self.available_devices if getattr(i, attr) == device_in), None)
|
||||||
|
if searched_device is not None:
|
||||||
|
return searched_device
|
||||||
|
else:
|
||||||
|
logger.error(f'device not returned for {device_in}, {attr}')
|
||||||
|
|
||||||
|
except SpotifyNetworkException:
|
||||||
|
logger.exception(f'error retrieving current devices')
|
||||||
|
|
||||||
|
elif isinstance(device_in, Device): return device_in
|
||||||
|
|
||||||
|
else: raise TypeError(f'invalid uri type provided - {type(device_in)}')
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from collections.abc import Iterable
|
||||||
|
|
||||||
from spotframework.util import get_uri
|
from spotframework.util import get_uri
|
||||||
|
from spotframework.model.uri import Uri
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -18,16 +21,23 @@ def debug(func):
|
|||||||
return value
|
return value
|
||||||
return wrapper_debug
|
return wrapper_debug
|
||||||
|
|
||||||
def inject_uri(_func=None, *, uri=True, uris=True, uri_optional=False, uris_optional=False):
|
def inject_uri(_func=None, *, uri=True, uris=True, flatten_to_uris=False, uri_optional=False, uris_optional=False):
|
||||||
|
|
||||||
def decorator_inject_uri(func):
|
def decorator_inject_uri(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def inject_uri_wrapper(*args, **kwargs):
|
def inject_uri_wrapper(*args, **kwargs):
|
||||||
|
|
||||||
if uri:
|
if uri:
|
||||||
if uri_optional:
|
if uri_optional:
|
||||||
kwargs['uri'] = get_uri(kwargs['uri']) if kwargs.get('uri') else None
|
if flatten_to_uris:
|
||||||
|
kwargs['uris'] = [ get_uri(kwargs['uri']) ] if kwargs.get('uri') else None
|
||||||
|
else:
|
||||||
|
kwargs['uri'] = get_uri(kwargs['uri']) if kwargs.get('uri') else None
|
||||||
else:
|
else:
|
||||||
kwargs['uri'] = get_uri(kwargs['uri'])
|
if flatten_to_uris:
|
||||||
|
kwargs['uris'] = [ get_uri(kwargs['uri']) ]
|
||||||
|
else:
|
||||||
|
kwargs['uri'] = get_uri(kwargs['uri'])
|
||||||
|
|
||||||
if uris:
|
if uris:
|
||||||
if uris_optional:
|
if uris_optional:
|
||||||
@ -44,3 +54,35 @@ def inject_uri(_func=None, *, uri=True, uris=True, uri_optional=False, uris_opti
|
|||||||
else:
|
else:
|
||||||
return decorator_inject_uri(_func)
|
return decorator_inject_uri(_func)
|
||||||
|
|
||||||
|
def contain_uri_types(types_in):
|
||||||
|
|
||||||
|
if isinstance(types_in, Iterable):
|
||||||
|
for uri_type in types_in:
|
||||||
|
if not isinstance(uri_type, Uri.ObjectType):
|
||||||
|
raise TypeError(f'provided type not a uri object type, {type(uri_type)}')
|
||||||
|
types = types_in
|
||||||
|
|
||||||
|
elif isinstance(types_in, Uri.ObjectType):
|
||||||
|
types = [types_in]
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise TypeError(f'provided type not a uri object type, {type(types_in)}')
|
||||||
|
|
||||||
|
return types
|
||||||
|
|
||||||
|
def uri_type_check(uri_type = None, uris_type = None):
|
||||||
|
def decorator_type_check(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper_type_check(*args, **kwargs):
|
||||||
|
if uri_type is not None:
|
||||||
|
if kwargs['uri'].object_type not in contain_uri_types(uri_type):
|
||||||
|
raise TypeError(f'uri not of required type {uri_type}, {kwargs["uri"].object_type}')
|
||||||
|
|
||||||
|
if uris_type is not None:
|
||||||
|
for uri in kwargs['uris']:
|
||||||
|
if uri.object_type not in contain_uri_types(uris_type):
|
||||||
|
raise TypeError(f'uri not of required type {uris_type}, {uri.object_type}')
|
||||||
|
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper_type_check
|
||||||
|
return decorator_type_check
|
||||||
|
Loading…
Reference in New Issue
Block a user