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'],
|
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
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
|
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.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,8 +29,12 @@ 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}')
|
||||||
|
|
||||||
|
if req.status_code != 204:
|
||||||
return req.json()
|
return req.json()
|
||||||
else:
|
else:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
|
||||||
if req.status_code == 429:
|
if req.status_code == 429:
|
||||||
retry_after = req.headers.get('Retry-After', None)
|
retry_after = req.headers.get('Retry-After', None)
|
||||||
@ -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 ''}")
|
||||||
|
@ -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'])
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
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