added listener, changed env var names
This commit is contained in:
parent
de5da3e057
commit
e6f120b930
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,5 +3,6 @@ __pycache__
|
|||||||
*.csv
|
*.csv
|
||||||
.idea
|
.idea
|
||||||
.spot
|
.spot
|
||||||
|
data
|
||||||
|
|
||||||
scratch.py
|
scratch.py
|
6
alarm.py
6
alarm.py
@ -42,9 +42,9 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
network = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
network = Network(NetworkUser(os.environ['SPOT_CLIENT'],
|
||||||
os.environ['SPOTSECRET'],
|
os.environ['SPOT_SECRET'],
|
||||||
os.environ['SPOTREFRESH']))
|
os.environ['SPOT_REFRESH']))
|
||||||
network.user.refresh_access_token()
|
network.user.refresh_access_token()
|
||||||
|
|
||||||
found = False
|
found = False
|
||||||
|
@ -29,9 +29,9 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# try:
|
# try:
|
||||||
|
|
||||||
network = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
network = Network(NetworkUser(os.environ['SPOT_CLIENT'],
|
||||||
os.environ['SPOTSECRET'],
|
os.environ['SPOT_SECRET'],
|
||||||
os.environ['SPOTREFRESH']))
|
os.environ['SPOT_REFRESH']))
|
||||||
network.user.refresh_access_token()
|
network.user.refresh_access_token()
|
||||||
playlists = network.get_user_playlists()
|
playlists = network.get_user_playlists()
|
||||||
|
|
||||||
|
@ -122,9 +122,9 @@ def go():
|
|||||||
logger.critical('none to execute, terminating')
|
logger.critical('none to execute, terminating')
|
||||||
return
|
return
|
||||||
|
|
||||||
net = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
net = Network(NetworkUser(os.environ['SPOT_CLIENT'],
|
||||||
os.environ['SPOTSECRET'],
|
os.environ['SPOT_SECRET'],
|
||||||
os.environ['SPOTREFRESH']))
|
os.environ['SPOT_REFRESH']))
|
||||||
net.user.refresh_access_token()
|
net.user.refresh_access_token()
|
||||||
|
|
||||||
engine = PlaylistEngine(net)
|
engine = PlaylistEngine(net)
|
||||||
|
@ -15,9 +15,9 @@ logger.addHandler(stream_handler)
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
network = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
network = Network(NetworkUser(os.environ['SPOT_CLIENT'],
|
||||||
os.environ['SPOTSECRET'],
|
os.environ['SPOT_SECRET'],
|
||||||
os.environ['SPOTREFRESH']))
|
os.environ['SPOT_REFRESH']))
|
||||||
network.user.refresh_access_token()
|
network.user.refresh_access_token()
|
||||||
|
|
||||||
print(network.user.access_token)
|
print(network.user.access_token)
|
||||||
|
45
listener.py
Normal file
45
listener.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import click
|
||||||
|
from spotframework.listener.cmd import ListenCmd
|
||||||
|
from spotframework.net.network import Network, NetworkUser
|
||||||
|
|
||||||
|
logger = logging.getLogger('spotframework')
|
||||||
|
|
||||||
|
log_format = '%(asctime)s %(levelname)s %(name)s:%(funcName)s - %(message)s'
|
||||||
|
|
||||||
|
file_handler = logging.FileHandler("data/listener.log")
|
||||||
|
formatter = logging.Formatter(log_format)
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
stream_log_format = '%(levelname)s %(name)s:%(funcName)s - %(message)s'
|
||||||
|
stream_formatter = logging.Formatter(stream_log_format)
|
||||||
|
|
||||||
|
stream_handler = logging.StreamHandler()
|
||||||
|
stream_handler.setFormatter(stream_formatter)
|
||||||
|
|
||||||
|
logger.addHandler(stream_handler)
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option('-v', '--verbose', is_flag=True)
|
||||||
|
@click.option('--client-id', prompt=True, default=lambda: os.environ.get('SPOT_CLIENT', ''))
|
||||||
|
@click.option('--client-secret', prompt=True, default=lambda: os.environ.get('SPOT_SECRET', ''))
|
||||||
|
@click.option('--refresh-token', prompt=True, default=lambda: os.environ.get('SPOT_REFRESH', ''))
|
||||||
|
def listen(verbose, client_id, client_secret, refresh_token):
|
||||||
|
if verbose:
|
||||||
|
stream_handler.setLevel('DEBUG')
|
||||||
|
else:
|
||||||
|
stream_handler.setLevel('WARNING')
|
||||||
|
|
||||||
|
net = Network(NetworkUser(client_id=client_id,
|
||||||
|
client_secret=client_secret,
|
||||||
|
refresh_token=refresh_token).refresh_access_token())
|
||||||
|
cmd = ListenCmd(net, stream_handler)
|
||||||
|
cmd.cmdloop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
listen()
|
@ -1,6 +1,7 @@
|
|||||||
certifi==2019.6.16
|
certifi==2019.11.28
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
|
Click==7.0
|
||||||
idna==2.8
|
idna==2.8
|
||||||
requests==2.22.0
|
requests==2.22.0
|
||||||
tabulate==0.8.3
|
tabulate==0.8.6
|
||||||
urllib3==1.25.3
|
urllib3==1.25.7
|
||||||
|
@ -28,9 +28,9 @@ logger.addHandler(stream_handler)
|
|||||||
|
|
||||||
def go(playlist_name):
|
def go(playlist_name):
|
||||||
|
|
||||||
net = Network(NetworkUser(os.environ['SPOTCLIENT'],
|
net = Network(NetworkUser(os.environ['SPOT_CLIENT'],
|
||||||
os.environ['SPOTSECRET'],
|
os.environ['SPOT_SECRET'],
|
||||||
os.environ['SPOTREFRESH']))
|
os.environ['SPOT_REFRESH']))
|
||||||
net.user.refresh_access_token()
|
net.user.refresh_access_token()
|
||||||
|
|
||||||
engine = PlaylistEngine(net)
|
engine = PlaylistEngine(net)
|
||||||
|
0
spotframework/listener/__init__.py
Normal file
0
spotframework/listener/__init__.py
Normal file
153
spotframework/listener/cmd.py
Normal file
153
spotframework/listener/cmd.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
from cmd import Cmd
|
||||||
|
import functools
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from spotframework.net.network import Network
|
||||||
|
from spotframework.listener.thread import ListenerThread
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def check_thread(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
def check_thread_wrapper(self, *args, **kwargs):
|
||||||
|
if self.listen_thread is not None:
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
print('>> not running')
|
||||||
|
|
||||||
|
return check_thread_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def check_last_dataset(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
def check_last_dataset_wrapper(self, *args, **kwargs):
|
||||||
|
if self.last_dataset is not None:
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
print('>> not running')
|
||||||
|
|
||||||
|
return check_last_dataset_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class ListenCmd(Cmd):
|
||||||
|
intro = 'listener... ? for help'
|
||||||
|
prompt = '(listen)> '
|
||||||
|
|
||||||
|
dt_format = "%-H:%M:%S %-d %b %-y"
|
||||||
|
|
||||||
|
def __init__(self, net: Network, log_stream_handler=None):
|
||||||
|
Cmd.__init__(self)
|
||||||
|
self.net = net
|
||||||
|
self.listen_thread: Optional[ListenerThread] = None
|
||||||
|
self.last_dataset = None
|
||||||
|
|
||||||
|
self.verbose = False
|
||||||
|
self.log_stream_handler = log_stream_handler
|
||||||
|
|
||||||
|
def start_listener(self):
|
||||||
|
if self.listen_thread is not None:
|
||||||
|
logger.info('restarting')
|
||||||
|
print('>> restarting listener')
|
||||||
|
self.stop_listener()
|
||||||
|
|
||||||
|
logger.info('starting')
|
||||||
|
print('>> starting listener')
|
||||||
|
self.listen_thread = ListenerThread(self.net)
|
||||||
|
self.listen_thread.on_playback_change.append(lambda x: print(f'playback changed -> {x}'))
|
||||||
|
self.listen_thread.start()
|
||||||
|
|
||||||
|
@check_thread
|
||||||
|
def stop_listener(self):
|
||||||
|
logger.info('stopping')
|
||||||
|
print('>> stopping listener')
|
||||||
|
self.last_dataset = self.listen_thread.recent_tracks
|
||||||
|
self.listen_thread.stop()
|
||||||
|
|
||||||
|
def print_tracks(self, tracks):
|
||||||
|
[print(f'({i.played_at.strftime(self.dt_format)}) {i.name} / {i.artists_names}')
|
||||||
|
for i in tracks]
|
||||||
|
|
||||||
|
def print(self):
|
||||||
|
if self.listen_thread is not None:
|
||||||
|
print('now playing:', self.listen_thread.now_playing)
|
||||||
|
self.print_tracks(self.listen_thread.recent_tracks)
|
||||||
|
elif self.last_dataset is not None:
|
||||||
|
self.print_tracks(self.last_dataset)
|
||||||
|
else:
|
||||||
|
print('>> no tracks to print')
|
||||||
|
|
||||||
|
@check_thread
|
||||||
|
def set_poll_interval(self, value):
|
||||||
|
if value.isdigit():
|
||||||
|
logger.info(f'setting polling interval to {value}')
|
||||||
|
print(f'>> interval set to {value}')
|
||||||
|
self.listen_thread.sleep_interval = int(value)
|
||||||
|
else:
|
||||||
|
logger.error(f'{value} is not a valid interval')
|
||||||
|
print('>> not a number')
|
||||||
|
|
||||||
|
def do_interval(self, args):
|
||||||
|
"""set polling interval"""
|
||||||
|
self.set_poll_interval(args)
|
||||||
|
|
||||||
|
def do_print(self, args):
|
||||||
|
"""print recently played tracks"""
|
||||||
|
self.print()
|
||||||
|
|
||||||
|
def do_p(self, args):
|
||||||
|
"""print recently played tracks"""
|
||||||
|
self.print()
|
||||||
|
|
||||||
|
def do_listen(self, args):
|
||||||
|
"""start listener"""
|
||||||
|
self.start_listener()
|
||||||
|
|
||||||
|
def do_l(self, args):
|
||||||
|
"""start listener"""
|
||||||
|
self.start_listener()
|
||||||
|
|
||||||
|
def do_stop(self, args):
|
||||||
|
"""stop listener"""
|
||||||
|
self.stop_listener()
|
||||||
|
|
||||||
|
def do_e(self, args):
|
||||||
|
"""stop listener"""
|
||||||
|
self.stop_listener()
|
||||||
|
|
||||||
|
def do_verbose(self, args):
|
||||||
|
"""toggle verbosity"""
|
||||||
|
if self.log_stream_handler is not None:
|
||||||
|
self.verbose = not self.verbose
|
||||||
|
if self.verbose:
|
||||||
|
self.log_stream_handler.setLevel('DEBUG')
|
||||||
|
else:
|
||||||
|
self.log_stream_handler.setLevel('WARNING')
|
||||||
|
|
||||||
|
@check_thread
|
||||||
|
def do_quit(self, args):
|
||||||
|
"""stop and quit"""
|
||||||
|
logger.info('quitting')
|
||||||
|
self.stop_listener()
|
||||||
|
self.listen_thread.join()
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
@check_thread
|
||||||
|
def do_q(self, args):
|
||||||
|
"""stop and quit"""
|
||||||
|
logger.info('quitting')
|
||||||
|
self.stop_listener()
|
||||||
|
self.listen_thread.join()
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
@check_thread
|
||||||
|
def do_status(self, args):
|
||||||
|
"""stop and quit"""
|
||||||
|
if self.listen_thread is not None:
|
||||||
|
if self.listen_thread.is_alive():
|
||||||
|
print('>> running')
|
||||||
|
else:
|
||||||
|
print('>> thread dead')
|
||||||
|
else:
|
||||||
|
print('>> not running')
|
65
spotframework/listener/thread.py
Normal file
65
spotframework/listener/thread.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import threading
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from spotframework.net.network import Network
|
||||||
|
from spotframework.model.service import CurrentlyPlaying
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ListenerThread(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
net: Network,
|
||||||
|
sleep_interval: int = 5,
|
||||||
|
request_size: int = 5):
|
||||||
|
super(ListenerThread, self).__init__()
|
||||||
|
self._stop_event = threading.Event()
|
||||||
|
|
||||||
|
self.net = net
|
||||||
|
|
||||||
|
self.sleep_interval = sleep_interval
|
||||||
|
self.request_size = request_size
|
||||||
|
|
||||||
|
self.recent_tracks = []
|
||||||
|
self.prev_now_playing: Optional[CurrentlyPlaying] = None
|
||||||
|
self.now_playing: Optional[CurrentlyPlaying] = net.get_player()
|
||||||
|
|
||||||
|
self.on_playback_change = []
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
logger.info('stopping thread')
|
||||||
|
self._stop_event.set()
|
||||||
|
|
||||||
|
def stopped(self):
|
||||||
|
return self._stop_event.is_set()
|
||||||
|
|
||||||
|
def update_now_playing(self):
|
||||||
|
live_now_playing = self.net.get_player()
|
||||||
|
|
||||||
|
if self.now_playing is None and live_now_playing is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if live_now_playing != self.now_playing:
|
||||||
|
self.prev_now_playing = self.now_playing
|
||||||
|
self.now_playing = live_now_playing
|
||||||
|
for func in self.on_playback_change:
|
||||||
|
func(live_now_playing)
|
||||||
|
else:
|
||||||
|
self.now_playing = live_now_playing
|
||||||
|
|
||||||
|
def update_recent_tracks(self):
|
||||||
|
logger.debug('pulling tracks')
|
||||||
|
tracks = self.net.get_recently_played_tracks(response_limit=self.request_size)
|
||||||
|
for track in tracks:
|
||||||
|
if track.played_at not in [i.played_at for i in self.recent_tracks]:
|
||||||
|
self.recent_tracks.append(track)
|
||||||
|
|
||||||
|
self.recent_tracks.sort(key=lambda x: x.played_at)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while not self.stopped():
|
||||||
|
self.update_recent_tracks()
|
||||||
|
self.update_now_playing()
|
||||||
|
self._stop_event.wait(self.sleep_interval)
|
@ -25,7 +25,7 @@ class Context:
|
|||||||
return f'Context: {self.object_type} uri({self.uri})'
|
return f'Context: {self.object_type} uri({self.uri})'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.object_type} / {self.uri}'
|
return str(self.uri)
|
||||||
|
|
||||||
|
|
||||||
class Device:
|
class Device:
|
||||||
@ -110,4 +110,4 @@ class CurrentlyPlaying:
|
|||||||
else:
|
else:
|
||||||
playing = '(paused)'
|
playing = '(paused)'
|
||||||
|
|
||||||
return f'{playing} {self.track} on {self.device} ({self._format_duration(self.progress_ms)})'
|
return f'{playing} {self.track} on {self.device} from {self.context} ({self._format_duration(self.progress_ms)})'
|
||||||
|
Loading…
Reference in New Issue
Block a user