1 parent
9c462679c8
commit
4c6976b2df
6 files changed
+152
-81
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
@@ -57,4 +57,26 @@ class NavidromeAPI:
|
|||||||
|
|
||||||
return [SubSonic.Song(song_d) for song_d in response.json()]
|
return [SubSonic.Song(song_d) for song_d in response.json()]
|
||||||
|
|
||||||
|
def getAlbums(self, end: int, start: int, order: str = 'ACS', sort: str = 'name', name: str | None = None, seed: float | None = None) -> list[SubSonic.Album]:
|
||||||
|
url = self.base_url + '/api/album?'
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"_end": end,
|
||||||
|
"_order": order,
|
||||||
|
"_sort" : sort,
|
||||||
|
"_start": start,
|
||||||
|
"seed": 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if seed:
|
||||||
|
payload['seed'] = seed
|
||||||
|
|
||||||
|
if name:
|
||||||
|
payload['name'] = name
|
||||||
|
|
||||||
|
response = requests.get(url + self._encodePayload(payload), headers=self.auth_header)
|
||||||
|
|
||||||
|
print(response.content)
|
||||||
|
|
||||||
|
return [SubSonic.Album(album_d) for album_d in response.json()]
|
||||||
|
|
||||||
@@ -36,3 +36,7 @@ Artists Page
|
|||||||
View of favorite songs
|
View of favorite songs
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
View of some random albums
|
||||||
|
|
||||||
|

|
||||||
+4
-6
@@ -32,7 +32,7 @@ class Album:
|
|||||||
name: str
|
name: str
|
||||||
album: str
|
album: str
|
||||||
artist: str
|
artist: str
|
||||||
coverArtId: str
|
coverArtId: str | None
|
||||||
duration: int
|
duration: int
|
||||||
artistId: str
|
artistId: str
|
||||||
songCount: int
|
songCount: int
|
||||||
@@ -42,11 +42,11 @@ class Album:
|
|||||||
|
|
||||||
def __init__(self, album_info: dict):
|
def __init__(self, album_info: dict):
|
||||||
self.id = album_info['id']
|
self.id = album_info['id']
|
||||||
self.title = album_info['title']
|
|
||||||
self.name = album_info['name']
|
self.name = album_info['name']
|
||||||
self.album = album_info['album']
|
self.title = album_info['title'] if 'title' in album_info else self.name
|
||||||
|
self.album = album_info['album'] if 'album' in album_info else self.name
|
||||||
self.artist = album_info['artist']
|
self.artist = album_info['artist']
|
||||||
self.coverArtId = album_info['coverArt']
|
self.coverArtId = album_info['coverArt'] if 'coverArt' in album_info else None
|
||||||
self.duration = int(album_info['duration'])
|
self.duration = int(album_info['duration'])
|
||||||
self.artistId = album_info['artistId']
|
self.artistId = album_info['artistId']
|
||||||
self.songCount = int(album_info['songCount'])
|
self.songCount = int(album_info['songCount'])
|
||||||
@@ -448,5 +448,3 @@ class SubpyConn:
|
|||||||
|
|
||||||
return random_songs
|
return random_songs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -561,11 +561,24 @@ class ArtistsGUI(Screen):
|
|||||||
artist_panel.set_artist(filtered_artists[self.artists_displayed])
|
artist_panel.set_artist(filtered_artists[self.artists_displayed])
|
||||||
self.artists_displayed += 1
|
self.artists_displayed += 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Albums(Screen):
|
class Albums(Screen):
|
||||||
|
|
||||||
albums: list[SubSonic.Album]
|
BINDINGS = [
|
||||||
|
("a", "page_backward()", "Prev. Page"),
|
||||||
|
("d", "page_forward()", "Next Page")
|
||||||
|
]
|
||||||
|
|
||||||
|
modes: list[str] = ['All', 'Random', 'Recently Added']
|
||||||
|
# list[page number, albums]
|
||||||
|
albums: list[tuple[int, list[SubSonic.Album]]] = [(0, []) for n in range(len(modes))]
|
||||||
|
mode: int = 0
|
||||||
|
page: list[int] = [0 for n in range(len(modes))]
|
||||||
|
DATATABLE_SIZE: int
|
||||||
|
seed: float = random.random()
|
||||||
|
last_mode: int = 0
|
||||||
|
|
||||||
|
SPACE_NEEDED: int = 12
|
||||||
|
search_value: str = ''
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Header(icon='')
|
yield Header(icon='')
|
||||||
@@ -575,21 +588,96 @@ class Albums(Screen):
|
|||||||
yield Label(Text("* F3 ", style="bold orange").append_text(Text("Albums", style="white")), classes="headerb")
|
yield Label(Text("* F3 ", style="bold orange").append_text(Text("Albums", style="white")), classes="headerb")
|
||||||
yield Label(Text("F4 ", style="bold orange").append_text(Text("Songs", style="white")), classes="headerb")
|
yield Label(Text("F4 ", style="bold orange").append_text(Text("Songs", style="white")), classes="headerb")
|
||||||
yield Label(Text("F5 ", style="bold orange").append_text(Text("Playlists", style="white")), classes="headerb")
|
yield Label(Text("F5 ", style="bold orange").append_text(Text("Playlists", style="white")), classes="headerb")
|
||||||
|
|
||||||
|
with Horizontal(id='albumScreen'):
|
||||||
|
yield ListView(
|
||||||
|
*[ListItem(Label(mode_name)) for mode_name in self.modes],
|
||||||
|
id='albumModeSelectorList'
|
||||||
|
)
|
||||||
|
|
||||||
with Vertical(id="albumContainer"):
|
with Vertical(id="albumContainer"):
|
||||||
yield Input(placeholder="Search", id="albumSearchBar")
|
yield Input(placeholder="Search", id="albumSearchBar")
|
||||||
yield DataTable(id="albumList")
|
yield DataTable(id="albumList")
|
||||||
|
yield Label(id="pager")
|
||||||
|
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
app.sub_title = "Albums"
|
app.sub_title = "Albums"
|
||||||
self.albums = conn.getAllAlbums()
|
dt = self.query_one(DataTable)
|
||||||
albumTable = self.query_one(DataTable)
|
|
||||||
|
|
||||||
albumTable.cursor_type = 'row'
|
dt.cursor_type = 'row'
|
||||||
albumTable.add_columns("Title", "Artist", "Songs", "Plays", "Year", "Time")
|
dt.add_columns("Title", "Artist", "Songs", "Plays", "Time")
|
||||||
|
|
||||||
for album in self.albums:
|
self.DATATABLE_SIZE = self.size.height - self.SPACE_NEEDED
|
||||||
albumTable.add_row(album.title, album.artist, album.songCount, album.playCount, album.year, album.duration, key=album.id)
|
self._update_datatable()
|
||||||
|
|
||||||
|
self._update_pager()
|
||||||
|
|
||||||
|
@on(events.Resize)
|
||||||
|
def on_resize(self) -> None:
|
||||||
|
self.DATATABLE_SIZE = self.size.height - self.SPACE_NEEDED
|
||||||
|
self._update_datatable()
|
||||||
|
|
||||||
|
def action_page_forward(self) -> None:
|
||||||
|
self.page[self.mode] += 1
|
||||||
|
self._update_datatable()
|
||||||
|
|
||||||
|
def action_page_backward(self) -> None:
|
||||||
|
if self.page[self.mode] - 1 < 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.page[self.mode] -= 1
|
||||||
|
self._update_datatable()
|
||||||
|
|
||||||
|
def get_current_page(self) -> int:
|
||||||
|
return self.page[self.mode]
|
||||||
|
|
||||||
|
def _get_offset(self) -> int:
|
||||||
|
return (self.get_current_page()) * self.DATATABLE_SIZE
|
||||||
|
|
||||||
|
def _update_pager(self) -> None:
|
||||||
|
pager = self.query_one("#pager", Label)
|
||||||
|
pager.update(f'Page {self.page[self.mode] + 1}')
|
||||||
|
|
||||||
|
def _update_datatable(self, force_update: bool = False) -> None:
|
||||||
|
dt = self.query_one(DataTable)
|
||||||
|
|
||||||
|
space = self.DATATABLE_SIZE
|
||||||
|
|
||||||
|
offset = self._get_offset()
|
||||||
|
current_page: int = self.get_current_page()
|
||||||
|
|
||||||
|
page_n, albums = self.albums[self.mode]
|
||||||
|
|
||||||
|
if self.page[self.mode] == 0 and self.mode == 1 and self.last_mode != 1:
|
||||||
|
self.seed = random.random()
|
||||||
|
albums = []
|
||||||
|
|
||||||
|
if page_n != current_page or not albums or len(albums) < space or force_update or current_page == 1:
|
||||||
|
|
||||||
|
if self.mode == 0:
|
||||||
|
albums = app.navidrome.getAlbums(offset + space, offset, name=self.search_value)
|
||||||
|
elif self.mode == 1:
|
||||||
|
albums = app.navidrome.getAlbums(offset + space, offset, sort='random', seed=self.seed, name=self.search_value)
|
||||||
|
elif self.mode == 2:
|
||||||
|
albums = app.navidrome.getAlbums(offset + space, offset, order='DESC', sort='recently_added', name=self.search_value)
|
||||||
|
|
||||||
|
self.albums[self.mode] = (current_page, albums)
|
||||||
|
|
||||||
|
dt.clear()
|
||||||
|
|
||||||
|
for album in albums[:space]:
|
||||||
|
dt.add_row(album.title, album.artist, album.songCount, album.playCount, album.duration, key=album.id)
|
||||||
|
|
||||||
|
self.last_mode = self.mode
|
||||||
|
|
||||||
|
self._update_pager()
|
||||||
|
|
||||||
|
@on(ListView.Selected)
|
||||||
|
def mode_selected(self, e: ListView.Selected) -> None:
|
||||||
|
self.mode = e.index
|
||||||
|
self._update_datatable()
|
||||||
|
|
||||||
@on(events.ScreenResume)
|
@on(events.ScreenResume)
|
||||||
def on_screen_resume(self, event: events.ScreenResume) -> None:
|
def on_screen_resume(self, event: events.ScreenResume) -> None:
|
||||||
@@ -602,14 +690,13 @@ class Albums(Screen):
|
|||||||
|
|
||||||
@on(Input.Submitted)
|
@on(Input.Submitted)
|
||||||
def search_submit(self, event: Input.Submitted) -> None:
|
def search_submit(self, event: Input.Submitted) -> None:
|
||||||
search_value = event.value.lower()
|
self.search_value = event.value.lower()
|
||||||
|
|
||||||
album_table = self.query_one(DataTable)
|
if self.search_value:
|
||||||
|
self.page[self.mode] = 0
|
||||||
|
|
||||||
|
self._update_datatable(force_update=True)
|
||||||
|
|
||||||
album_table.clear()
|
|
||||||
for album in self.albums:
|
|
||||||
if search_value in album.title.lower() or search_value in album.artist.lower():
|
|
||||||
album_table.add_row(album.title, album.artist, album.songCount, album.playCount, album.year, album.duration, key=album.id)
|
|
||||||
|
|
||||||
class Playlists(Screen):
|
class Playlists(Screen):
|
||||||
|
|
||||||
@@ -829,63 +916,6 @@ class SongsScreen(Screen):
|
|||||||
for song in self.songs:
|
for song in self.songs:
|
||||||
dt.add_row(song.title, song.album, song.artist, song.playCount, song.duration)
|
dt.add_row(song.title, song.album, song.artist, song.playCount, song.duration)
|
||||||
|
|
||||||
class Favourites(Screen):
|
|
||||||
BINDINGS = [
|
|
||||||
('p', 'play()', 'Play Song'),
|
|
||||||
('q', 'queue()', 'Queue Song'),
|
|
||||||
('m', 'shuffle_mix()', 'Shuffle Play')
|
|
||||||
]
|
|
||||||
|
|
||||||
songs: list[SubSonic.Song]
|
|
||||||
selected_song_id: str | None = None
|
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
|
||||||
yield Header(icon="")
|
|
||||||
with Horizontal(id="headerbs"):
|
|
||||||
yield Label(Text("F1 ", style="bold orange").append_text(Text("Player", style="white")), classes="headerb")
|
|
||||||
yield Label(Text("F2 ", style="bold orange").append_text(Text("Artists", style="white")), classes="headerb")
|
|
||||||
yield Label(Text("F3 ", style="bold orange").append_text(Text("Albums", style="white")), classes="headerb")
|
|
||||||
yield Label(Text("* F4 ", style="bold orange").append_text(Text("Songs", style="white")), classes="headerb")
|
|
||||||
yield Label(Text("F5 ", style="bold orange").append_text(Text("Playlists", style="white")), classes="headerb")
|
|
||||||
yield DataTable()
|
|
||||||
yield Footer()
|
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
|
||||||
app.sub_title = "Favourites"
|
|
||||||
self.songs = conn.getStarredSongs()
|
|
||||||
songTable = self.query_one(DataTable)
|
|
||||||
|
|
||||||
songTable.cursor_type = "row"
|
|
||||||
songTable.add_columns("Title", "Album", "Artist", "Plays", "Duration", "Bitrate")
|
|
||||||
|
|
||||||
for song in self.songs:
|
|
||||||
songTable.add_row(song.title, song.album, song.artist, song.playCount, song.duration, song.bitRate, key=song.id)
|
|
||||||
|
|
||||||
@on(DataTable.RowHighlighted)
|
|
||||||
def row_highlighted(self, event: DataTable.RowHighlighted) -> None:
|
|
||||||
self.selected_song_id = event.row_key.value
|
|
||||||
|
|
||||||
def action_play(self) -> None:
|
|
||||||
song_index = self._find_selected_song()
|
|
||||||
app.play_song(self.songs[song_index])
|
|
||||||
app.song_queue = self.songs[song_index + 1:]
|
|
||||||
|
|
||||||
def action_queue(self) -> None:
|
|
||||||
song_index = self._find_selected_song()
|
|
||||||
app.queue_song(self.songs[song_index])
|
|
||||||
|
|
||||||
# Returns index of selected song
|
|
||||||
def _find_selected_song(self) -> int:
|
|
||||||
for i, song in enumerate(self.songs):
|
|
||||||
if song.id == self.selected_song_id:
|
|
||||||
return i
|
|
||||||
|
|
||||||
def action_shuffle_mix(self) -> None:
|
|
||||||
q = self.songs.copy()
|
|
||||||
random.shuffle(q)
|
|
||||||
app.play_song(q.pop(0))
|
|
||||||
app.song_queue = q
|
|
||||||
|
|
||||||
class CoverArtPixels(Container):
|
class CoverArtPixels(Container):
|
||||||
|
|
||||||
cover = reactive[Image.Image | None](None)
|
cover = reactive[Image.Image | None](None)
|
||||||
|
|||||||
@@ -183,6 +183,23 @@ QueueScreen {
|
|||||||
width: 1fr;
|
width: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
# SongsScreen {
|
#albumModeSelectorList {
|
||||||
# background: red;
|
height: 100%;
|
||||||
# }
|
width: 0.2fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#albumModeSelectorList Label {
|
||||||
|
padding: 1 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#albumSearchBar {
|
||||||
|
margin: 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#albumList {
|
||||||
|
min-height: 1fr;
|
||||||
|
height: 1fr;
|
||||||
|
# border: white;
|
||||||
|
margin: 1 1;
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user