mirror of
https://github.com/SoongNoonien/mpdevil.git
synced 2023-08-10 21:12:44 +03:00
reworked browser to support "*sort" tags
This commit is contained in:
parent
237bd3bd92
commit
432957e291
353
bin/mpdevil
353
bin/mpdevil
|
@ -557,10 +557,13 @@ class Song(collections.UserDict):
|
||||||
def __missing__(self, key):
|
def __missing__(self, key):
|
||||||
if self.data:
|
if self.data:
|
||||||
if key == "albumartist":
|
if key == "albumartist":
|
||||||
if "artist" in self.data:
|
return self["artist"]
|
||||||
return self.data["artist"]
|
elif key == "albumartistsort":
|
||||||
else:
|
return self["albumartist"]
|
||||||
return MultiTag([""])
|
elif key == "artistsort":
|
||||||
|
return self["artist"]
|
||||||
|
elif key == "albumsort":
|
||||||
|
return self["album"]
|
||||||
elif key == "title":
|
elif key == "title":
|
||||||
return MultiTag([os.path.basename(self.data["file"])])
|
return MultiTag([os.path.basename(self.data["file"])])
|
||||||
elif key == "duration":
|
elif key == "duration":
|
||||||
|
@ -627,6 +630,36 @@ class Client(MPDClient):
|
||||||
# connect
|
# connect
|
||||||
self._settings.connect("changed::active-profile", self._on_active_profile_changed)
|
self._settings.connect("changed::active-profile", self._on_active_profile_changed)
|
||||||
|
|
||||||
|
# workaround for list group
|
||||||
|
# see: https://github.com/Mic92/python-mpd2/pull/187
|
||||||
|
def _parse_objects(self, lines, delimiters=[], lookup_delimiter=False):
|
||||||
|
obj = {}
|
||||||
|
for key, value in self._parse_pairs(lines):
|
||||||
|
key = key.lower()
|
||||||
|
if lookup_delimiter and key not in delimiters:
|
||||||
|
delimiters = delimiters + [key]
|
||||||
|
if obj:
|
||||||
|
if key in delimiters:
|
||||||
|
if lookup_delimiter:
|
||||||
|
if key in obj:
|
||||||
|
yield obj
|
||||||
|
obj = obj.copy()
|
||||||
|
while delimiters[-1] != key:
|
||||||
|
obj.pop(delimiters[-1], None)
|
||||||
|
delimiters.pop()
|
||||||
|
else:
|
||||||
|
yield obj
|
||||||
|
obj = {}
|
||||||
|
elif key in obj:
|
||||||
|
if not isinstance(obj[key], list):
|
||||||
|
obj[key] = [obj[key], value]
|
||||||
|
else:
|
||||||
|
obj[key].append(value)
|
||||||
|
continue
|
||||||
|
obj[key] = value
|
||||||
|
if obj:
|
||||||
|
yield obj
|
||||||
|
|
||||||
# overloads
|
# overloads
|
||||||
def currentsong(self, *args):
|
def currentsong(self, *args):
|
||||||
return Song(super().currentsong(*args))
|
return Song(super().currentsong(*args))
|
||||||
|
@ -690,70 +723,12 @@ class Client(MPDClient):
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def files_to_playlist(self, files, mode="default"): # modes: default, play, append, enqueue
|
def _to_playlist(self, append, mode="default"): # modes: default, play, append, enqueue
|
||||||
def append(files):
|
if mode == "default":
|
||||||
for f in files:
|
|
||||||
self.add(f)
|
|
||||||
def play(files):
|
|
||||||
if files:
|
|
||||||
self.clear()
|
|
||||||
for f in files:
|
|
||||||
self.add(f)
|
|
||||||
self.play()
|
|
||||||
def enqueue(files):
|
|
||||||
status=self.status()
|
|
||||||
if status["state"] == "stop":
|
|
||||||
self.clear()
|
|
||||||
append(files)
|
|
||||||
else:
|
|
||||||
self.moveid(status["songid"], 0)
|
|
||||||
current_song_file=self.currentsong()["file"]
|
|
||||||
try:
|
|
||||||
self.delete((1,)) # delete all songs, but the first. bad song index possible
|
|
||||||
except MPDBase.CommandError:
|
|
||||||
pass
|
|
||||||
for f in files:
|
|
||||||
if f == current_song_file:
|
|
||||||
self.move(0, (int(self.status()["playlistlength"])-1))
|
|
||||||
else:
|
|
||||||
self.add(f)
|
|
||||||
if mode == "append":
|
|
||||||
append(files)
|
|
||||||
elif mode == "enqueue":
|
|
||||||
enqueue(files)
|
|
||||||
elif mode == "play":
|
|
||||||
play(files)
|
|
||||||
elif mode == "default":
|
|
||||||
if self._settings.get_boolean("force-mode"):
|
if self._settings.get_boolean("force-mode"):
|
||||||
play(files)
|
mode="play"
|
||||||
else:
|
else:
|
||||||
enqueue(files)
|
mode="enqueue"
|
||||||
|
|
||||||
def album_to_playlist(self, album, artist, year, genre, mode="default"):
|
|
||||||
if genre is None:
|
|
||||||
genre_filter=()
|
|
||||||
else:
|
|
||||||
genre_filter=("genre", genre)
|
|
||||||
songs=self.find("album", album, "date", year, self._settings.get_artist_type(), artist, *genre_filter)
|
|
||||||
self.files_to_playlist([song["file"] for song in songs], mode)
|
|
||||||
|
|
||||||
def artist_to_playlist(self, artist, genre, mode):
|
|
||||||
def append():
|
|
||||||
if self._settings.get_boolean("sort-albums-by-year"):
|
|
||||||
sort_tag="date"
|
|
||||||
else:
|
|
||||||
sort_tag="album"
|
|
||||||
if artist is None: # treat 'None' as 'all artists'
|
|
||||||
if genre is None:
|
|
||||||
self.searchadd("any", "", "sort", sort_tag)
|
|
||||||
else:
|
|
||||||
self.findadd("genre", genre, "sort", sort_tag)
|
|
||||||
else:
|
|
||||||
artist_type=self._settings.get_artist_type()
|
|
||||||
if genre is None:
|
|
||||||
self.findadd(artist_type, artist, "sort", sort_tag)
|
|
||||||
else:
|
|
||||||
self.findadd(artist_type, artist, "genre", genre, "sort", sort_tag)
|
|
||||||
if mode == "append":
|
if mode == "append":
|
||||||
append()
|
append()
|
||||||
elif mode == "play":
|
elif mode == "play":
|
||||||
|
@ -778,6 +753,32 @@ class Client(MPDClient):
|
||||||
self.move(0, duplicates[1]["pos"])
|
self.move(0, duplicates[1]["pos"])
|
||||||
self.delete(int(duplicates[1]["pos"])-1)
|
self.delete(int(duplicates[1]["pos"])-1)
|
||||||
|
|
||||||
|
|
||||||
|
def files_to_playlist(self, files, mode="default"):
|
||||||
|
def append():
|
||||||
|
for f in files:
|
||||||
|
self.add(f)
|
||||||
|
self._to_playlist(append, mode)
|
||||||
|
|
||||||
|
def filter_to_playlist(self, tag_filter, mode="default"):
|
||||||
|
def append():
|
||||||
|
if tag_filter:
|
||||||
|
self.findadd(*tag_filter)
|
||||||
|
else:
|
||||||
|
self.searchadd("any", "")
|
||||||
|
self._to_playlist(append, mode)
|
||||||
|
|
||||||
|
def album_to_playlist(self, albumartist, albumartistsort, album, albumsort, date, mode="default"):
|
||||||
|
tag_filter=("albumartist", albumartist, "albumartistsort", albumartistsort, "album", album, "albumsort", albumsort, "date", date)
|
||||||
|
self.filter_to_playlist(tag_filter, mode)
|
||||||
|
|
||||||
|
def artist_to_playlist(self, artist, genre, mode="default"):
|
||||||
|
def append():
|
||||||
|
for album in self.get_albums(artist, genre):
|
||||||
|
self.findadd("albumartist", album["albumartist"], "albumartistsort", album["albumartistsort"],
|
||||||
|
"album", album["album"], "albumsort", album["albumsort"], "date", album["date"])
|
||||||
|
self._to_playlist(append, mode)
|
||||||
|
|
||||||
def comp_list(self, *args): # simulates listing behavior of python-mpd2 1.0
|
def comp_list(self, *args): # simulates listing behavior of python-mpd2 1.0
|
||||||
native_list=self.list(*args)
|
native_list=self.list(*args)
|
||||||
if len(native_list) > 0:
|
if len(native_list) > 0:
|
||||||
|
@ -788,6 +789,33 @@ class Client(MPDClient):
|
||||||
else:
|
else:
|
||||||
return([])
|
return([])
|
||||||
|
|
||||||
|
def get_artists(self, genre):
|
||||||
|
if genre is None:
|
||||||
|
artists=self.list("albumartist", "group", "albumartistsort")
|
||||||
|
else:
|
||||||
|
artists=self.list("albumartist", "genre", genre, "group", "albumartistsort")
|
||||||
|
return [(artist["albumartist"], artist["albumartistsort"]) for artist in artists]
|
||||||
|
|
||||||
|
def get_albums(self, artist, genre):
|
||||||
|
if genre is None:
|
||||||
|
genre_filter=()
|
||||||
|
else:
|
||||||
|
genre_filter=("genre", genre)
|
||||||
|
if artist is None:
|
||||||
|
artists=self.get_artists(genre)
|
||||||
|
else:
|
||||||
|
artists=[artist]
|
||||||
|
albums=[]
|
||||||
|
for albumartist, albumartistsort in artists:
|
||||||
|
albums0=self.list(
|
||||||
|
"album", "albumartist", albumartist, "albumartistsort", albumartistsort,
|
||||||
|
*genre_filter, "group", "date", "group", "albumsort")
|
||||||
|
for album in albums0:
|
||||||
|
album["albumartist"]=albumartist
|
||||||
|
album["albumartistsort"]=albumartistsort
|
||||||
|
albums+=albums0
|
||||||
|
return albums
|
||||||
|
|
||||||
def get_cover_path(self, song):
|
def get_cover_path(self, song):
|
||||||
path=None
|
path=None
|
||||||
song_file=song["file"]
|
song_file=song["file"]
|
||||||
|
@ -963,12 +991,6 @@ class Settings(Gio.Settings):
|
||||||
array[pos]=value
|
array[pos]=value
|
||||||
self.set_value(key, GLib.Variant(vtype, array))
|
self.set_value(key, GLib.Variant(vtype, array))
|
||||||
|
|
||||||
def get_artist_type(self):
|
|
||||||
if self.get_boolean("use-album-artist"):
|
|
||||||
return "albumartist"
|
|
||||||
else:
|
|
||||||
return "artist"
|
|
||||||
|
|
||||||
def get_profile(self, num):
|
def get_profile(self, num):
|
||||||
return self._profiles[num]
|
return self._profiles[num]
|
||||||
|
|
||||||
|
@ -1058,7 +1080,6 @@ class BehaviorSettings(SettingsList):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
toggle_data=[
|
toggle_data=[
|
||||||
(_("Support “MPRIS”"), "mpris", True),
|
(_("Support “MPRIS”"), "mpris", True),
|
||||||
(_("Use “Album Artist” tag"), "use-album-artist", False),
|
|
||||||
(_("Sort albums by year"), "sort-albums-by-year", False),
|
(_("Sort albums by year"), "sort-albums-by-year", False),
|
||||||
(_("Send notification on title change"), "send-notify", False),
|
(_("Send notification on title change"), "send-notify", False),
|
||||||
(_("Play selected albums and titles immediately"), "force-mode", False),
|
(_("Play selected albums and titles immediately"), "force-mode", False),
|
||||||
|
@ -1657,36 +1678,32 @@ class AlbumPopover(Gtk.Popover):
|
||||||
self.add(songs_window)
|
self.add(songs_window)
|
||||||
songs_window.show_all()
|
songs_window.show_all()
|
||||||
|
|
||||||
def open(self, album, album_artist, date, genre, widget, x, y):
|
def open(self, albumartist, albumartistsort, album, albumsort, date, widget, x, y):
|
||||||
self._rect.x=x
|
self._rect.x=x
|
||||||
self._rect.y=y
|
self._rect.y=y
|
||||||
self.set_pointing_to(self._rect)
|
self.set_pointing_to(self._rect)
|
||||||
self.set_relative_to(widget)
|
self.set_relative_to(widget)
|
||||||
self._scroll.set_max_content_height(4*widget.get_allocated_height()//7)
|
self._scroll.set_max_content_height(4*widget.get_allocated_height()//7)
|
||||||
self._store.clear()
|
self._store.clear()
|
||||||
if genre is None:
|
tag_filter=("albumartist", albumartist, "albumartistsort", albumartistsort, "album", album, "albumsort", albumsort, "date", date)
|
||||||
genre_filter=()
|
count=self._client.count(*tag_filter)
|
||||||
else:
|
|
||||||
genre_filter=("genre", genre)
|
|
||||||
artist_type=self._settings.get_artist_type()
|
|
||||||
count=self._client.count(artist_type, album_artist, "album", album, "date", date, *genre_filter)
|
|
||||||
duration=str(Duration(float(count["playtime"])))
|
duration=str(Duration(float(count["playtime"])))
|
||||||
length=int(count["songs"])
|
length=int(count["songs"])
|
||||||
text=ngettext("{number} song ({duration})", "{number} songs ({duration})", length).format(number=length, duration=duration)
|
text=ngettext("{number} song ({duration})", "{number} songs ({duration})", length).format(number=length, duration=duration)
|
||||||
self._column_title.set_title(" • ".join([_("Title"), text]))
|
self._column_title.set_title(" • ".join([_("Title"), text]))
|
||||||
self._client.restrict_tagtypes("track", "title", "artist")
|
self._client.restrict_tagtypes("track", "title", "artist")
|
||||||
songs=self._client.find("album", album, "date", date, artist_type, album_artist, *genre_filter)
|
songs=self._client.find(*tag_filter)
|
||||||
self._client.tagtypes("all")
|
self._client.tagtypes("all")
|
||||||
for song in songs:
|
for song in songs:
|
||||||
track=song["track"][0]
|
track=song["track"][0]
|
||||||
title=song["title"][0]
|
title=song["title"][0]
|
||||||
# only show artists =/= albumartist
|
# only show artists =/= albumartist
|
||||||
try:
|
try:
|
||||||
song["artist"].remove(album_artist)
|
song["artist"].remove(albumartist)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
artist=str(song["artist"])
|
artist=str(song["artist"])
|
||||||
if artist == album_artist or not artist:
|
if artist == albumartist or not artist:
|
||||||
title_artist=f"<b>{GLib.markup_escape_text(title)}</b>"
|
title_artist=f"<b>{GLib.markup_escape_text(title)}</b>"
|
||||||
else:
|
else:
|
||||||
title_artist=f"<b>{GLib.markup_escape_text(title)}</b> • {GLib.markup_escape_text(artist)}"
|
title_artist=f"<b>{GLib.markup_escape_text(title)}</b> • {GLib.markup_escape_text(artist)}"
|
||||||
|
@ -1939,9 +1956,9 @@ class SelectionList(TreeView):
|
||||||
self._selected_path=None
|
self._selected_path=None
|
||||||
|
|
||||||
# store
|
# store
|
||||||
# (item, weight, initial-letter, weight-initials)
|
# (item, weight, initial-letter, weight-initials, sort-string)
|
||||||
self._store=Gtk.ListStore(str, Pango.Weight, str, Pango.Weight)
|
self._store=Gtk.ListStore(str, Pango.Weight, str, Pango.Weight, str)
|
||||||
self._store.append([self.select_all_string, Pango.Weight.BOOK, "", Pango.Weight.BOOK])
|
self._store.append([self.select_all_string, Pango.Weight.BOOK, "", Pango.Weight.BOOK, ""])
|
||||||
self.set_model(self._store)
|
self.set_model(self._store)
|
||||||
self._selection=self.get_selection()
|
self._selection=self.get_selection()
|
||||||
|
|
||||||
|
@ -1962,27 +1979,28 @@ class SelectionList(TreeView):
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self._store.clear()
|
self._store.clear()
|
||||||
self._store.append([self.select_all_string, Pango.Weight.BOOK, "", Pango.Weight.BOOK])
|
self._store.append([self.select_all_string, Pango.Weight.BOOK, "", Pango.Weight.BOOK, ""])
|
||||||
self._selected_path=None
|
self._selected_path=None
|
||||||
self.emit("clear")
|
self.emit("clear")
|
||||||
|
|
||||||
def set_items(self, items):
|
def set_items(self, items):
|
||||||
self.clear()
|
self.clear()
|
||||||
current_char=""
|
current_char=""
|
||||||
items.sort(key=locale.strxfrm)
|
items.sort(key=lambda item: locale.strxfrm(item[1]))
|
||||||
items.sort(key=lambda item: locale.strxfrm(item[:1]))
|
items.sort(key=lambda item: locale.strxfrm(item[1][:1]))
|
||||||
for item in items:
|
for item in items:
|
||||||
if current_char == item[:1].upper():
|
if current_char == item[1][:1].upper():
|
||||||
self._store.insert_with_valuesv(-1, range(4), [item, Pango.Weight.BOOK, "", Pango.Weight.BOOK])
|
self._store.insert_with_valuesv(-1, range(5), [item[0], Pango.Weight.BOOK, "", Pango.Weight.BOOK, item[1]])
|
||||||
else:
|
else:
|
||||||
self._store.insert_with_valuesv(-1, range(4), [item, Pango.Weight.BOOK, item[:1].upper(), Pango.Weight.BOLD])
|
self._store.insert_with_valuesv(
|
||||||
current_char=item[:1].upper()
|
-1, range(5), [item[0], Pango.Weight.BOOK, item[1][:1].upper(), Pango.Weight.BOLD, item[1]])
|
||||||
|
current_char=item[1][:1].upper()
|
||||||
|
|
||||||
def get_item(self, path):
|
def get_item_at_path(self, path):
|
||||||
if path == Gtk.TreePath(0):
|
if path == Gtk.TreePath(0):
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return self._store[path][0]
|
return self._store[path][0,4]
|
||||||
|
|
||||||
def length(self):
|
def length(self):
|
||||||
return len(self._store)-1
|
return len(self._store)-1
|
||||||
|
@ -1995,7 +2013,7 @@ class SelectionList(TreeView):
|
||||||
row_num=len(self._store)
|
row_num=len(self._store)
|
||||||
for i in range(0, row_num):
|
for i in range(0, row_num):
|
||||||
path=Gtk.TreePath(i)
|
path=Gtk.TreePath(i)
|
||||||
if self._store[path][0] == item:
|
if self._store[path][0] == item[0] and self._store[path][4] == item[1]:
|
||||||
self.select_path(path)
|
self.select_path(path)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -2003,13 +2021,14 @@ class SelectionList(TreeView):
|
||||||
self.set_cursor(Gtk.TreePath(0), None, False)
|
self.set_cursor(Gtk.TreePath(0), None, False)
|
||||||
self.row_activated(Gtk.TreePath(0), self._column_item)
|
self.row_activated(Gtk.TreePath(0), self._column_item)
|
||||||
|
|
||||||
def get_selected(self):
|
def get_path_selected(self):
|
||||||
if self._selected_path is None:
|
if self._selected_path is None:
|
||||||
raise ValueError("None selected")
|
raise ValueError("None selected")
|
||||||
elif self._selected_path == Gtk.TreePath(0):
|
|
||||||
return None
|
|
||||||
else:
|
else:
|
||||||
return self._store[self._selected_path][0]
|
return self._selected_path
|
||||||
|
|
||||||
|
def get_item_selected(self):
|
||||||
|
return self.get_item_at_path(self.get_path_selected())
|
||||||
|
|
||||||
def highlight_selected(self):
|
def highlight_selected(self):
|
||||||
self.set_cursor(self._selected_path, None, False)
|
self.set_cursor(self._selected_path, None, False)
|
||||||
|
@ -2036,7 +2055,8 @@ class GenreList(SelectionList):
|
||||||
self.select_all()
|
self.select_all()
|
||||||
|
|
||||||
def _refresh(self, *args):
|
def _refresh(self, *args):
|
||||||
self.set_items(self._client.comp_list("genre"))
|
l=self._client.comp_list("genre")
|
||||||
|
self.set_items(list(zip(l,l)))
|
||||||
self.select_all()
|
self.select_all()
|
||||||
|
|
||||||
def _on_disconnected(self, *args):
|
def _on_disconnected(self, *args):
|
||||||
|
@ -2069,18 +2089,17 @@ class ArtistList(SelectionList):
|
||||||
self.genre_list.connect_after("item-selected", self._refresh)
|
self.genre_list.connect_after("item-selected", self._refresh)
|
||||||
|
|
||||||
def _refresh(self, *args):
|
def _refresh(self, *args):
|
||||||
genre=self.genre_list.get_selected()
|
genre=self.genre_list.get_item_selected()
|
||||||
if genre is None:
|
if genre is not None:
|
||||||
artists=self._client.comp_list(self._settings.get_artist_type())
|
genre=genre[0]
|
||||||
else:
|
artists=self._client.get_artists(genre)
|
||||||
artists=self._client.comp_list(self._settings.get_artist_type(), "genre", genre)
|
|
||||||
self.set_items(artists)
|
self.set_items(artists)
|
||||||
if genre is not None:
|
if genre is not None:
|
||||||
self.select_all()
|
self.select_all()
|
||||||
else:
|
else:
|
||||||
song=self._client.currentsong()
|
song=self._client.currentsong()
|
||||||
if song:
|
if song:
|
||||||
artist=song[self._settings.get_artist_type()][0]
|
artist=(song["albumartist"][0],song["albumartistsort"][0])
|
||||||
self.select(artist)
|
self.select(artist)
|
||||||
else:
|
else:
|
||||||
if self.length() > 0:
|
if self.length() > 0:
|
||||||
|
@ -2094,8 +2113,7 @@ class ArtistList(SelectionList):
|
||||||
path_re=widget.get_path_at_pos(int(event.x), int(event.y))
|
path_re=widget.get_path_at_pos(int(event.x), int(event.y))
|
||||||
if path_re is not None:
|
if path_re is not None:
|
||||||
path=path_re[0]
|
path=path_re[0]
|
||||||
genre=self.genre_list.get_selected()
|
artist,genre=self.get_artist_at_path(path)
|
||||||
artist=self.get_item(path)
|
|
||||||
if event.button == 1:
|
if event.button == 1:
|
||||||
self._client.artist_to_playlist(artist, genre, "play")
|
self._client.artist_to_playlist(artist, genre, "play")
|
||||||
elif event.button == 2:
|
elif event.button == 2:
|
||||||
|
@ -2103,20 +2121,28 @@ class ArtistList(SelectionList):
|
||||||
elif event.button == 3:
|
elif event.button == 3:
|
||||||
self._artist_popover.open(artist, genre, self, event.x, event.y)
|
self._artist_popover.open(artist, genre, self, event.x, event.y)
|
||||||
|
|
||||||
|
def get_artist_at_path(self, path):
|
||||||
|
genre=self.genre_list.get_item_selected()
|
||||||
|
artist=self.get_item_at_path(path)
|
||||||
|
if genre is not None:
|
||||||
|
genre=genre[0]
|
||||||
|
return (artist, genre)
|
||||||
|
|
||||||
|
def get_artist_selected(self):
|
||||||
|
return self.get_artist_at_path(self.get_path_selected())
|
||||||
|
|
||||||
def add_to_playlist(self, mode):
|
def add_to_playlist(self, mode):
|
||||||
selected_rows=self._selection.get_selected_rows()
|
selected_rows=self._selection.get_selected_rows()
|
||||||
if selected_rows is not None:
|
if selected_rows is not None:
|
||||||
path=selected_rows[1][0]
|
path=selected_rows[1][0]
|
||||||
genre=self.genre_list.get_selected()
|
artist,genre=self.get_artist_at_path(path)
|
||||||
artist=self.get_item(path)
|
|
||||||
self._client.artist_to_playlist(artist, genre, mode)
|
self._client.artist_to_playlist(artist, genre, mode)
|
||||||
|
|
||||||
def show_info(self):
|
def show_info(self):
|
||||||
treeview, treeiter=self._selection.get_selected()
|
treeview, treeiter=self._selection.get_selected()
|
||||||
if treeiter is not None:
|
if treeiter is not None:
|
||||||
path=self._store.get_path(treeiter)
|
path=self._store.get_path(treeiter)
|
||||||
genre=self.genre_list.get_selected()
|
artist,genre=self.get_artist_at_path(path)
|
||||||
artist=self.get_item(path)
|
|
||||||
self._artist_popover.open(artist, genre, self, *self.get_popover_point(path))
|
self._artist_popover.open(artist, genre, self, *self.get_popover_point(path))
|
||||||
|
|
||||||
def _on_disconnected(self, *args):
|
def _on_disconnected(self, *args):
|
||||||
|
@ -2143,22 +2169,6 @@ class AlbumLoadingThread(threading.Thread):
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self._stop_flag=True
|
self._stop_flag=True
|
||||||
|
|
||||||
def _album_generator(self):
|
|
||||||
for artist in self._artists:
|
|
||||||
try: # client cloud meanwhile disconnect
|
|
||||||
grouped_albums=main_thread_function(self._client.list)(
|
|
||||||
"album", self._artist_type, artist, *self._genre_filter, "group", "date")
|
|
||||||
except (MPDBase.ConnectionError, ConnectionResetError) as e:
|
|
||||||
return
|
|
||||||
for album_group in grouped_albums:
|
|
||||||
date=album_group["date"]
|
|
||||||
if isinstance(album_group["album"], str):
|
|
||||||
albums=[album_group["album"]]
|
|
||||||
else:
|
|
||||||
albums=album_group["album"]
|
|
||||||
for album in albums:
|
|
||||||
yield {"name": album, "artist": artist, "date": date}
|
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self._settings.set_property("cursor-watch", True)
|
self._settings.set_property("cursor-watch", True)
|
||||||
self._progress_bar.show()
|
self._progress_bar.show()
|
||||||
|
@ -2167,32 +2177,27 @@ class AlbumLoadingThread(threading.Thread):
|
||||||
self._iconview.set_model(None)
|
self._iconview.set_model(None)
|
||||||
self._store.clear()
|
self._store.clear()
|
||||||
self._cover_size=self._settings.get_int("album-cover")
|
self._cover_size=self._settings.get_int("album-cover")
|
||||||
self._artist_type=self._settings.get_artist_type()
|
|
||||||
if self._genre is None:
|
|
||||||
self._genre_filter=()
|
|
||||||
else:
|
|
||||||
self._genre_filter=("genre", self._genre)
|
|
||||||
if self._artist is None:
|
if self._artist is None:
|
||||||
self._iconview.set_markup_column(2) # show artist names
|
self._iconview.set_markup_column(2) # show artist names
|
||||||
self._artists=self._client.comp_list(self._artist_type, *self._genre_filter)
|
|
||||||
else:
|
else:
|
||||||
self._iconview.set_markup_column(1) # hide artist names
|
self._iconview.set_markup_column(1) # hide artist names
|
||||||
self._artists=[self._artist]
|
self._albums=self._client.get_albums(self._artist, self._genre)
|
||||||
super().start()
|
super().start()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# temporarily display all albums with fallback cover
|
# temporarily display all albums with fallback cover
|
||||||
fallback_cover=GdkPixbuf.Pixbuf.new_from_file_at_size(FALLBACK_COVER, self._cover_size, self._cover_size)
|
fallback_cover=GdkPixbuf.Pixbuf.new_from_file_at_size(FALLBACK_COVER, self._cover_size, self._cover_size)
|
||||||
add=main_thread_function(self._store.append)
|
add=main_thread_function(self._store.append)
|
||||||
for i, album in enumerate(self._album_generator()):
|
for i, album in enumerate(self._albums):
|
||||||
# album label
|
# album label
|
||||||
if album["date"]:
|
if album["date"]:
|
||||||
display_label=f"<b>{GLib.markup_escape_text(album['name'])}</b> ({GLib.markup_escape_text(album['date'])})"
|
display_label=f"<b>{GLib.markup_escape_text(album['album'])}</b> ({GLib.markup_escape_text(album['date'])})"
|
||||||
else:
|
else:
|
||||||
display_label=f"<b>{GLib.markup_escape_text(album['name'])}</b>"
|
display_label=f"<b>{GLib.markup_escape_text(album['album'])}</b>"
|
||||||
display_label_artist=f"{display_label}\n{GLib.markup_escape_text(album['artist'])}"
|
display_label_artist=f"{display_label}\n{GLib.markup_escape_text(album['albumartist'])}"
|
||||||
# add album
|
# add album
|
||||||
add([fallback_cover,display_label,display_label_artist,album["name"],album["date"],album["artist"]])
|
add([fallback_cover,display_label,display_label_artist,
|
||||||
|
album["albumartist"],album["albumartistsort"],album["album"],album["albumsort"],album["date"]])
|
||||||
if i%10 == 0:
|
if i%10 == 0:
|
||||||
if self._stop_flag:
|
if self._stop_flag:
|
||||||
self._exit()
|
self._exit()
|
||||||
|
@ -2200,9 +2205,9 @@ class AlbumLoadingThread(threading.Thread):
|
||||||
GLib.idle_add(self._progress_bar.pulse)
|
GLib.idle_add(self._progress_bar.pulse)
|
||||||
# sort model
|
# sort model
|
||||||
if main_thread_function(self._settings.get_boolean)("sort-albums-by-year"):
|
if main_thread_function(self._settings.get_boolean)("sort-albums-by-year"):
|
||||||
main_thread_function(self._store.set_sort_column_id)(4, Gtk.SortType.ASCENDING)
|
main_thread_function(self._store.set_sort_column_id)(7, Gtk.SortType.ASCENDING)
|
||||||
else:
|
else:
|
||||||
main_thread_function(self._store.set_sort_column_id)(1, Gtk.SortType.ASCENDING)
|
main_thread_function(self._store.set_sort_column_id)(6, Gtk.SortType.ASCENDING)
|
||||||
GLib.idle_add(self._iconview.set_model, self._store)
|
GLib.idle_add(self._iconview.set_model, self._store)
|
||||||
# load covers
|
# load covers
|
||||||
total=2*len(self._store)
|
total=2*len(self._store)
|
||||||
|
@ -2212,7 +2217,9 @@ class AlbumLoadingThread(threading.Thread):
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
self._client.restrict_tagtypes("albumartist", "album")
|
self._client.restrict_tagtypes("albumartist", "album")
|
||||||
song=self._client.find("album",row[3],"date",row[4],self._artist_type,row[5],*self._genre_filter,"window","0:1")[0]
|
song=self._client.find("albumartist", row[3], "albumartistsort",
|
||||||
|
row[4], "album", row[5], "albumsort", row[6],
|
||||||
|
"date", row[7], "window", "0:1")[0]
|
||||||
self._client.tagtypes("all")
|
self._client.tagtypes("all")
|
||||||
return self._client.get_cover(song)
|
return self._client.get_cover(song)
|
||||||
covers=[]
|
covers=[]
|
||||||
|
@ -2256,8 +2263,8 @@ class AlbumList(Gtk.IconView):
|
||||||
self._client=client
|
self._client=client
|
||||||
self._artist_list=artist_list
|
self._artist_list=artist_list
|
||||||
|
|
||||||
# cover, display_label, display_label_artist, album, date, artist
|
# cover, display_label, display_label_artist, albumartist, albumartistsort, album, albumsort, date
|
||||||
self._store=Gtk.ListStore(GdkPixbuf.Pixbuf, str, str, str, str, str)
|
self._store=Gtk.ListStore(GdkPixbuf.Pixbuf, str, str, str, str, str, str, str)
|
||||||
self._store.set_default_sort_func(lambda *args: 0)
|
self._store.set_default_sort_func(lambda *args: 0)
|
||||||
self.set_model(self._store)
|
self.set_model(self._store)
|
||||||
|
|
||||||
|
@ -2266,7 +2273,6 @@ class AlbumList(Gtk.IconView):
|
||||||
|
|
||||||
# popover
|
# popover
|
||||||
self._album_popover=AlbumPopover(self._client, self._settings)
|
self._album_popover=AlbumPopover(self._client, self._settings)
|
||||||
self._artist_popover=ArtistPopover(self._client)
|
|
||||||
|
|
||||||
# cover thread
|
# cover thread
|
||||||
self._cover_thread=AlbumLoadingThread(self._client, self._settings, self.progress_bar, self, self._store, None, None)
|
self._cover_thread=AlbumLoadingThread(self._client, self._settings, self.progress_bar, self, self._store, None, None)
|
||||||
|
@ -2290,7 +2296,6 @@ class AlbumList(Gtk.IconView):
|
||||||
def _clear(self, *args):
|
def _clear(self, *args):
|
||||||
def callback():
|
def callback():
|
||||||
self._album_popover.popdown()
|
self._album_popover.popdown()
|
||||||
self._artist_popover.popdown()
|
|
||||||
self._workaround_clear()
|
self._workaround_clear()
|
||||||
if self._cover_thread.is_alive():
|
if self._cover_thread.is_alive():
|
||||||
self._cover_thread.set_callback(callback)
|
self._cover_thread.set_callback(callback)
|
||||||
|
@ -2306,7 +2311,7 @@ class AlbumList(Gtk.IconView):
|
||||||
row_num=len(self._store)
|
row_num=len(self._store)
|
||||||
for i in range(0, row_num):
|
for i in range(0, row_num):
|
||||||
path=Gtk.TreePath(i)
|
path=Gtk.TreePath(i)
|
||||||
if self._store[path][3] == album:
|
if self._store[path][5] == album:
|
||||||
self.set_cursor(path, None, False)
|
self.set_cursor(path, None, False)
|
||||||
self.select_path(path)
|
self.select_path(path)
|
||||||
self.scroll_to_path(path, True, 0, 0)
|
self.scroll_to_path(path, True, 0, 0)
|
||||||
|
@ -2319,16 +2324,15 @@ class AlbumList(Gtk.IconView):
|
||||||
def _sort_settings(self, *args):
|
def _sort_settings(self, *args):
|
||||||
if not self._cover_thread.is_alive():
|
if not self._cover_thread.is_alive():
|
||||||
if self._settings.get_boolean("sort-albums-by-year"):
|
if self._settings.get_boolean("sort-albums-by-year"):
|
||||||
self._store.set_sort_column_id(4, Gtk.SortType.ASCENDING)
|
self._store.set_sort_column_id(7, Gtk.SortType.ASCENDING)
|
||||||
else:
|
else:
|
||||||
self._store.set_sort_column_id(1, Gtk.SortType.ASCENDING)
|
self._store.set_sort_column_id(6, Gtk.SortType.ASCENDING)
|
||||||
|
|
||||||
def _refresh(self, *args):
|
def _refresh(self, *args):
|
||||||
def callback():
|
def callback():
|
||||||
if self._cover_thread.is_alive(): # already started?
|
if self._cover_thread.is_alive(): # already started?
|
||||||
return False
|
return False
|
||||||
artist=self._artist_list.get_selected()
|
artist,genre=self._artist_list.get_artist_selected()
|
||||||
genre=self._artist_list.genre_list.get_selected()
|
|
||||||
self._cover_thread=AlbumLoadingThread(self._client,self._settings,self.progress_bar,self,self._store,artist,genre)
|
self._cover_thread=AlbumLoadingThread(self._client,self._settings,self.progress_bar,self,self._store,artist,genre)
|
||||||
self._cover_thread.start()
|
self._cover_thread.start()
|
||||||
if self._cover_thread.is_alive():
|
if self._cover_thread.is_alive():
|
||||||
|
@ -2338,11 +2342,8 @@ class AlbumList(Gtk.IconView):
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
def _path_to_playlist(self, path, mode="default"):
|
def _path_to_playlist(self, path, mode="default"):
|
||||||
album=self._store[path][3]
|
tags=self._store[path][3:8]
|
||||||
year=self._store[path][4]
|
self._client.album_to_playlist(*tags, mode)
|
||||||
artist=self._store[path][5]
|
|
||||||
genre=self._artist_list.genre_list.get_selected()
|
|
||||||
self._client.album_to_playlist(album, artist, year, genre, mode)
|
|
||||||
|
|
||||||
def _on_button_press_event(self, widget, event):
|
def _on_button_press_event(self, widget, event):
|
||||||
path=widget.get_path_at_pos(int(event.x), int(event.y))
|
path=widget.get_path_at_pos(int(event.x), int(event.y))
|
||||||
|
@ -2355,16 +2356,10 @@ class AlbumList(Gtk.IconView):
|
||||||
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
|
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||||
v=self.get_vadjustment().get_value()
|
v=self.get_vadjustment().get_value()
|
||||||
h=self.get_hadjustment().get_value()
|
h=self.get_hadjustment().get_value()
|
||||||
genre=self._artist_list.genre_list.get_selected()
|
|
||||||
if path is not None:
|
if path is not None:
|
||||||
album=self._store[path][3]
|
tags=self._store[path][3:8]
|
||||||
year=self._store[path][4]
|
|
||||||
artist=self._store[path][5]
|
|
||||||
# when using "button-press-event" in iconview popovers only show up in combination with idle_add (bug in GTK?)
|
# when using "button-press-event" in iconview popovers only show up in combination with idle_add (bug in GTK?)
|
||||||
GLib.idle_add(self._album_popover.open, album, artist, year, genre, widget, event.x-h, event.y-v)
|
GLib.idle_add(self._album_popover.open, *tags, widget, event.x-h, event.y-v)
|
||||||
else:
|
|
||||||
artist=self._artist_list.get_selected()
|
|
||||||
GLib.idle_add(self._artist_popover.open, artist, genre, widget, event.x-h, event.y-v)
|
|
||||||
|
|
||||||
def _on_item_activated(self, widget, path):
|
def _on_item_activated(self, widget, path):
|
||||||
self._path_to_playlist(path)
|
self._path_to_playlist(path)
|
||||||
|
@ -2383,8 +2378,8 @@ class AlbumList(Gtk.IconView):
|
||||||
rect=self.get_allocation()
|
rect=self.get_allocation()
|
||||||
x=max(min(rect.x+cell.width//2, rect.x+rect.width), rect.x)
|
x=max(min(rect.x+cell.width//2, rect.x+rect.width), rect.x)
|
||||||
y=max(min(cell.y+cell.height//2, rect.y+rect.height), rect.y)
|
y=max(min(cell.y+cell.height//2, rect.y+rect.height), rect.y)
|
||||||
genre=self._artist_list.genre_list.get_selected()
|
tags=self._store[path][3:8]
|
||||||
self._album_popover.open(self._store[path][3], self._store[path][5], self._store[path][4], genre, self, x, y)
|
self._album_popover.open(*tags, self, x, y)
|
||||||
|
|
||||||
def add_to_playlist(self, mode):
|
def add_to_playlist(self, mode):
|
||||||
paths=self.get_selected_items()
|
paths=self.get_selected_items()
|
||||||
|
@ -2428,16 +2423,15 @@ class Browser(Gtk.Paned):
|
||||||
def back_to_current_album(self, force=False):
|
def back_to_current_album(self, force=False):
|
||||||
song=self._client.currentsong()
|
song=self._client.currentsong()
|
||||||
if song:
|
if song:
|
||||||
# get artist name
|
artist,genre=self._artist_list.get_artist_selected()
|
||||||
artist=song[self._settings.get_artist_type()][0]
|
|
||||||
# deactivate genre filter to show all artists (if needed)
|
# deactivate genre filter to show all artists (if needed)
|
||||||
if song["genre"][0] != self._genre_list.get_selected() or force:
|
if song["genre"][0] != genre or force:
|
||||||
self._genre_list.deactivate()
|
self._genre_list.deactivate()
|
||||||
# select artist
|
# select artist
|
||||||
if self._artist_list.get_selected() is None and not force: # all artists selected
|
if artist is None and not force: # all artists selected
|
||||||
self._artist_list.highlight_selected()
|
self._artist_list.highlight_selected()
|
||||||
else: # one artist selected
|
else: # one artist selected
|
||||||
self._artist_list.select(artist)
|
self._artist_list.select((song["albumartist"][0],song["albumartistsort"][0]))
|
||||||
self._album_list.scroll_to_current_album()
|
self._album_list.scroll_to_current_album()
|
||||||
else:
|
else:
|
||||||
self._genre_list.deactivate()
|
self._genre_list.deactivate()
|
||||||
|
@ -2844,17 +2838,16 @@ class CoverEventBox(Gtk.EventBox):
|
||||||
if self._client.connected():
|
if self._client.connected():
|
||||||
song=self._client.currentsong()
|
song=self._client.currentsong()
|
||||||
if song:
|
if song:
|
||||||
artist=song[self._settings.get_artist_type()][0]
|
tags=(song["albumartist"][0], song["albumartistsort"][0],
|
||||||
album=song["album"][0]
|
song["album"][0], song["albumsort"][0], song["date"][0])
|
||||||
year=song["date"][0]
|
|
||||||
if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
|
if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||||
self._client.album_to_playlist(album, artist, year, None)
|
self._client.album_to_playlist(*tags)
|
||||||
elif event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
|
elif event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
|
||||||
self._client.album_to_playlist(album, artist, year, None, "play")
|
self._client.album_to_playlist(*tags, "play")
|
||||||
elif event.button == 2 and event.type == Gdk.EventType.BUTTON_PRESS:
|
elif event.button == 2 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||||
self._client.album_to_playlist(album, artist, year, None, "append")
|
self._client.album_to_playlist(*tags, "append")
|
||||||
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
|
elif event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS:
|
||||||
self._album_popover.open(album, artist, year, None, widget, event.x, event.y)
|
self._album_popover.open(*tags, widget, event.x, event.y)
|
||||||
|
|
||||||
def _on_disconnected(self, *args):
|
def _on_disconnected(self, *args):
|
||||||
self._album_popover.popdown()
|
self._album_popover.popdown()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user