Cover art is working

This commit is contained in:
Sumner Evans
2019-06-22 20:48:04 -06:00
parent 1bbf2c0c73
commit 1f22e4f18d
6 changed files with 187 additions and 123 deletions

View File

@@ -149,7 +149,10 @@ class LibremsonicApp(Gtk.Application):
# Reset the CacheManager.
CacheManager.reset(
self.state.config.servers[self.state.config.current_server])
self.state.config,
self.state.config.servers[current_server]
if current_server >= 0 else None,
)
# Update the window according to the new server configuration.
self.update_window()

View File

@@ -1,6 +1,9 @@
from typing import Dict, List, Optional
import os
from pathlib import Path
from collections import defaultdict
from typing import DefaultDict, Dict, List, Optional, Tuple
from libremsonic.config import ServerConfiguration
from libremsonic.config import AppConfiguration, ServerConfiguration
from libremsonic.server import Server
from libremsonic.server.api_objects import Playlist, PlaylistWithSongs
@@ -18,7 +21,15 @@ class CacheManager(metaclass=Singleton):
playlists: Optional[List[Playlist]] = None
playlist_details: Dict[int, PlaylistWithSongs] = {}
def __init__(self, server_config: ServerConfiguration):
# {id -> {size -> file_location}}
cover_art: DefaultDict[str, Dict[str, Path]] = defaultdict(dict)
def __init__(
self,
app_config: AppConfiguration,
server_config: ServerConfiguration,
):
self.app_config = app_config
self.server = Server(
name=server_config.name,
hostname=server_config.server_address,
@@ -26,6 +37,16 @@ class CacheManager(metaclass=Singleton):
password=server_config.password,
)
def save_file(self, relative_path: str, data: bytes) -> Path:
cache_location = Path(self.app_config.cache_location)
absolute_path = cache_location.joinpath(relative_path)
# Make the necessary directories and write to file.
os.makedirs(absolute_path.parent, exist_ok=True)
with open(absolute_path, 'wb+') as f:
f.write(data)
return absolute_path
def get_playlists(self, force: bool = False) -> List[Playlist]:
if not self.playlists or force:
self.playlists = self.server.get_playlists().playlist
@@ -43,12 +64,25 @@ class CacheManager(metaclass=Singleton):
return self.playlist_details[playlist_id]
def get_cover_art(
self,
id: str,
size: str = '200',
force: bool = False,
) -> str:
if not self.cover_art[id].get(size):
raw_cover = self.server.get_cover_art(id, size)
abs_path = self.save_file(f'cover_art/{id}_{size}', raw_cover)
self.cover_art[id][size] = abs_path
return str(self.cover_art[id][size])
_instance: Optional[__CacheManagerInternal] = None
def __init__(self, server_config: ServerConfiguration):
raise Exception('Do not instantiate the CacheManager.')
@classmethod
def reset(cls, server_config):
def reset(cls, app_config, server_config):
CacheManager._instance = CacheManager.__CacheManagerInternal(
server_config)
app_config, server_config)

View File

@@ -39,16 +39,14 @@ class ServerConfiguration:
class AppConfiguration:
servers: List[ServerConfiguration]
current_server: int
permanent_cache_location: str
temporary_cache_location: str
cache_location: str
max_cache_size_mb: int # -1 means unlimited
def to_json(self):
return {
'servers': [s.__dict__ for s in self.servers],
'current_server': self.current_server,
'permanent_cache_location': self.permanent_cache_location,
'temporary_cache_location': self.temporary_cache_location,
'cache_location': self.cache_location,
'max_cache_size_mb': self.max_cache_size_mb,
}
@@ -61,8 +59,7 @@ class AppConfiguration:
config = AppConfiguration()
config.servers = []
config.current_server = -1
config.permanent_cache_location = default_cache_location
config.temporary_cache_location = config.permanent_cache_location
config.cache_location = default_cache_location
config.max_cache_size_mb = -1
return config

View File

@@ -73,22 +73,10 @@ class Server:
def _make_url(self, endpoint: str) -> str:
return f'{self.hostname}/rest/{endpoint}.view'
def _subsonic_error_to_exception(self, error):
def _subsonic_error_to_exception(self, error) -> Exception:
return Exception(f'{error.code}: {error.message}')
def _post(
self,
url: str,
**params: Union[None, str, datetime, int, List[int]],
) -> Response:
"""
Make a post to a *Sonic REST API. Handle all types of errors including
*Sonic ``<error>`` responses.
:returns: a Response containing all of the data of the
response, deserialized
:raises Exception: needs some work TODO
"""
def _post(self, url, **params):
params = {**self._get_params(), **params}
# Deal with datetime parameters (convert to milliseconds since 1970)
@@ -101,6 +89,22 @@ class Server:
if result.status_code != 200:
raise Exception(f'Fail! {result.status_code}')
return result
def _post_json(
self,
url: str,
**params: Union[None, str, datetime, int, List[int]],
) -> Response:
"""
Make a post to a *Sonic REST API. Handle all types of errors including
*Sonic ``<error>`` responses.
:returns: a Response containing all of the data of the
response, deserialized
:raises Exception: needs some work TODO
"""
result = self._post(url, **params)
subsonic_response = result.json()['subsonic-response']
# TODO make better
@@ -123,12 +127,7 @@ class Server:
"""
Stream a file.
"""
params = {**self._get_params(), **params}
result = requests.post(url, data=params, stream=True)
# TODO make better
if result.status_code != 200:
raise Exception(f'Fail! {result.status_code}')
result = self._post(url, **params)
content_type = result.headers.get('Content-Type', '')
if 'application/json' in content_type:
@@ -144,24 +143,27 @@ class Server:
else:
return result.iter_content(chunk_size=1024)
def _download(self, url, **params) -> bytes:
return self._post(url, **params).content
def ping(self) -> Response:
"""
Used to test connectivity with the server.
"""
return self._post(self._make_url('ping'))
return self._post_json(self._make_url('ping'))
def get_license(self) -> License:
"""
Get details about the software license.
"""
result = self._post(self._make_url('getLicense'))
result = self._post_json(self._make_url('getLicense'))
return result.license
def get_music_folders(self) -> MusicFolders:
"""
Returns all configured top-level music folders.
"""
result = self._post(self._make_url('getMusicFolders'))
result = self._post_json(self._make_url('getMusicFolders'))
return result.musicFolders
def get_indexes(
@@ -177,9 +179,9 @@ class Server:
:param if_modified_since: If specified, only return a result if the
artist collection has changed since the given time.
"""
result = self._post(self._make_url('getIndexes'),
musicFolderId=music_folder_id,
ifModifiedSince=if_modified_since)
result = self._post_json(self._make_url('getIndexes'),
musicFolderId=music_folder_id,
ifModifiedSince=if_modified_since)
return result.indexes
def get_music_directory(self, dir_id) -> Directory:
@@ -190,15 +192,15 @@ class Server:
:param dir_id: A string which uniquely identifies the music folder.
Obtained by calls to ``getIndexes`` or ``getMusicDirectory``.
"""
result = self._post(self._make_url('getMusicDirectory'),
id=str(dir_id))
result = self._post_json(self._make_url('getMusicDirectory'),
id=str(dir_id))
return result.directory
def get_genres(self) -> Genres:
"""
Returns all genres.
"""
result = self._post(self._make_url('getGenres'))
result = self._post_json(self._make_url('getGenres'))
return result.genres
def get_artists(self, music_folder_id: int = None) -> ArtistsID3:
@@ -208,8 +210,8 @@ class Server:
:param music_folder_id: If specified, only return artists in the music
folder with the given ID. See ``getMusicFolders``.
"""
result = self._post(self._make_url('getArtists'),
musicFolderId=music_folder_id)
result = self._post_json(self._make_url('getArtists'),
musicFolderId=music_folder_id)
return result.artists
def get_artist(self, artist_id: int) -> ArtistWithAlbumsID3:
@@ -219,7 +221,7 @@ class Server:
:param artist_id: The artist ID.
"""
result = self._post(self._make_url('getArtist'), id=artist_id)
result = self._post_json(self._make_url('getArtist'), id=artist_id)
return result.artist
def get_album(self, album_id: int) -> AlbumWithSongsID3:
@@ -229,7 +231,7 @@ class Server:
:param album_id: The album ID.
"""
result = self._post(self._make_url('getAlbum'), id=album_id)
result = self._post_json(self._make_url('getAlbum'), id=album_id)
return result.album
def get_song(self, song_id: int) -> Child:
@@ -238,14 +240,14 @@ class Server:
:param song_id: The song ID.
"""
result = self._post(self._make_url('getSong'), id=song_id)
result = self._post_json(self._make_url('getSong'), id=song_id)
return result.song
def get_videos(self) -> Optional[List[Child]]:
"""
Returns all video files.
"""
result = self._post(self._make_url('getVideos'))
result = self._post_json(self._make_url('getVideos'))
return result.videos.video
def get_video_info(self, video_id: int) -> Optional[VideoInfo]:
@@ -255,7 +257,7 @@ class Server:
:param video_id: The video ID.
"""
result = self._post(self._make_url('getVideoInfo'), id=video_id)
result = self._post_json(self._make_url('getVideoInfo'), id=video_id)
return result.videoInfo
def get_artist_info(
@@ -275,7 +277,7 @@ class Server:
present in the media library. Defaults to false according to API
Spec.
"""
result = self._post(
result = self._post_json(
self._make_url('getArtistInfo'),
id=id,
count=count,
@@ -299,7 +301,7 @@ class Server:
present in the media library. Defaults to false according to API
Spec.
"""
result = self._post(
result = self._post_json(
self._make_url('getArtistInfo2'),
id=id,
count=count,
@@ -313,7 +315,7 @@ class Server:
:param id: The album or song ID.
"""
result = self._post(self._make_url('getAlbumInfo'), id=id)
result = self._post_json(self._make_url('getAlbumInfo'), id=id)
return result.albumInfo
def get_album_info2(self, id: int) -> Optional[AlbumInfo]:
@@ -322,7 +324,7 @@ class Server:
:param id: The album or song ID.
"""
result = self._post(self._make_url('getAlbumInfo2'), id=id)
result = self._post_json(self._make_url('getAlbumInfo2'), id=id)
return result.albumInfo
def get_similar_songs(self, id: int, count: int = None) -> List[Child]:
@@ -335,7 +337,7 @@ class Server:
:param count: Max number of songs to return. Defaults to 50 according
to API Spec.
"""
result = self._post(
result = self._post_json(
self._make_url('getSimilarSongs'),
id=id,
count=count,
@@ -350,7 +352,7 @@ class Server:
:param count: Max number of songs to return. Defaults to 50 according
to API Spec.
"""
result = self._post(
result = self._post_json(
self._make_url('getSimilarSongs2'),
id=id,
count=count,
@@ -365,7 +367,7 @@ class Server:
:param count: Max number of songs to return. Defaults to 50 according
to API Spec.
"""
result = self._post(
result = self._post_json(
self._make_url('getTopSongs'),
artist=artist,
count=count,
@@ -407,7 +409,7 @@ class Server:
:param music_folder_id: (Since 1.11.0) Only return albums in the music
folder with the given ID. See ``getMusicFolders``.
"""
result = self._post(
result = self._post_json(
self._make_url('getAlbumList'),
type=type,
size=size,
@@ -452,7 +454,7 @@ class Server:
:param music_folder_id: (Since 1.11.0) Only return albums in the music
folder with the given ID. See ``getMusicFolders``.
"""
result = self._post(
result = self._post_json(
self._make_url('getAlbumList2'),
type=type,
size=size,
@@ -483,7 +485,7 @@ class Server:
:param music_folder_id: Only return albums in the music folder with the
given ID. See ``getMusicFolders``.
"""
result = self._post(
result = self._post_json(
self._make_url('getRandomSongs'),
size=size,
genre=genre,
@@ -511,7 +513,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) Only return albums in the music
folder with the given ID. See ``getMusicFolders``.
"""
result = self._post(
result = self._post_json(
self._make_url('getSongsByGenre'),
genre=genre,
count=count,
@@ -525,7 +527,7 @@ class Server:
Returns what is currently being played by all users. Takes no extra
parameters.
"""
result = self._post(self._make_url('getNowPlaying'))
result = self._post_json(self._make_url('getNowPlaying'))
return result.nowPlaying
def get_starred(self, music_folder_id: int = None) -> Starred:
@@ -535,7 +537,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) Only return results from the
music folder with the given ID. See ``getMusicFolders``.
"""
result = self._post(self._make_url('getStarred'))
result = self._post_json(self._make_url('getStarred'))
return result.starred
def get_starred2(self, music_folder_id: int = None) -> Starred2:
@@ -545,7 +547,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) Only return results from the
music folder with the given ID. See ``getMusicFolders``.
"""
result = self._post(self._make_url('getStarred2'))
result = self._post_json(self._make_url('getStarred2'))
return result.starred2
@deprecated(version='1.4.0', reason='You should use search2 instead.')
@@ -571,7 +573,7 @@ class Server:
:param offset: Search result offset. Used for paging.
:param newer_than: Only return matches that are newer than this.
"""
result = self._post(
result = self._post_json(
self._make_url('search'),
artist=artist,
album=album,
@@ -615,7 +617,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) Only return results from the
music folder with the given ID. See ``getMusicFolders``.
"""
result = self._post(
result = self._post_json(
self._make_url('search2'),
query=query,
artistCount=artist_count,
@@ -658,7 +660,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) Only return results from the
music folder with the given ID. See ``getMusicFolders``.
"""
result = self._post(
result = self._post_json(
self._make_url('search3'),
query=query,
artistCount=artist_count,
@@ -679,7 +681,8 @@ class Server:
user rather than for the authenticated user. The authenticated user
must have admin role if this parameter is used.
"""
result = self._post(self._make_url('getPlaylists'), username=username)
result = self._post_json(self._make_url('getPlaylists'),
username=username)
return result.playlists
def get_playlist(self, id: int = None) -> PlaylistWithSongs:
@@ -689,7 +692,7 @@ class Server:
:param username: ID of the playlist to return, as obtained by
``getPlaylists``.
"""
result = self._post(self._make_url('getPlaylist'), id=id)
result = self._post_json(self._make_url('getPlaylist'), id=id)
return result.playlist
def create_playlist(
@@ -707,7 +710,7 @@ class Server:
:param song_id: ID(s) of a song in the playlist. Can be a single ID or
a list of IDs.
"""
result = self._post(
result = self._post_json(
self._make_url('createPlaylist'),
playlistId=playlist_id,
name=name,
@@ -742,7 +745,7 @@ class Server:
:param song_id_to_remove: Remove the song at this/these position(s) in
the playlist. Can be a single ID or a list of IDs.
"""
return self._post(
return self._post_json(
self._make_url('updatePlaylist'),
playlistId=playlist_id,
name=name,
@@ -756,7 +759,7 @@ class Server:
"""
Deletes a saved playlist
"""
return self._post(self._make_url('deletePlaylist'), id=id)
return self._post_json(self._make_url('deletePlaylist'), id=id)
def stream(
self,
@@ -817,16 +820,16 @@ class Server:
Obtained by calls to ``getMusicDirectory``.
"""
# TODO make this a decent object
return self._post(self._make_url('download'), id=id)
return self._download(self._make_url('download'), id=id)
def get_cover_art(self, id: int, size: str = None):
def get_cover_art(self, id: str, size: str = None):
"""
Returns the cover art image in binary form.
:param id: The ID of a song, album or artist.
:param size: If specified, scale image to this size.
"""
return self._post(self._make_url('getCoverArt'), id=id, size=size)
return self._download(self._make_url('getCoverArt'), id=id, size=size)
def get_lyrics(self, artist: str = None, title: str = None) -> Lyrics:
"""
@@ -835,7 +838,7 @@ class Server:
:param artist: The artist name.
:param title: The song title.
"""
result = self._post(
result = self._post_json(
self._make_url('getLyrics'),
artist=artist,
title=title,
@@ -848,7 +851,7 @@ class Server:
:param username: the user in question.
"""
return self._post(self._make_url('getAvatar'), username=username)
return self._download(self._make_url('getAvatar'), username=username)
def star(
self,
@@ -870,7 +873,7 @@ class Server:
according to ID3 tags rather than file structure. Can be a single
ID or a list of IDs.
"""
return self._post(
return self._post_json(
self._make_url('star'),
id=id,
albumId=album_id,
@@ -897,7 +900,7 @@ class Server:
according to ID3 tags rather than file structure. Can be a single
ID or a list of IDs.
"""
return self._post(
return self._post_json(
self._make_url('unstar'),
id=id,
albumId=album_id,
@@ -913,7 +916,9 @@ class Server:
:param rating: The rating between 1 and 5 (inclusive), or 0 to remove
the rating.
"""
return self._post(self._make_url('setRating'), id=id, rating=rating)
return self._post_json(self._make_url('setRating'),
id=id,
rating=rating)
def scrobble(
self,
@@ -943,7 +948,7 @@ class Server:
:param submission: Whether this is a "submission" or a "now playing"
notification.
"""
return self._post(
return self._post_json(
self._make_url('scrobble'),
id=id,
time=time,
@@ -955,7 +960,7 @@ class Server:
Returns information about shared media this user is allowed to manage.
Takes no extra parameters.
"""
result = self._post(self._make_url('getShares'))
result = self._post_json(self._make_url('getShares'))
return result.shares
def create_share(
@@ -977,7 +982,7 @@ class Server:
to people visiting the shared media.
:param expires: The time at which the share expires.
"""
result = self._post(
result = self._post_json(
self._make_url('createShare'),
id=id,
description=description,
@@ -999,7 +1004,7 @@ class Server:
to people visiting the shared media.
:param expires: The time at which the share expires.
"""
return self._post(
return self._post_json(
self._make_url('updateShare'),
id=id,
description=description,
@@ -1012,13 +1017,13 @@ class Server:
:param id: ID of the share to delete.
"""
return self._post(self._make_url('deleteShare'), id=id)
return self._post_json(self._make_url('deleteShare'), id=id)
def get_internet_radio_stations(self) -> InternetRadioStations:
"""
Returns all internet radio stations. Takes no extra parameters.
"""
result = self._post(self._make_url('getInternetRadioStations'))
result = self._post_json(self._make_url('getInternetRadioStations'))
return result.internetRadioStations
def create_internet_radio_station(
@@ -1035,7 +1040,7 @@ class Server:
:param name: The user-defined name for the station.
:param homepage_url: The home page URL for the station.
"""
return self._post(
return self._post_json(
self._make_url('createInternetRadioStation'),
streamUrl=stream_url,
name=name,
@@ -1058,7 +1063,7 @@ class Server:
:param name: The user-defined name for the station.
:param homepage_url: The home page URL for the station.
"""
return self._post(
return self._post_json(
self._make_url('updateInternetRadioStation'),
id=id,
streamUrl=stream_url,
@@ -1073,7 +1078,8 @@ class Server:
:param id: The ID for the station.
"""
return self._post(self._make_url('deleteInternetRadioStation'), id=id)
return self._post_json(self._make_url('deleteInternetRadioStation'),
id=id)
def get_user(self, username: str) -> User:
"""
@@ -1084,7 +1090,7 @@ class Server:
:param username: The name of the user to retrieve. You can only
retrieve your own user unless you have admin privileges.
"""
result = self._post(self._make_url('getUser'), username=username)
result = self._post_json(self._make_url('getUser'), username=username)
return result.user
def get_users(self) -> Users:
@@ -1093,7 +1099,7 @@ class Server:
folder access they have. Only users with admin privileges are allowed
to call this method.
"""
result = self._post(self._make_url('getUsers'))
result = self._post_json(self._make_url('getUsers'))
return result.users
def create_user(
@@ -1147,7 +1153,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) IDs of the music folders the
user is allowed access to. Can be a single ID or a list of IDs.
"""
return self._post(
return self._post_json(
self._make_url('createUser'),
username=username,
password=password,
@@ -1219,7 +1225,7 @@ class Server:
:param music_folder_id: (Since 1.12.0) IDs of the music folders the
user is allowed access to. Can be a single ID or a list of IDs.
"""
return self._post(
return self._post_json(
self._make_url('updateUser'),
username=username,
password=password,
@@ -1246,7 +1252,7 @@ class Server:
:param username: The name of the new user.
"""
return self._post(self._make_url('deleteUser'), username=username)
return self._post_json(self._make_url('deleteUser'), username=username)
def change_password(self, username: str, password: str) -> Response:
"""
@@ -1257,7 +1263,7 @@ class Server:
:param password: The new password of the new user, either in clear text
of hex-encoded.
"""
return self._post(
return self._post_json(
self._make_url('changePassword'),
username=username,
password=password,
@@ -1268,7 +1274,7 @@ class Server:
Returns all bookmarks for this user. A bookmark is a position within a
certain media file.
"""
result = self._post(self._make_url('getBookmarks'))
result = self._post_json(self._make_url('getBookmarks'))
return result.bookmarks
def create_bookmarks(
@@ -1286,7 +1292,7 @@ class Server:
:param position: The position (in milliseconds) within the media file.
:param comment: A user-defined comment.
"""
return self._post(
return self._post_json(
self._make_url('createBookmark'),
id=id,
position=position,
@@ -1300,7 +1306,7 @@ class Server:
:param id: ID of the media file for which to delete the bookmark. Other
users' bookmarks are not affected.
"""
return self._post(self._make_url('deleteBookmark'), id=id)
return self._post_json(self._make_url('deleteBookmark'), id=id)
def get_play_queue(self) -> Optional[PlayQueue]:
"""
@@ -1311,10 +1317,10 @@ class Server:
retaining the same play queue (for instance when listening to an audio
book).
"""
result = self._post(self._make_url('getPlayQueue'))
result = self._post_json(self._make_url('getPlayQueue'))
return result.playQueue
def savePlayQueue(
def save_play_queue(
self,
id: Union[int, List[int]],
current: int = None,
@@ -1333,7 +1339,7 @@ class Server:
:param position: The position in milliseconds within the currently
playing song.
"""
return self._post(
return self._post_json(
self._make_url('savePlayQueue'),
id=id,
current=current,
@@ -1345,12 +1351,12 @@ class Server:
Returns the current status for media library scanning. Takes no extra
parameters.
"""
result = self._post(self._make_url('getScanStatus'))
result = self._post_json(self._make_url('getScanStatus'))
return result.scanStatus
def start_scan(self) -> ScanStatus:
"""
Initiates a rescan of the media libraries. Takes no extra parameters.
"""
result = self._post(self._make_url('startScan'))
result = self._post_json(self._make_url('startScan'))
return result.scanStatus

View File

@@ -2,6 +2,7 @@
#album-artwork {
min-height: 200px;
min-width: 200px;
margin: 10px;
}
#album-artwork.collapsed {

View File

@@ -65,25 +65,22 @@ class PlaylistsPanel(Gtk.Paned):
orientation=Gtk.Orientation.HORIZONTAL,
)
self.playlist_artwork = Gtk.Image(name='album-artwork')
self.info_panel.pack_start(self.playlist_artwork, False, False, 10)
self.info_panel.pack_start(self.playlist_artwork, False, False, 0)
# Name, comment, number of songs, etc.
playlist_details_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
playlist_details_box.pack_start(Gtk.Box(), True, False, 0)
def make_label(name):
return Gtk.Label(name=name, halign=Gtk.Align.START)
self.playlist_indicator = make_label('playlist-indicator')
self.playlist_indicator = self.make_label(name='playlist-indicator')
playlist_details_box.add(self.playlist_indicator)
self.playlist_name = make_label('playlist-name')
self.playlist_name = self.make_label(name='playlist-name')
playlist_details_box.add(self.playlist_name)
self.playlist_comment = make_label('playlist-comment')
self.playlist_comment = self.make_label(name='playlist-comment')
playlist_details_box.add(self.playlist_comment)
self.playlist_stats = make_label('playlist-stats')
self.playlist_stats = self.make_label(name='playlist-stats')
playlist_details_box.add(self.playlist_stats)
self.info_panel.pack_start(playlist_details_box, True, True, 10)
@@ -92,7 +89,17 @@ class PlaylistsPanel(Gtk.Paned):
# Playlist songs list
songs_scroll_window = Gtk.ScrolledWindow()
self.playlist_songs = Gtk.ListBox()
self.playlist_songs = Gtk.TreeView()
self.playlist_songs.insert_column_with_attributes(
-1, 'TITLE', Gtk.CellRendererText())
self.playlist_songs.insert_column_with_attributes(
-1, 'ALBUM', Gtk.CellRendererText())
self.playlist_songs.insert_column_with_attributes(
-1, 'ARTIST', Gtk.CellRendererText())
self.playlist_songs.insert_column_with_attributes(
-1, 'DURATION', Gtk.CellRendererText())
self.playlist_songs.connect(
'row-activated', lambda x, y: print('a', x, y))
songs_scroll_window.add(self.playlist_songs)
playlist_box.pack_end(songs_scroll_window, True, True, 0)
@@ -104,7 +111,9 @@ class PlaylistsPanel(Gtk.Paned):
playlist_id = self.playlist_ids[row.get_index()]
playlist: PlaylistWithSongs = CacheManager.get_playlist(playlist_id)
# Update the Playlist Name
# Update the Playlist Info panel
self.playlist_artwork.set_from_file(
CacheManager.get_cover_art(playlist.coverArt))
self.playlist_indicator.set_markup('PLAYLIST')
self.playlist_name.set_markup(f'<b>{playlist.name}</b>')
self.playlist_comment.set_text(playlist.comment or '')
@@ -126,6 +135,9 @@ class PlaylistsPanel(Gtk.Paned):
# Helper Methods
# =========================================================================
def make_label(self, text=None, name=None, **params):
return Gtk.Label(text, name=name, halign=Gtk.Align.START, **params)
def update(self, state: ApplicationState):
self.update_playlist_list()
@@ -141,20 +153,28 @@ class PlaylistsPanel(Gtk.Paned):
self.playlist_list.show_all()
def create_playlist_label(self, playlist: PlaylistWithSongs):
return Gtk.Label(
f'<b>{playlist.name}</b>',
halign=Gtk.Align.START,
use_markup=True,
margin=10,
)
return self.make_label(f'<b>{playlist.name}</b>',
use_markup=True,
margin=10)
def create_song_row(self, song: Child):
return Gtk.Label(
f'<b>{song.title}</b>',
halign=Gtk.Align.START,
use_markup=True,
margin=5,
)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
title = self.make_label(f'<b>{song.title}</b>',
use_markup=True,
margin=5)
box.pack_start(title, True, True, 0)
album = self.make_label(song.album, margin=5)
box.pack_start(album, True, True, 0)
artist = self.make_label(song.artist, margin=5)
box.pack_start(artist, True, True, 0)
duration = self.make_label(self.format_song_duration(song.duration),
margin=5)
box.pack_start(duration, False, True, 0)
return box
def pluralize(self, string, number, pluralized_form=None):
if number != 1:
@@ -193,3 +213,6 @@ class PlaylistsPanel(Gtk.Paned):
format_components.append(secs)
return ', '.join(format_components)
def format_song_duration(self, duration_secs) -> str:
return f'{duration_secs // 60}:{duration_secs % 60:02}'