added player and service objects
This commit is contained in:
parent
92217ad3a4
commit
6076ecd610
1
alarm.py
1
alarm.py
@ -43,7 +43,6 @@ if __name__ == '__main__':
|
||||
|
||||
network = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
||||
os.environ['SPOTSECRET'],
|
||||
os.environ['SPOTACCESS'],
|
||||
os.environ['SPOTREFRESH']))
|
||||
|
||||
found = False
|
||||
|
@ -31,7 +31,6 @@ if __name__ == '__main__':
|
||||
|
||||
network = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
||||
os.environ['SPOTSECRET'],
|
||||
os.environ['SPOTACCESS'],
|
||||
os.environ['SPOTREFRESH']))
|
||||
playlists = network.get_user_playlists()
|
||||
|
||||
|
@ -124,7 +124,6 @@ def go():
|
||||
|
||||
net = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
||||
os.environ['SPOTSECRET'],
|
||||
os.environ['SPOTACCESS'],
|
||||
os.environ['SPOTREFRESH']))
|
||||
|
||||
engine = PlaylistEngine(net)
|
||||
|
@ -17,7 +17,6 @@ if __name__ == '__main__':
|
||||
|
||||
network = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
||||
os.environ['SPOTSECRET'],
|
||||
os.environ['SPOTACCESS'],
|
||||
os.environ['SPOTREFRESH']))
|
||||
|
||||
print(network.user.access_token)
|
||||
|
@ -30,7 +30,6 @@ def go(playlist_name):
|
||||
|
||||
net = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
||||
os.environ['SPOTSECRET'],
|
||||
os.environ['SPOTACCESS'],
|
||||
os.environ['SPOTREFRESH']))
|
||||
|
||||
engine = PlaylistEngine(net)
|
||||
|
@ -26,13 +26,7 @@ class BatchSingleProcessor(AbstractProcessor, ABC):
|
||||
return track
|
||||
|
||||
def process_batch(self, tracks: List[Track]) -> List[Track]:
|
||||
processed = []
|
||||
|
||||
for track in tracks:
|
||||
processed_track = self.process_single(track)
|
||||
processed.append(processed_track)
|
||||
|
||||
return processed
|
||||
return [self.process_single(track) for track in tracks]
|
||||
|
||||
def process(self, tracks: List[Track]) -> List[Track]:
|
||||
return [i for i in self.process_batch(tracks) if i is not None]
|
||||
|
@ -26,6 +26,12 @@ class Playlist:
|
||||
def __len__(self):
|
||||
return len(self.tracks)
|
||||
|
||||
def __getitem__(self, item) -> Track:
|
||||
return self.tracks[item]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.tracks)
|
||||
|
||||
@property
|
||||
def tracks(self) -> List[Track]:
|
||||
return self._tracks
|
||||
|
91
spotframework/model/service.py
Normal file
91
spotframework/model/service.py
Normal 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}'
|
@ -8,6 +8,7 @@ from spotframework.net.parse import parse
|
||||
from spotframework.net.user import NetworkUser
|
||||
from spotframework.model.playlist import SpotifyPlaylist
|
||||
from spotframework.model.track import Track, PlaylistTrack
|
||||
from spotframework.model.service import CurrentlyPlaying, Device
|
||||
from requests.models import Response
|
||||
|
||||
limit = 50
|
||||
@ -28,8 +29,12 @@ class Network:
|
||||
|
||||
if 200 <= req.status_code < 300:
|
||||
logger.debug(f'{method} get {req.status_code}')
|
||||
|
||||
if req.status_code != 204:
|
||||
return req.json()
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
|
||||
if req.status_code == 429:
|
||||
retry_after = req.headers.get('Retry-After', None)
|
||||
@ -215,37 +220,55 @@ class Network:
|
||||
|
||||
return tracks
|
||||
|
||||
def get_available_devices(self) -> Optional[dict]:
|
||||
def get_available_devices(self) -> Optional[List[Device]]:
|
||||
|
||||
logger.info("retrieving")
|
||||
|
||||
resp = self._make_get_request('getAvailableDevices', 'me/player/devices')
|
||||
if resp:
|
||||
return resp
|
||||
return [parse.parse_device(i) for i in resp['devices']]
|
||||
else:
|
||||
logger.error('no devices returned')
|
||||
return None
|
||||
|
||||
def get_player(self) -> Optional[dict]:
|
||||
def get_player(self) -> Optional[CurrentlyPlaying]:
|
||||
|
||||
logger.info("retrieved")
|
||||
|
||||
resp = self._make_get_request('getPlayer', 'me/player')
|
||||
if resp:
|
||||
return resp
|
||||
return parse.parse_currently_playing(resp)
|
||||
else:
|
||||
logger.error('no player returned')
|
||||
logger.info('no player returned')
|
||||
return None
|
||||
|
||||
def get_device_id(self, devicename) -> Optional[str]:
|
||||
|
||||
logger.info(f"{devicename}")
|
||||
|
||||
resp = self.get_available_devices()
|
||||
if resp:
|
||||
return next((i for i in resp['devices'] if i['name'] == devicename), None)['id']
|
||||
devices = self.get_available_devices()
|
||||
if devices:
|
||||
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:
|
||||
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
|
||||
|
||||
def play(self, uri=None, uris=None, deviceid=None) -> Optional[Response]:
|
||||
@ -260,14 +283,12 @@ class Network:
|
||||
if uri and uris:
|
||||
raise Exception('wont take both context uri and uris')
|
||||
|
||||
payload = dict()
|
||||
|
||||
if uri:
|
||||
payload = {'context_uri': uri}
|
||||
|
||||
payload['context_uri'] = uri
|
||||
if uris:
|
||||
payload = {'uris': uris[:200]}
|
||||
|
||||
if not uri and not uris:
|
||||
raise Exception('need either context uri or uris')
|
||||
payload['uris'] = uris[:200]
|
||||
|
||||
req = self._make_put_request('play', 'me/player/play', params=params, json=payload)
|
||||
if req:
|
||||
@ -305,6 +326,21 @@ class Network:
|
||||
else:
|
||||
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]:
|
||||
|
||||
logger.info(f"{state}{' ' + deviceid if deviceid is not None else ''}")
|
||||
|
@ -3,6 +3,8 @@ from spotframework.model.album import SpotifyAlbum
|
||||
from spotframework.model.track import Track, SpotifyTrack, PlaylistTrack
|
||||
from spotframework.model.playlist import SpotifyPlaylist
|
||||
from spotframework.model.user import User
|
||||
from spotframework.model.service import Context, CurrentlyPlaying, Device
|
||||
import datetime
|
||||
|
||||
|
||||
def parse_artist(artist_dict) -> SpotifyArtist:
|
||||
@ -184,3 +186,33 @@ def parse_playlist(playlist_dict) -> SpotifyPlaylist:
|
||||
collaborative=collaborative,
|
||||
public=public,
|
||||
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'])
|
||||
|
||||
|
@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
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__('')
|
||||
|
||||
self.accesstoken = access_token
|
||||
|
0
spotframework/player/__init__.py
Normal file
0
spotframework/player/__init__.py
Normal file
97
spotframework/player/player.py
Normal file
97
spotframework/player/player.py
Normal 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')
|
Loading…
Reference in New Issue
Block a user