use new mpd commands 'albumart' and 'readpicture' to load missing covers

This commit is contained in:
Martin Wagner 2021-04-11 18:54:19 +02:00
parent c01360b298
commit 7113d9d5e4

View File

@ -389,9 +389,9 @@ class MPRISInterface: # TODO emit Seeked if needed
else: else:
lib_path=self._settings.get_value("paths")[self._settings.get_int("active-profile")] lib_path=self._settings.get_value("paths")[self._settings.get_int("active-profile")]
self._metadata["xesam:url"]=GLib.Variant("s", "file://{}".format(os.path.join(lib_path, song_file))) self._metadata["xesam:url"]=GLib.Variant("s", "file://{}".format(os.path.join(lib_path, song_file)))
cover=Cover(self._settings, mpd_meta) cover_path=self._client.get_cover_path(mpd_meta)
if cover.path is not None: if cover_path is not None:
self._metadata["mpris:artUrl"]=GLib.Variant("s", "file://{}".format(cover.path)) self._metadata["mpris:artUrl"]=GLib.Variant("s", "file://{}".format(cover_path))
def _update_property(self, interface_name, prop): def _update_property(self, interface_name, prop):
getter, setter=self._prop_mapping[interface_name][prop] getter, setter=self._prop_mapping[interface_name][prop]
@ -501,6 +501,18 @@ class ClientHelper():
length=length+float(song.get("duration", 0.0)) length=length+float(song.get("duration", 0.0))
return ClientHelper.seconds_to_display_time(int(length)) return ClientHelper.seconds_to_display_time(int(length))
def binary_to_pixbuf(binary, size):
loader=GdkPixbuf.PixbufLoader.new()
loader.write(binary)
loader.close()
raw_pixbuf=loader.get_pixbuf()
ratio=raw_pixbuf.get_width()/raw_pixbuf.get_height()
if ratio > 1:
pixbuf=raw_pixbuf.scale_simple(size,size/ratio,GdkPixbuf.InterpType.BILINEAR)
else:
pixbuf=raw_pixbuf.scale_simple(size*ratio,size,GdkPixbuf.InterpType.BILINEAR)
return pixbuf
class EventEmitter(GObject.Object): class EventEmitter(GObject.Object):
__gsignals__={ __gsignals__={
"update": (GObject.SignalFlags.RUN_FIRST, None, ()), "update": (GObject.SignalFlags.RUN_FIRST, None, ()),
@ -532,6 +544,7 @@ class Client(MPDClient):
self._last_status={} self._last_status={}
self._refresh_interval=self._settings.get_int("refresh-interval") self._refresh_interval=self._settings.get_int("refresh-interval")
self._main_timeout_id=None self._main_timeout_id=None
self.fallback_cover=Gtk.IconTheme.get_default().lookup_icon("media-optical", 128, Gtk.IconLookupFlags.FORCE_SVG).get_filename()
# connect # connect
self._settings.connect("changed::active-profile", self._on_active_profile_changed) self._settings.connect("changed::active-profile", self._on_active_profile_changed)
@ -670,6 +683,63 @@ class Client(MPDClient):
else: else:
return([]) return([])
def get_cover_path(self, raw_song):
path=None
song=ClientHelper.song_to_first_str_dict(raw_song)
song_file=song.get("file")
active_profile=self._settings.get_int("active-profile")
lib_path=self._settings.get_lib_path()
if lib_path is not None:
regex_str=self._settings.get_value("regex")[active_profile]
if regex_str == "":
regex=re.compile(COVER_REGEX, flags=re.IGNORECASE)
else:
regex_str=regex_str.replace("%AlbumArtist%", song.get("albumartist", ""))
regex_str=regex_str.replace("%Album%", song.get("album", ""))
try:
regex=re.compile(regex_str, flags=re.IGNORECASE)
except:
print("illegal regex:", regex_str)
return (None, None)
if song_file is not None:
song_dir=os.path.join(lib_path, os.path.dirname(song_file))
if song_dir.endswith(".cue"):
song_dir=os.path.dirname(song_dir) # get actual directory of .cue file
if os.path.exists(song_dir):
for f in os.listdir(song_dir):
if regex.match(f):
path=os.path.join(song_dir, f)
break
return path
def get_cover_binary(self, uri):
if uri is None:
binary=None
else:
try:
binary=self.albumart(uri)["binary"]
except:
try:
binary=self.readpicture(uri)["binary"]
except:
binary=None
return binary
def get_cover(self, song, size):
cover_path=self.get_cover_path(song)
if cover_path is None:
cover_binary=self.get_cover_binary(song.get("file"))
if cover_binary is None:
pixbuf=GdkPixbuf.Pixbuf.new_from_file_at_size(self.fallback_cover, size, size)
else:
pixbuf=ClientHelper.binary_to_pixbuf(cover_binary, size)
else:
try:
pixbuf=GdkPixbuf.Pixbuf.new_from_file_at_size(cover_path, size, size)
except: # load fallback if cover can't be loaded (GLib: Couldnt recognize the image file format for file...)
pixbuf=GdkPixbuf.Pixbuf.new_from_file_at_size(self.fallback_cover, size, size)
return pixbuf
def get_metadata(self, uri): def get_metadata(self, uri):
meta_base=self.lsinfo(uri)[0] meta_base=self.lsinfo(uri)[0]
try: # .cue files produce an error here try: # .cue files produce an error here
@ -702,7 +772,15 @@ class Client(MPDClient):
years=self.comp_list("date", "album", album, artist_type, artist) years=self.comp_list("date", "album", album, artist_type, artist)
for year in years: for year in years:
songs=self.find("album", album, "date", year, artist_type, artist, *genre_filter) songs=self.find("album", album, "date", year, artist_type, artist, *genre_filter)
albums.append({"artist": artist, "album": album, "year": year, "songs": songs}) cover_path=self.get_cover_path(songs[0])
if cover_path is None:
cover_binary=self.get_cover_binary(songs[0].get("file"))
if cover_binary is None:
albums.append({"artist": artist, "album": album, "year": year, "songs": songs})
else:
albums.append({"artist":artist,"album":album,"year":year,"songs":songs,"cover_binary":cover_binary})
else:
albums.append({"artist": artist, "album": album, "year": year, "songs": songs, "cover_path": cover_path})
return albums return albums
def toggle_play(self): def toggle_play(self):
@ -1763,47 +1841,6 @@ class ArtistPopover(Gtk.Popover):
self._client.artist_to_playlist(self._artist, self._genre, mode) self._client.artist_to_playlist(self._artist, self._genre, mode)
self.popdown() self.popdown()
class Cover(object):
def __init__(self, settings, raw_song):
self.path=None
song=ClientHelper.song_to_first_str_dict(raw_song)
if song != {}:
song_file=song["file"]
active_profile=settings.get_int("active-profile")
lib_path=settings.get_lib_path()
if lib_path is not None:
regex_str=settings.get_value("regex")[active_profile]
if regex_str == "":
regex=re.compile(COVER_REGEX, flags=re.IGNORECASE)
else:
regex_str=regex_str.replace("%AlbumArtist%", song.get("albumartist", ""))
regex_str=regex_str.replace("%Album%", song.get("album", ""))
try:
regex=re.compile(regex_str, flags=re.IGNORECASE)
except:
print("illegal regex:", regex_str)
return
if song_file is not None:
song_dir=os.path.join(lib_path, os.path.dirname(song_file))
if song_dir.endswith(".cue"):
song_dir=os.path.dirname(song_dir) # get actual directory of .cue file
if os.path.exists(song_dir):
for f in os.listdir(song_dir):
if regex.match(f):
self.path=os.path.join(song_dir, f)
break
def get_pixbuf(self, size):
if self.path is None: # fallback needed
path=Gtk.IconTheme.get_default().lookup_icon("media-optical", size, Gtk.IconLookupFlags.FORCE_SVG).get_filename()
return GdkPixbuf.Pixbuf.new_from_file_at_size(path, size, size)
else:
try:
return GdkPixbuf.Pixbuf.new_from_file_at_size(self.path, size, size)
except: # load fallback if cover can't be loaded (GLib: Couldnt recognize the image file format for file...)
path=Gtk.IconTheme.get_default().lookup_icon("media-optical", size, Gtk.IconLookupFlags.FORCE_SVG).get_filename()
return GdkPixbuf.Pixbuf.new_from_file_at_size(path, size, size)
########### ###########
# browser # # browser #
########### ###########
@ -2288,10 +2325,6 @@ class AlbumWindow(FocusFrame):
else: else:
self._store.set_sort_column_id(1, Gtk.SortType.ASCENDING) self._store.set_sort_column_id(1, Gtk.SortType.ASCENDING)
def _add_row(self, row): # needed for GLib.idle
self._store.append(row)
return False # stop after one run
def _refresh(self, *args): def _refresh(self, *args):
if self._done: if self._done:
self._done=False self._done=False
@ -2362,20 +2395,29 @@ class AlbumWindow(FocusFrame):
self._done_callback() self._done_callback()
return False return False
def load_covers(): def render_covers():
size=self._settings.get_int("album-cover") size=self._settings.get_int("album-cover")
total_albums=len(albums) total_albums=len(albums)
for i, album in enumerate(albums): for i, album in enumerate(albums):
if self._stop_flag: if self._stop_flag:
break break
album["cover"]=Cover(self._settings, album["songs"][0]).get_pixbuf(size) if "cover_path" in album:
try:
album["cover"]=GdkPixbuf.Pixbuf.new_from_file_at_size(album["cover_path"], size, size)
except: # load fallback if cover can't be loaded
album["cover"]=GdkPixbuf.Pixbuf.new_from_file_at_size(self._client.fallback_cover, size, size)
else:
if "cover_binary" in album:
album["cover"]=ClientHelper.binary_to_pixbuf(album["cover_binary"], size)
else:
album["cover"]=GdkPixbuf.Pixbuf.new_from_file_at_size(self._client.fallback_cover, size, size)
GLib.idle_add(self._progress_bar.set_fraction, (i+1)/total_albums) GLib.idle_add(self._progress_bar.set_fraction, (i+1)/total_albums)
if self._stop_flag: if self._stop_flag:
GLib.idle_add(self._done_callback) GLib.idle_add(self._done_callback)
else: else:
GLib.idle_add(display_albums) GLib.idle_add(display_albums)
cover_thread=threading.Thread(target=load_covers, daemon=True) cover_thread=threading.Thread(target=render_covers, daemon=True)
cover_thread.start() cover_thread.start()
elif not self._refresh in self._pending: elif not self._refresh in self._pending:
self._stop_flag=True self._stop_flag=True
@ -2777,11 +2819,11 @@ class MainCover(Gtk.Image):
def _refresh(self, *args): def _refresh(self, *args):
current_song=self._client.currentsong() current_song=self._client.currentsong()
self.set_from_pixbuf(Cover(self._settings, current_song).get_pixbuf(self._settings.get_int("track-cover"))) self.set_from_pixbuf(self._client.get_cover(current_song, self._settings.get_int("track-cover")))
def _on_disconnected(self, *args): def _on_disconnected(self, *args):
size=self._settings.get_int("track-cover") size=self._settings.get_int("track-cover")
self.set_from_pixbuf(Cover(self._settings, {}).get_pixbuf(size)) self.set_from_pixbuf(GdkPixbuf.Pixbuf.new_from_file_at_size(self._client.fallback_cover, size, size))
self.set_sensitive(False) self.set_sensitive(False)
def _on_reconnected(self, *args): def _on_reconnected(self, *args):
@ -3940,7 +3982,7 @@ class MainWindow(Gtk.ApplicationWindow):
if self._settings.get_boolean("send-notify"): if self._settings.get_boolean("send-notify"):
if not self.is_active() and self._client.status()["state"] == "play": if not self.is_active() and self._client.status()["state"] == "play":
notify=Notify.Notification.new(song["title"], song["artist"]+"\n"+song["album"]+date) notify=Notify.Notification.new(song["title"], song["artist"]+"\n"+song["album"]+date)
pixbuf=Cover(self._settings, song).get_pixbuf(400) pixbuf=self._client.get_cover(song, 400)
notify.set_image_from_pixbuf(pixbuf) notify.set_image_from_pixbuf(pixbuf)
notify.show() notify.show()