mirror of
https://github.com/vale981/clay
synced 2025-03-04 17:11:41 -05:00
Refactoring.
This commit is contained in:
parent
7aa91dc104
commit
4607dc6ca1
8 changed files with 142 additions and 105 deletions
21
app.py
21
app.py
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
|
||||
|
@ -43,16 +44,16 @@ PALETTE = [
|
|||
|
||||
class PlayProgress(urwid.ProgressBar):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
super(PlayProgress, self).__init__(*args, **kwargs)
|
||||
self.track = None
|
||||
|
||||
def get_text(self):
|
||||
if self.track is None:
|
||||
return 'Idle'
|
||||
return u'Idle'
|
||||
progress = player.get_play_progress_seconds()
|
||||
total = player.get_length_seconds()
|
||||
return ' {} {} - {} [{:02d}:{:02d} / {:02d}:{:02d}] {} {}'.format(
|
||||
'>' if player.is_playing else 'X',
|
||||
return u' {} {} - {} [{:02d}:{:02d} / {:02d}:{:02d}] {} {}'.format(
|
||||
u'|>' if player.is_playing else u'||',
|
||||
# '\u25B6' if player.is_playing else '\u25A0',
|
||||
self.track.artist,
|
||||
self.track.title,
|
||||
|
@ -60,11 +61,11 @@ class PlayProgress(urwid.ProgressBar):
|
|||
progress % 60,
|
||||
total // 60,
|
||||
total % 60,
|
||||
'S' if player.get_is_random() else ' ',
|
||||
'R' if player.get_is_repeat_one() else ' '
|
||||
u'S' if player.get_is_random() else ' ',
|
||||
u'R' if player.get_is_repeat_one() else ' '
|
||||
# '⋍' if player.get_is_random() else ' ',
|
||||
# '⟲' if player.get_is_repeat_one() else ' '
|
||||
).encode('utf-8')
|
||||
)
|
||||
|
||||
def set_track(self, track):
|
||||
self.track = track
|
||||
|
@ -78,7 +79,7 @@ class AppWidget(urwid.Frame):
|
|||
def __init__(self, page_class):
|
||||
self.page_class = page_class
|
||||
# self.attrwrap = urwid.AttrWrap(urwid.Text(), 'panel')
|
||||
super().__init__(
|
||||
super(AppWidget.Tab, self).__init__(
|
||||
self.get_title()
|
||||
)
|
||||
|
||||
|
@ -133,7 +134,7 @@ class AppWidget(urwid.Frame):
|
|||
self.seekbar
|
||||
])
|
||||
self.current_page = StartUp(self)
|
||||
super().__init__(
|
||||
super(AppWidget, self).__init__(
|
||||
header=self.header,
|
||||
footer=self.panel,
|
||||
body=self.current_page
|
||||
|
@ -207,7 +208,7 @@ class AppWidget(urwid.Frame):
|
|||
elif key == 'ctrl q':
|
||||
sys.exit(0)
|
||||
else:
|
||||
super().keypress(size, key)
|
||||
super(AppWidget, self).keypress(size, key)
|
||||
|
||||
|
||||
def main():
|
||||
|
|
90
gp.py
90
gp.py
|
@ -1,6 +1,8 @@
|
|||
from gmusicapi.clients import Mobileclient
|
||||
from threading import Thread, Lock
|
||||
|
||||
gp = None
|
||||
|
||||
|
||||
def async(fn):
|
||||
def wrapper(*args, **kwargs):
|
||||
|
@ -12,6 +14,7 @@ def async(fn):
|
|||
result = fn(*args, **kwargs)
|
||||
except Exception as e:
|
||||
callback(None, e, **extra)
|
||||
raise
|
||||
else:
|
||||
callback(result, None, **extra)
|
||||
|
||||
|
@ -32,13 +35,68 @@ def synchronized(fn):
|
|||
return wrapper
|
||||
|
||||
|
||||
class Track(object):
|
||||
def __init__(self, id, title, artist, duration):
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.artist = artist
|
||||
self.duration = duration
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data, many=False):
|
||||
if many:
|
||||
return [cls.from_data(one) for one in data]
|
||||
|
||||
return Track(
|
||||
id=data['id'],
|
||||
title=data['title'],
|
||||
artist=data['artist'],
|
||||
duration=int(data['durationMillis'])
|
||||
)
|
||||
|
||||
def get_url(self, callback):
|
||||
gp.get_stream_url(self.id, callback=callback, extra=dict(track=self))
|
||||
|
||||
|
||||
class Playlist(object):
|
||||
def __init__(self, id, name, tracks):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.tracks = tracks
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data, many=False):
|
||||
if many:
|
||||
return [cls.from_data(one) for one in data]
|
||||
|
||||
return Playlist(
|
||||
id=data['id'],
|
||||
name=data['name'],
|
||||
tracks=cls.playlist_items_to_tracks(data['tracks'])
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def playlist_items_to_tracks(self, playlist_tracks):
|
||||
results = []
|
||||
cached_tracks_map = gp.get_cached_tracks_map()
|
||||
for playlist_track in playlist_tracks:
|
||||
if 'track' in playlist_track:
|
||||
track = dict(playlist_track['track'])
|
||||
track['id'] = playlist_track['trackId']
|
||||
track = Track.from_data(track)
|
||||
else:
|
||||
track = cached_tracks_map[playlist_track['trackId']]
|
||||
results.append(track)
|
||||
return results
|
||||
|
||||
|
||||
class GP(object):
|
||||
def __init__(self):
|
||||
self.mc = Mobileclient()
|
||||
self.invalidate_caches()
|
||||
|
||||
def invalidate_caches(self):
|
||||
self.cached_songs = None
|
||||
self.cached_tracks = None
|
||||
self.cached_playlists = None
|
||||
|
||||
@async
|
||||
|
@ -51,11 +109,11 @@ class GP(object):
|
|||
|
||||
@async
|
||||
@synchronized
|
||||
def get_all_songs(self):
|
||||
if self.cached_songs:
|
||||
return self.cached_songs
|
||||
self.cached_songs = self.mc.get_all_songs()
|
||||
return self.cached_songs
|
||||
def get_all_tracks(self):
|
||||
if self.cached_tracks:
|
||||
return self.cached_tracks
|
||||
self.cached_tracks = Track.from_data(self.mc.get_all_songs(), True)
|
||||
return self.cached_tracks
|
||||
|
||||
@async
|
||||
def get_stream_url(self, id):
|
||||
|
@ -66,18 +124,18 @@ class GP(object):
|
|||
def get_all_user_playlist_contents(self):
|
||||
if self.cached_playlists:
|
||||
return self.cached_playlists
|
||||
if not self.cached_songs:
|
||||
self.cached_songs = self.mc.get_all_songs()
|
||||
cached_songs_map = {track['id']: track for track in self.cached_songs}
|
||||
self.cached_playlists = self.mc.get_all_user_playlist_contents()
|
||||
for playlist in self.cached_playlists:
|
||||
for song in playlist['tracks']:
|
||||
if 'track' not in song:
|
||||
song['track'] = cached_songs_map[song['trackId']]
|
||||
else:
|
||||
song['track']['id'] = song['trackId']
|
||||
if not self.cached_tracks:
|
||||
self.cached_tracks = self.mc.get_all_tracks()
|
||||
|
||||
self.cached_playlists = Playlist.from_data(
|
||||
self.mc.get_all_user_playlist_contents(),
|
||||
True
|
||||
)
|
||||
return self.cached_playlists
|
||||
|
||||
def get_cached_tracks_map(self):
|
||||
return {track.id: track for track in self.cached_tracks}
|
||||
|
||||
|
||||
gp = GP()
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import urwid
|
||||
from gp import gp
|
||||
from songlist import SongListBox
|
||||
from player import Track
|
||||
|
||||
|
||||
class MyLibrary(urwid.Columns):
|
||||
|
@ -12,16 +11,16 @@ class MyLibrary(urwid.Columns):
|
|||
self.app = app
|
||||
self.songlist = SongListBox(app)
|
||||
|
||||
gp.get_all_songs(callback=self.on_get_all_songs)
|
||||
gp.get_all_tracks(callback=self.on_get_all_songs)
|
||||
|
||||
return super().__init__([
|
||||
return super(MyLibrary, self).__init__([
|
||||
self.songlist
|
||||
])
|
||||
|
||||
def on_get_all_songs(self, results, error):
|
||||
def on_get_all_songs(self, tracks, error):
|
||||
if error:
|
||||
self.app.set_page('Error', error)
|
||||
return
|
||||
self.songlist.populate(Track.from_data(results, many=True))
|
||||
self.songlist.populate(tracks)
|
||||
self.app.redraw()
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import urwid
|
||||
from gp import gp
|
||||
from player import Track
|
||||
from songlist import SongListBox
|
||||
|
||||
|
||||
class PlaylistListItem(urwid.Columns):
|
||||
class MyPlaylistListItem(urwid.Columns):
|
||||
signals = ['activate']
|
||||
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
def __init__(self, playlist):
|
||||
self.playlist = playlist
|
||||
self.text = urwid.SelectableIcon(' ☰ {} ({})'.format(
|
||||
self.data['name'],
|
||||
len(self.data['tracks'])
|
||||
self.playlist.name,
|
||||
len(self.playlist.tracks)
|
||||
), cursor_position=3)
|
||||
self.text.set_layout('left', 'clip', None)
|
||||
self.content = urwid.AttrWrap(
|
||||
|
@ -28,14 +28,10 @@ class PlaylistListItem(urwid.Columns):
|
|||
return super().keypress(size, key)
|
||||
|
||||
def get_tracks(self):
|
||||
return Track.from_data([
|
||||
item['track']
|
||||
for item
|
||||
in self.data['tracks']
|
||||
], many=True)
|
||||
return self.playlist.tracks
|
||||
|
||||
|
||||
class PlaylistListBox(urwid.ListBox):
|
||||
class MyPlaylistListBox(urwid.ListBox):
|
||||
signals = ['activate']
|
||||
|
||||
def __init__(self, app):
|
||||
|
@ -59,18 +55,18 @@ class PlaylistListBox(urwid.ListBox):
|
|||
|
||||
items = []
|
||||
for playlist in playlists:
|
||||
playlistlistitem = PlaylistListItem(playlist)
|
||||
myplaylistlistitem = MyPlaylistListItem(playlist)
|
||||
urwid.connect_signal(
|
||||
playlistlistitem, 'activate', self.item_activated
|
||||
myplaylistlistitem, 'activate', self.item_activated
|
||||
)
|
||||
items.append(playlistlistitem)
|
||||
items.append(myplaylistlistitem)
|
||||
|
||||
self.walker[:] = items
|
||||
|
||||
self.app.redraw()
|
||||
|
||||
def item_activated(self, playlistlistitem):
|
||||
urwid.emit_signal(self, 'activate', playlistlistitem)
|
||||
def item_activated(self, myplaylistlistitem):
|
||||
urwid.emit_signal(self, 'activate', myplaylistlistitem)
|
||||
|
||||
|
||||
class MyPlaylists(urwid.Columns):
|
||||
|
@ -80,21 +76,21 @@ class MyPlaylists(urwid.Columns):
|
|||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
self.playlistlist = PlaylistListBox(app)
|
||||
self.myplaylistlist = MyPlaylistListBox(app)
|
||||
self.songlist = SongListBox(app)
|
||||
self.songlist.populate([])
|
||||
|
||||
urwid.connect_signal(
|
||||
self.playlistlist, 'activate', self.playlistlistitem_activated
|
||||
self.myplaylistlist, 'activate', self.myplaylistlistitem_activated
|
||||
)
|
||||
|
||||
return super().__init__([
|
||||
self.playlistlist,
|
||||
self.myplaylistlist,
|
||||
self.songlist
|
||||
])
|
||||
|
||||
def playlistlistitem_activated(self, playlistlistitem):
|
||||
def myplaylistlistitem_activated(self, myplaylistlistitem):
|
||||
self.songlist.populate(
|
||||
playlistlistitem.get_tracks()
|
||||
myplaylistlistitem.get_tracks()
|
||||
)
|
||||
|
||||
|
|
4
pages.py
4
pages.py
|
@ -22,7 +22,7 @@ class StartUp(urwid.Filler):
|
|||
'Please set your credentials on the settings page.'
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
super(StartUp, self).__init__(
|
||||
urwid.Pile([
|
||||
urwid.Padding(
|
||||
urwid.AttrWrap(urwid.BigText(
|
||||
|
@ -59,7 +59,7 @@ class StartUp(urwid.Filler):
|
|||
|
||||
class Error(urwid.Filler):
|
||||
def __init__(self, app, error):
|
||||
super().__init__(
|
||||
super(Error, self).__init__(
|
||||
urwid.Text('Error:\n\n{}'.format(str(error))),
|
||||
valign='top'
|
||||
)
|
||||
|
|
53
player.py
53
player.py
|
@ -5,34 +5,9 @@ import json
|
|||
|
||||
import vlc
|
||||
from eventhook import EventHook
|
||||
import gp
|
||||
from gp import gp
|
||||
|
||||
|
||||
class Track(object):
|
||||
def __init__(self, id, title, artist, duration):
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.artist = artist
|
||||
self.duration = duration
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data, many=False):
|
||||
if many:
|
||||
return [cls.from_data(one) for one in data]
|
||||
|
||||
return Track(
|
||||
id=data['id'],
|
||||
title=data['title'],
|
||||
artist=data['artist'],
|
||||
duration=int(data['durationMillis'])
|
||||
)
|
||||
|
||||
def get_url(self, callback):
|
||||
gp.get_stream_url(self.id, callback=callback, extra=dict(track=self))
|
||||
|
||||
|
||||
class Playlist(object):
|
||||
class Queue(object):
|
||||
def __init__(self):
|
||||
self.random = False
|
||||
self.repeat_one = False
|
||||
|
@ -109,10 +84,10 @@ class Player(object):
|
|||
self._media_position_changed
|
||||
)
|
||||
|
||||
self.playlist = Playlist()
|
||||
self.queue = Queue()
|
||||
|
||||
def broadcast_state(self):
|
||||
track = self.playlist.get_current_track()
|
||||
track = self.queue.get_current_track()
|
||||
if track is None:
|
||||
data = dict(
|
||||
playing=False,
|
||||
|
@ -145,29 +120,29 @@ class Player(object):
|
|||
self.get_play_progress()
|
||||
)
|
||||
|
||||
def load_playlist(self, data, current_index):
|
||||
self.playlist.load(data, current_index)
|
||||
def load_queue(self, data, current_index):
|
||||
self.queue.load(data, current_index)
|
||||
self._play()
|
||||
|
||||
def get_is_random(self):
|
||||
return self.playlist.random
|
||||
return self.queue.random
|
||||
|
||||
def get_is_repeat_one(self):
|
||||
return self.playlist.repeat_one
|
||||
return self.queue.repeat_one
|
||||
|
||||
def set_random(self, value):
|
||||
self.playlist.random = value
|
||||
self.queue.random = value
|
||||
self.playback_flags_changed.fire()
|
||||
|
||||
def set_repeat_one(self, value):
|
||||
self.playlist.repeat_one = value
|
||||
self.queue.repeat_one = value
|
||||
self.playback_flags_changed.fire()
|
||||
|
||||
def get_queue(self):
|
||||
return self.playlist.get_tracks()
|
||||
return self.queue.get_tracks()
|
||||
|
||||
def _play(self):
|
||||
track = self.playlist.get_current_track()
|
||||
track = self.queue.get_current_track()
|
||||
if track is None:
|
||||
return
|
||||
track.get_url(callback=self._play_ready)
|
||||
|
@ -200,14 +175,14 @@ class Player(object):
|
|||
return int(self.mp.get_length() // 1000)
|
||||
|
||||
def next(self, force=False):
|
||||
self.playlist.next(force)
|
||||
self.queue.next(force)
|
||||
self._play()
|
||||
|
||||
def get_current_track(self):
|
||||
return self.playlist.get_current_track()
|
||||
return self.queue.get_current_track()
|
||||
|
||||
# def prev(self):
|
||||
# self.playlist.prev()
|
||||
# self.queue.prev()
|
||||
# self._play()
|
||||
|
||||
def seek(self, delta):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import errno
|
||||
import yaml
|
||||
|
||||
import appdirs
|
||||
|
@ -51,7 +52,13 @@ class Settings(urwid.Columns):
|
|||
@classmethod
|
||||
def get_config_filename(cls):
|
||||
filedir = appdirs.user_config_dir('clay', 'Clay')
|
||||
os.makedirs(filedir, exist_ok=True)
|
||||
|
||||
try:
|
||||
os.makedirs(filedir)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
path = os.path.join(filedir, 'config.json')
|
||||
if not os.path.exists(path):
|
||||
with open(path, 'w') as f:
|
||||
|
|
21
songlist.py
21
songlist.py
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import urwid
|
||||
from player import player
|
||||
|
||||
|
@ -12,9 +13,9 @@ class SongListItem(urwid.Pile):
|
|||
|
||||
STATE_ICONS = {
|
||||
0: ' ',
|
||||
1: '\u2505',
|
||||
2: '\u25B6',
|
||||
3: '\u25A0'
|
||||
1: u'\u2505',
|
||||
2: u'\u25B6',
|
||||
3: u'\u25A0'
|
||||
}
|
||||
|
||||
def __init__(self, track, index):
|
||||
|
@ -36,7 +37,7 @@ class SongListItem(urwid.Pile):
|
|||
'line1_focus'
|
||||
)
|
||||
|
||||
super().__init__([
|
||||
super(SongListItem, self).__init__([
|
||||
self.content
|
||||
])
|
||||
self.update_text()
|
||||
|
@ -50,8 +51,8 @@ class SongListItem(urwid.Pile):
|
|||
|
||||
def update_text(self):
|
||||
self.line1.set_text(
|
||||
'{index:3d} {icon} {title} [{minutes:02d}:{seconds:02d}]'.format(
|
||||
index=self.index,
|
||||
u'{index:3d} {icon} {title} [{minutes:02d}:{seconds:02d}]'.format(
|
||||
index=self.index + 1,
|
||||
icon=self.get_state_icon(self.state),
|
||||
title=self.track.title,
|
||||
minutes=self.track.duration // (1000 * 60),
|
||||
|
@ -59,7 +60,7 @@ class SongListItem(urwid.Pile):
|
|||
)
|
||||
)
|
||||
self.line2.set_text(
|
||||
' {}'.format(self.track.artist)
|
||||
u' {}'.format(self.track.artist)
|
||||
)
|
||||
if self.state == SongListItem.STATE_IDLE:
|
||||
self.content.set_attr('line1')
|
||||
|
@ -72,7 +73,7 @@ class SongListItem(urwid.Pile):
|
|||
if key == 'enter':
|
||||
urwid.emit_signal(self, 'activate', self)
|
||||
return
|
||||
return super().keypress(size, key)
|
||||
return super(SongListItem, self).keypress(size, key)
|
||||
|
||||
# def render(self, size, focus=False):
|
||||
# # if focus:
|
||||
|
@ -99,7 +100,7 @@ class SongListBox(urwid.ListBox):
|
|||
player.track_changed += self.track_changed
|
||||
player.media_state_changed += self.media_state_changed
|
||||
|
||||
return super().__init__(self.walker)
|
||||
return super(SongListBox, self).__init__(self.walker)
|
||||
|
||||
def tracks_to_songlist(self, tracks):
|
||||
current_track = player.get_current_track()
|
||||
|
@ -113,7 +114,7 @@ class SongListBox(urwid.ListBox):
|
|||
return items
|
||||
|
||||
def item_activated(self, songitem):
|
||||
player.load_playlist(self.tracks, songitem.index)
|
||||
player.load_queue(self.tracks, songitem.index)
|
||||
|
||||
def track_changed(self, track):
|
||||
for songitem in self.walker:
|
||||
|
|
Loading…
Add table
Reference in a new issue