added player and service objects

This commit is contained in:
aj 2019-09-15 03:32:24 +01:00
parent 92217ad3a4
commit 6076ecd610
13 changed files with 279 additions and 28 deletions

View File

@ -43,7 +43,6 @@ if __name__ == '__main__':
network = Network(NetworkUser(os.environ['SPOTCLIENT'], network = Network(NetworkUser(os.environ['SPOTCLIENT'],
os.environ['SPOTSECRET'], os.environ['SPOTSECRET'],
os.environ['SPOTACCESS'],
os.environ['SPOTREFRESH'])) os.environ['SPOTREFRESH']))
found = False found = False

View File

@ -31,7 +31,6 @@ if __name__ == '__main__':
network = Network(NetworkUser(os.environ['SPOTCLIENT'], network = Network(NetworkUser(os.environ['SPOTCLIENT'],
os.environ['SPOTSECRET'], os.environ['SPOTSECRET'],
os.environ['SPOTACCESS'],
os.environ['SPOTREFRESH'])) os.environ['SPOTREFRESH']))
playlists = network.get_user_playlists() playlists = network.get_user_playlists()

View File

@ -124,7 +124,6 @@ def go():
net = Network(NetworkUser(os.environ['SPOTCLIENT'], net = Network(NetworkUser(os.environ['SPOTCLIENT'],
os.environ['SPOTSECRET'], os.environ['SPOTSECRET'],
os.environ['SPOTACCESS'],
os.environ['SPOTREFRESH'])) os.environ['SPOTREFRESH']))
engine = PlaylistEngine(net) engine = PlaylistEngine(net)

View File

@ -17,7 +17,6 @@ if __name__ == '__main__':
network = Network(NetworkUser(os.environ['SPOTCLIENT'], network = Network(NetworkUser(os.environ['SPOTCLIENT'],
os.environ['SPOTSECRET'], os.environ['SPOTSECRET'],
os.environ['SPOTACCESS'],
os.environ['SPOTREFRESH'])) os.environ['SPOTREFRESH']))
print(network.user.access_token) print(network.user.access_token)

View File

@ -30,7 +30,6 @@ def go(playlist_name):
net = Network(NetworkUser(os.environ['SPOTCLIENT'], net = Network(NetworkUser(os.environ['SPOTCLIENT'],
os.environ['SPOTSECRET'], os.environ['SPOTSECRET'],
os.environ['SPOTACCESS'],
os.environ['SPOTREFRESH'])) os.environ['SPOTREFRESH']))
engine = PlaylistEngine(net) engine = PlaylistEngine(net)

View File

@ -26,13 +26,7 @@ class BatchSingleProcessor(AbstractProcessor, ABC):
return track return track
def process_batch(self, tracks: List[Track]) -> List[Track]: def process_batch(self, tracks: List[Track]) -> List[Track]:
processed = [] return [self.process_single(track) for track in tracks]
for track in tracks:
processed_track = self.process_single(track)
processed.append(processed_track)
return processed
def process(self, tracks: List[Track]) -> List[Track]: def process(self, tracks: List[Track]) -> List[Track]:
return [i for i in self.process_batch(tracks) if i is not None] return [i for i in self.process_batch(tracks) if i is not None]

View File

@ -26,6 +26,12 @@ class Playlist:
def __len__(self): def __len__(self):
return len(self.tracks) return len(self.tracks)
def __getitem__(self, item) -> Track:
return self.tracks[item]
def __iter__(self):
return iter(self.tracks)
@property @property
def tracks(self) -> List[Track]: def tracks(self) -> List[Track]:
return self._tracks return self._tracks

View File

@ -0,0 +1,91 @@
from datetime import datetime
from spotframework.model.track import Track
from enum import Enum
class Context:
def __init__(self,
uri: str,
object_type: str = None,
href: str = None,
external_spot: str = None):
self.uri = uri
self.object_type = object_type
self.href = href
self.external_spot = external_spot
def __repr__(self):
return f'Context: {self.object_type} uri({self.uri})'
def __str__(self):
return f'{self.object_type} / {self.uri}'
class Device:
class DeviceType(Enum):
COMPUTER = 1
TABLET = 2
SMARTPHONE = 3
SPEAKER = 4
TV = 5
AVR = 6
STB = 7
AUDIODONGLE = 8
GAMECONSOLE = 9
CASTVIDEO = 10
CASTAUDIO = 11
AUTOMOBILE = 12
UNKNOWN = 13
def __init__(self,
device_id: str,
is_active: bool,
is_private_session: bool,
is_restricted: bool,
name: str,
object_type: DeviceType,
volume: int):
self.device_id = device_id
self.is_active = is_active
self.is_private_session = is_private_session
self.is_restricted = is_restricted
self.name = name
self.object_type = object_type
self.volume = volume
def __repr__(self):
return f'Device: {self.name} active({self.is_active}) type({self.object_type}) vol({self.volume})'
def __str__(self):
return self.name
class CurrentlyPlaying:
def __init__(self,
context: Context,
timestamp: datetime,
progress_ms: int,
is_playing: bool,
track: Track,
device: Device,
shuffle: bool,
repeat: bool,
currently_playing_type: str):
self.context = context
self.timestamp = timestamp
self.progress_ms = progress_ms
self.is_playing = is_playing
self.track = track
self.device = device
self.shuffle = shuffle
self.repeat = repeat
self.currently_playing_type = currently_playing_type
def __repr__(self):
return f'CurrentlyPlaying: is_playing({self.is_playing}) progress({self.progress_ms}) ' \
f'context({self.context}) track({self.track}) device({self.device}) shuffle({self.shuffle}) ' \
f'repeat({self.repeat}) time({self.timestamp})'
def __str__(self):
return f'playing: {self.is_playing} - {self.track} on {self.device}'

View File

@ -8,6 +8,7 @@ from spotframework.net.parse import parse
from spotframework.net.user import NetworkUser from spotframework.net.user import NetworkUser
from spotframework.model.playlist import SpotifyPlaylist from spotframework.model.playlist import SpotifyPlaylist
from spotframework.model.track import Track, PlaylistTrack from spotframework.model.track import Track, PlaylistTrack
from spotframework.model.service import CurrentlyPlaying, Device
from requests.models import Response from requests.models import Response
limit = 50 limit = 50
@ -28,7 +29,11 @@ class Network:
if 200 <= req.status_code < 300: if 200 <= req.status_code < 300:
logger.debug(f'{method} get {req.status_code}') logger.debug(f'{method} get {req.status_code}')
return req.json()
if req.status_code != 204:
return req.json()
else:
return None
else: else:
if req.status_code == 429: if req.status_code == 429:
@ -215,37 +220,55 @@ class Network:
return tracks return tracks
def get_available_devices(self) -> Optional[dict]: def get_available_devices(self) -> Optional[List[Device]]:
logger.info("retrieving") logger.info("retrieving")
resp = self._make_get_request('getAvailableDevices', 'me/player/devices') resp = self._make_get_request('getAvailableDevices', 'me/player/devices')
if resp: if resp:
return resp return [parse.parse_device(i) for i in resp['devices']]
else: else:
logger.error('no devices returned') logger.error('no devices returned')
return None return None
def get_player(self) -> Optional[dict]: def get_player(self) -> Optional[CurrentlyPlaying]:
logger.info("retrieved") logger.info("retrieved")
resp = self._make_get_request('getPlayer', 'me/player') resp = self._make_get_request('getPlayer', 'me/player')
if resp: if resp:
return resp return parse.parse_currently_playing(resp)
else: else:
logger.error('no player returned') logger.info('no player returned')
return None return None
def get_device_id(self, devicename) -> Optional[str]: def get_device_id(self, devicename) -> Optional[str]:
logger.info(f"{devicename}") logger.info(f"{devicename}")
resp = self.get_available_devices() devices = self.get_available_devices()
if resp: if devices:
return next((i for i in resp['devices'] if i['name'] == devicename), None)['id'] device = next((i for i in devices if i.name == devicename), None)
if device:
return device.device_id
else:
logger.error(f'{devicename} not found')
else: else:
logger.error('no devices returned') logger.error('no devices returned')
def change_playback_device(self, device_id):
logger.info(device_id)
json = {
'device_ids': [device_id],
'play': True
}
resp = self._make_put_request('changePlaybackDevice', 'me/player', json=json)
if resp:
return True
else:
return None return None
def play(self, uri=None, uris=None, deviceid=None) -> Optional[Response]: def play(self, uri=None, uris=None, deviceid=None) -> Optional[Response]:
@ -260,14 +283,12 @@ class Network:
if uri and uris: if uri and uris:
raise Exception('wont take both context uri and uris') raise Exception('wont take both context uri and uris')
payload = dict()
if uri: if uri:
payload = {'context_uri': uri} payload['context_uri'] = uri
if uris: if uris:
payload = {'uris': uris[:200]} payload['uris'] = uris[:200]
if not uri and not uris:
raise Exception('need either context uri or uris')
req = self._make_put_request('play', 'me/player/play', params=params, json=payload) req = self._make_put_request('play', 'me/player/play', params=params, json=payload)
if req: if req:
@ -305,6 +326,21 @@ class Network:
else: else:
logger.error('error skipping') logger.error('error skipping')
def previous(self, deviceid=None) -> Optional[Response]:
logger.info(f"{deviceid if deviceid is not None else ''}")
if deviceid is not None:
params = {'device_id': deviceid}
else:
params = None
req = self._make_post_request('previous', 'me/player/previous', params=params)
if req:
return req
else:
logger.error('error reversing')
def set_shuffle(self, state, deviceid=None) -> Optional[Response]: def set_shuffle(self, state, deviceid=None) -> Optional[Response]:
logger.info(f"{state}{' ' + deviceid if deviceid is not None else ''}") logger.info(f"{state}{' ' + deviceid if deviceid is not None else ''}")

View File

@ -3,6 +3,8 @@ from spotframework.model.album import SpotifyAlbum
from spotframework.model.track import Track, SpotifyTrack, PlaylistTrack from spotframework.model.track import Track, SpotifyTrack, PlaylistTrack
from spotframework.model.playlist import SpotifyPlaylist from spotframework.model.playlist import SpotifyPlaylist
from spotframework.model.user import User from spotframework.model.user import User
from spotframework.model.service import Context, CurrentlyPlaying, Device
import datetime
def parse_artist(artist_dict) -> SpotifyArtist: def parse_artist(artist_dict) -> SpotifyArtist:
@ -184,3 +186,33 @@ def parse_playlist(playlist_dict) -> SpotifyPlaylist:
collaborative=collaborative, collaborative=collaborative,
public=public, public=public,
ext_spotify=ext_spotify) ext_spotify=ext_spotify)
def parse_context(context_dict) -> Context:
return Context(object_type=context_dict['type'],
href=context_dict['href'],
external_spot=context_dict['external_urls']['spotify'],
uri=context_dict['uri'])
def parse_currently_playing(play_dict) -> CurrentlyPlaying:
return CurrentlyPlaying(context=parse_context(play_dict['context']) if play_dict['context'] is not None else None,
timestamp=datetime.datetime.fromtimestamp(play_dict['timestamp']/1000),
progress_ms=play_dict['progress_ms'],
is_playing=play_dict['is_playing'],
track=parse_track(play_dict['item']),
device=parse_device(play_dict['device']),
shuffle=play_dict['shuffle_state'],
repeat=play_dict['repeat_state'],
currently_playing_type=play_dict['currently_playing_type'])
def parse_device(device_dict) -> Device:
return Device(device_id=device_dict['id'],
is_active=device_dict['is_active'],
is_private_session=device_dict['is_private_session'],
is_restricted=device_dict['is_restricted'],
name=device_dict['name'],
object_type=Device.DeviceType[device_dict['type'].upper()],
volume=device_dict['volume_percent'])

View File

@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
class NetworkUser(User): class NetworkUser(User):
def __init__(self, client_id, client_secret, access_token, refresh_token): def __init__(self, client_id, client_secret, refresh_token, access_token=None):
super().__init__('') super().__init__('')
self.accesstoken = access_token self.accesstoken = access_token

View File

View File

@ -0,0 +1,97 @@
from spotframework.net.network import Network
from spotframework.model.track import SpotifyTrack
from spotframework.model.service import Context, Device
from typing import List
import logging
logger = logging.getLogger(__name__)
class Player:
def __init__(self,
net: Network):
self.net = net
self.user = net.user
self.last_status = None
def __str__(self):
return f'{self.user.username} - {self.status}'
def __repr__(self):
return f'Player: {self.user} - {self.status}'
@property
def available_devices(self):
return self.net.get_available_devices()
@property
def status(self):
new_status = self.net.get_player()
if new_status:
self.last_status = new_status
return self.last_status
def play(self,
context: Context = None,
tracks: List[SpotifyTrack] = None,
device: Device = None):
if context and tracks:
raise Exception('cant execute context and track list')
if context:
if device:
self.net.play(uri=context.uri, deviceid=device.device_id)
else:
self.net.play(uri=context.uri)
elif tracks:
if device:
self.net.play(uris=[i.uri for i in tracks], deviceid=device.device_id)
else:
self.net.play(uris=[i.uri for i in tracks])
else:
self.net.play()
def change_device(self, device: Device):
self.net.change_playback_device(device.device_id)
def pause(self):
self.net.pause()
def toggle_playback(self):
status = self.status
if status:
if status.is_playing:
self.pause()
else:
self.play()
else:
logger.warning('no current playback, playing')
self.play()
def next(self):
self.net.next()
def previous(self):
self.net.previous()
def shuffle(self, state=None):
if state is not None:
if isinstance(state, bool):
self.net.set_shuffle(state)
else:
raise TypeError(f'{state} is not bool')
else:
status = self.status
if status.shuffle:
self.shuffle(state=False)
else:
self.shuffle(state=True)
def set_volume(self, value: int, device: Device = None):
if 0 <= int(value) <= 100:
if device:
self.net.set_volume(value, deviceid=device.device_id)
else:
self.net.set_volume(value)
else:
logger.error(f'{value} not between 0 and 100')