mirror of
https://github.com/vale981/clay
synced 2025-03-04 17:11:41 -05:00
Upd
This commit is contained in:
parent
357ab3bd73
commit
556bf51d1f
6 changed files with 88 additions and 29 deletions
27
README.md
27
README.md
|
@ -24,15 +24,15 @@ It's being actively developed, but is still in the early alpha version, so many
|
|||
- Playback
|
||||
- Music library
|
||||
- Playlists
|
||||
- Queue
|
||||
- Radio stations
|
||||
- Queue management
|
||||
- Notifications
|
||||
- Configuration
|
||||
- Caching (partially)
|
||||
- Basic error handling
|
||||
|
||||
# What is being developed
|
||||
|
||||
- Search
|
||||
- Add to playlist
|
||||
- Like/dislike
|
||||
- Caching
|
||||
- Other functionality that is supported by [gmusicapi]
|
||||
|
@ -83,14 +83,31 @@ Also be aware that this app has not been tested with 2FA yet.
|
|||
|
||||
# Controls
|
||||
|
||||
## General
|
||||
|
||||
- `<UP|DOWN|LEFT|RIGHT>` - nagivate around
|
||||
- `<ALT> + 1..9` - switch active tab
|
||||
- `<ENTER>` - play selected track
|
||||
|
||||
## Songs
|
||||
|
||||
- `<ENTER>` - play highlighted track
|
||||
- `<CTRL> w` - play/pause
|
||||
- `<CTRL> e` - play next song
|
||||
- `<SHIFT> <LEFT|RIGHT>` - seek backward/forward by 5% of the song duration
|
||||
- `<CTRL> a` - append highlighted song to the queue
|
||||
- `<CTRL> u` - remove highlighted song from the queue
|
||||
- `<CTRL> p` - start station from highlighted song
|
||||
|
||||
## Playback
|
||||
|
||||
- `<CTRL> s` - toggle shuffle
|
||||
- `<CTRL> r` - toggle song repeat
|
||||
- `<SHIFT> <LEFT|RIGHT>` - seek backward/forward by 5% of the song duration
|
||||
- `<CTRL> q` - seek to song beginning
|
||||
|
||||
## Misc
|
||||
|
||||
- `<ESC>` - close most recent notification (we should remap this to something else)
|
||||
- `<CTRL> x` - exit app
|
||||
|
||||
# Credits
|
||||
|
||||
|
|
|
@ -238,7 +238,7 @@ class AppWidget(urwid.Frame):
|
|||
player.set_random(not player.get_is_random())
|
||||
elif key == 'ctrl r':
|
||||
player.set_repeat_one(not player.get_is_repeat_one())
|
||||
elif key == 'ctrl q':
|
||||
elif key == 'ctrl x':
|
||||
sys.exit(0)
|
||||
elif key == 'esc':
|
||||
NotificationArea.close_newest()
|
||||
|
|
62
clay/gp.py
62
clay/gp.py
|
@ -36,27 +36,49 @@ def synchronized(fn):
|
|||
|
||||
|
||||
class Track(object):
|
||||
def __init__(self, id, title, artist, duration):
|
||||
TYPE_UPLOADED = 'uploaded'
|
||||
TYPE_STORE = 'store'
|
||||
|
||||
def __init__(self, id, title, artist, duration, type):
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.artist = artist
|
||||
self.duration = duration
|
||||
self.type = type
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data, many=False):
|
||||
if many:
|
||||
return [cls.from_data(one) for one in data]
|
||||
|
||||
if 'id' in data:
|
||||
track_id = data['id']
|
||||
track_type = 'uploaded'
|
||||
elif 'storeId' in data:
|
||||
track_id = data['storeId']
|
||||
track_type = 'store'
|
||||
else:
|
||||
raise Exception('Track is missing both "id" and "storeId"! Where does it come from?')
|
||||
|
||||
return Track(
|
||||
id=data['id'],
|
||||
id=track_id,
|
||||
title=data['title'],
|
||||
artist=data['artist'],
|
||||
duration=int(data['durationMillis'])
|
||||
duration=int(data['durationMillis']),
|
||||
type=track_type
|
||||
)
|
||||
|
||||
def get_url(self, callback):
|
||||
gp.get_stream_url(self.id, callback=callback, extra=dict(track=self))
|
||||
|
||||
@async
|
||||
@synchronized
|
||||
def create_station(self):
|
||||
station_id = gp.mc.create_station(name=u'Station - {}'.format(self.title), track_id=self.id)
|
||||
station = Station(station_id)
|
||||
station.load_tracks()
|
||||
return station
|
||||
|
||||
|
||||
class Playlist(object):
|
||||
def __init__(self, id, name, tracks):
|
||||
|
@ -91,13 +113,18 @@ class Playlist(object):
|
|||
|
||||
|
||||
class Station(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
self.tracks = []
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data):
|
||||
print(data)
|
||||
raise Exception(str(data))
|
||||
def load_tracks(self):
|
||||
data = gp.mc.get_station_tracks(self.id, 100)
|
||||
# import json
|
||||
# raise Exception(json.dumps(data, indent=4))
|
||||
self.tracks = Track.from_data(data, many=True)
|
||||
|
||||
def get_tracks(self):
|
||||
return self.tracks
|
||||
|
||||
|
||||
class GP(object):
|
||||
|
@ -149,14 +176,15 @@ class GP(object):
|
|||
)
|
||||
return self.cached_playlists
|
||||
|
||||
@async
|
||||
@synchronized
|
||||
def create_station(self, name, track_id=None, artist_id=None, album_id=None, genre_id=None):
|
||||
kwargs = dict(track_id=track_id, artist_id=artist_id, album_id=album_id, genre_id=genre_id)
|
||||
# kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
||||
# if len(kwargs) != 1:
|
||||
# raise Exception('Must provide one of artist_id, album_id or genre_id')
|
||||
return Station.from_data(self.mc.create_station(name, **kwargs))
|
||||
# @async
|
||||
# @synchronized
|
||||
# def create_station(self, name, track_id=None, artist_id=None, album_id=None, genre_id=None):
|
||||
# kwargs = dict(track_id=track_id, artist_id=artist_id, album_id=album_id, genre_id=genre_id)
|
||||
# # kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
||||
# # if len(kwargs) != 1:
|
||||
# # raise Exception('Must provide one of artist_id, album_id or genre_id')
|
||||
# station_id = Station.from_data(self.mc.create_station(name, **kwargs))
|
||||
# return station_id
|
||||
|
||||
def get_cached_tracks_map(self):
|
||||
return {track.id: track for track in self.cached_tracks}
|
||||
|
|
|
@ -19,7 +19,7 @@ class Notification(urwid.Columns):
|
|||
])
|
||||
|
||||
def update(self, message):
|
||||
self.text.text = Notification.TEMPLATE.format(message)
|
||||
self.text.set_text(Notification.TEMPLATE.format(message))
|
||||
|
||||
def close(self):
|
||||
for notification, props in reversed(self.area.contents):
|
||||
|
|
|
@ -18,7 +18,7 @@ class Queue(object):
|
|||
def load(self, tracks, current_track=None):
|
||||
self.tracks = tracks[:]
|
||||
if current_track is None and len(self.tracks):
|
||||
current_track = self.tracks[0]
|
||||
current_track = 0
|
||||
self.current_track = current_track
|
||||
|
||||
def append(self, track):
|
||||
|
@ -126,7 +126,7 @@ class Player(object):
|
|||
self.get_play_progress()
|
||||
)
|
||||
|
||||
def load_queue(self, data, current_index):
|
||||
def load_queue(self, data, current_index=None):
|
||||
self.queue.load(data, current_index)
|
||||
self.queue_changed.fire()
|
||||
self._play()
|
||||
|
@ -141,6 +141,22 @@ class Player(object):
|
|||
self.queue_changed.fire()
|
||||
self.track_removed.fire(track)
|
||||
|
||||
def create_station_from_track(self, track):
|
||||
self._create_station_notification = NotificationArea.notify('Creating station...')
|
||||
track.create_station(callback=self._create_station_from_track_ready)
|
||||
|
||||
def _create_station_from_track_ready(self, station, error):
|
||||
if error:
|
||||
self._create_station_notification.update('Failed to create station: {}'.format(str(error)))
|
||||
return
|
||||
|
||||
if not station.get_tracks():
|
||||
self._create_station_notification.update('Newly created station is empty :(')
|
||||
return
|
||||
|
||||
self.load_queue(station.get_tracks())
|
||||
self._create_station_notification.update('Station ready!')
|
||||
|
||||
def get_is_random(self):
|
||||
return self.queue.random
|
||||
|
||||
|
|
|
@ -170,9 +170,7 @@ class SongListBox(urwid.ListBox):
|
|||
player.remove_from_queue(songitem.track)
|
||||
|
||||
def item_station_requested(self, songitem):
|
||||
# TODO: Implement me
|
||||
# player.start_station()
|
||||
pass
|
||||
player.create_station_from_track(songitem.track)
|
||||
|
||||
def track_changed(self, track):
|
||||
for i, songitem in enumerate(self.walker):
|
||||
|
|
Loading…
Add table
Reference in a new issue