commit 42e51ec5f95795bb43d507a0fbdc1951b0c19c2c
parent 0aa392a1a735b6b84a559aa0295e7b48f9246b2a
Author: Paco Esteban <paco@e1e0.net>
Date: Thu, 21 Oct 2021 21:07:30 +0200
implement now playing with time and all
Diffstat:
3 files changed, 99 insertions(+), 14 deletions(-)
diff --git a/subclient/helpers.py b/subclient/helpers.py
@@ -12,6 +12,8 @@ def format_duration(seconds):
:return: String of the form "%H:%M:%S"
:rtype: string
"""
+ if seconds is None:
+ return "-"
time_format = "%H:%M:%S" if seconds > 3600 else "%M:%S"
ty_res = time.gmtime(seconds)
return time.strftime(time_format, ty_res)
diff --git a/subclient/player.py b/subclient/player.py
@@ -15,7 +15,6 @@ class Player:
for s in playlist:
filename = self.download_stream(s)
self.mpv.loadfile(filename, 'append-play')
- # self.mpv.wait_for_property('eof-reached')
def download_stream(self, song):
if not os.path.isdir(self.cache_dir):
@@ -41,9 +40,6 @@ class Player:
def is_paused(self):
return self.mpv.pause
- def is_idle(self):
- return self.mpv.core_idle
-
def set_pause(self, state):
self.mpv.pause = state
@@ -58,3 +54,28 @@ class Player:
def seek(self, duration='+5'):
self.mpv.command('seek', duration)
+
+ def get_song_info(self):
+ title = self.mpv.media_title
+ if title is None:
+ return ' '
+ metadata = self.mpv.metadata
+ artist = ' '
+ if metadata is not None:
+ artist = self.mpv.metadata.get('album_artist', ' ')
+ return f'{artist} :: {title}'
+
+ def get_list_info(self):
+ playlist_total = self.mpv.playlist_count
+ playlist_pos = self.mpv.playlist_pos_1
+ if playlist_total is None or playlist_pos is None:
+ return ' '
+ return f'{playlist_pos} of {playlist_total}'
+
+ def get_duration_info(self):
+ raw_duration = self.mpv.duration
+ if raw_duration is None:
+ return ' '
+ duration = helpers.format_duration(raw_duration)
+ time_pos = helpers.format_duration(self.mpv.time_pos)
+ return f'{time_pos} / {duration}'
diff --git a/subclient/subclient.py b/subclient/subclient.py
@@ -4,6 +4,8 @@
import configparser
import curses
import sys
+import threading
+from datetime import timedelta
from subclient import subsonic
from subclient import player
from subclient import helpers
@@ -12,6 +14,25 @@ from subclient import helpers
__version__ = 'v0.0.1'
+class Updater(threading.Thread):
+ def __init__(self, interval, execute, *args, **kwargs):
+ threading.Thread.__init__(self)
+ self.daemon = False
+ self.stopped = threading.Event()
+ self.interval = interval
+ self.execute = execute
+ self.args = args
+ self.kwargs = kwargs
+
+ def stop(self):
+ self.stopped.set()
+ self.join()
+
+ def run(self):
+ while not self.stopped.wait(self.interval.total_seconds()):
+ self.execute(*self.args, **self.kwargs)
+
+
class SubClient:
UP = -1
DOWN = 1
@@ -43,6 +64,7 @@ class SubClient:
self.width = 0
self.listwin_height = 0
self.infowin_height = 0
+ self.info_updater = Updater(timedelta(seconds=1), self.update_info)
self.init_curses()
self.nav_list = self.subsonic.get_artists()
@@ -63,9 +85,10 @@ class SubClient:
self.infowin_height = self.height - self.listwin_height
self.listwin = curses.newwin(self.listwin_height, self.width,
0, 0)
- self.infowin = curses.newwin(3, self.width,
- self.listwin_height + 2, 0)
+ self.infowin = curses.newwin(6, self.width,
+ self.listwin_height + 1, 0)
self.listwin.keypad(True)
+ self.infowin.border(0)
curses.curs_set(0) # disable cursor
curses.noecho()
@@ -74,44 +97,51 @@ class SubClient:
def run(self):
# read chars until we exit, then clean
try:
+ self.refresh(['list', 'info'])
self.input_loop()
except KeyboardInterrupt:
pass
finally:
+ self.info_updater.stop()
self.player.exit()
curses.endwin()
def input_loop(self):
while True:
- self.listwin.erase()
- self.load_list()
- self.refresh()
-
c = self.listwin.getkey()
if c == 'q': # quit
break
elif c == 'j':
self.nav_scroll(self.DOWN)
+ self.refresh(['list'])
elif c == 'k':
self.nav_scroll(self.UP)
+ self.refresh(['list'])
elif c == 'f':
self.nav_page(self.DOWN)
+ self.refresh(['list'])
elif c == 'b':
self.nav_page(self.UP)
+ self.refresh(['list'])
elif c == 'l':
self.nav_in_out(self.IN)
+ self.refresh(['list'])
elif c == 'h':
self.nav_in_out(self.OUT)
+ self.refresh(['list'])
elif c == 'n':
self.player.play_next()
+ self.refresh(['info'])
elif c == 'p':
self.player.play_prev()
+ self.refresh(['info'])
elif c == ' ':
self.player.set_pause(not self.player.is_paused())
+ self.refresh(['info'])
elif c in [curses.KEY_ENTER, '\r', '\n']:
if self.nav_list_type == self.SONGS:
- playlist = self.nav_list[self.nav_top + self.nav_selected:self.nav_bottom]
- self.player.play(playlist)
+ self.start_player()
+ self.info_updater.start()
def load_list(self):
for i, t in enumerate(
@@ -203,8 +233,40 @@ class SubClient:
self.nav_bottom = len(self.nav_list)
self.nav_pages = self.nav_bottom // self.nav_max_lines
- def refresh(self):
- self.listwin.noutrefresh()
+ def start_player(self):
+ playlist = self.nav_list[self.nav_top + self.nav_selected:self.nav_bottom]
+ self.player.play(playlist)
+
+ def get_info(self):
+ list_info = self.player.get_list_info()
+ song_info = self.player.get_song_info()
+ duration_info = self.player.get_duration_info()
+ if self.player.is_paused():
+ icon = '|| '
+ else:
+ icon = '> '
+
+ if song_info is not None:
+ self.infowin.addnstr(1, 2, icon + list_info, self.width - 3)
+ self.infowin.addnstr(2, 2, song_info, self.width - 3)
+ self.infowin.addnstr(3, 2, duration_info, self.width - 3)
+
+ def update_info(self):
+ self.refresh(['info'])
+
+ def refresh(self, win):
+ if 'list' in win:
+ self.listwin.erase()
+ self.load_list()
+ self.listwin.noutrefresh()
+
+ if 'info' in win:
+ self.infowin.addstr(1, 2, " " * (self.width - 3))
+ self.infowin.addstr(2, 2, " " * (self.width - 3))
+ self.infowin.addstr(3, 2, " " * (self.width - 3))
+ self.get_info()
+ self.infowin.noutrefresh()
+
curses.doupdate()